Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NextJS dynamic routing in Amazon CloudFront

I have an application that uses NextJS as a wrapper, and I make use of NextJS's dynamic routing feature. I had a problem when deploying it to CloudFront due to dns.com/path/page not being rendered, instead CloudFront expected it to be dns.com/path/page.html. I worked it around by applying this lambda-edge-nice-url solution. It works properly now. However, there's still one issue left: NextJS's dynamic routes. dsn.com/path/subpath/123 should work, since 123 is a dynamic parameter. However, that does no work. In only returns the page when I access dns.com/path/subpath/[id], which of course is not correct, since [id] is not a parameter I want to load.

The strangest thing is: if I try to access the URL as I stated above directly, it fails. However, inside the application I have buttons and links that redirect the user, and that works properly.

Navigating from inside the application (button with router.push inside its callback): enter image description here

Trying to access the url directly: enter image description here

Can anyone help me to properly route the requests?

like image 940
Pelicer Avatar asked Oct 24 '25 03:10

Pelicer


1 Answers

After trying a lot of different code, I finally came up with a Lambda edge expression that fixed two issues in one:

  • The need to insert .html at the end of the URL
  • NextJS dynamic routes that were't working on refresh or when accessed directly to URL.

The code below basically takes care of dynamic routes first. It uses a regex expression to understand the current URL and redirect the request to the proper [id].html file. After that, if the none of the regex are matched, and if the URL does not contain .html extension, it adds the extension and retrieves the correct file.

const config = {
    suffix: '.html',
    appendToDirs: 'index.html',
    removeTrailingSlash: false,
};

const regexSuffixless = /\/[^/.]+$/; // e.g. "/some/page" but not "/", "/some/" or "/some.jpg"
const regexTrailingSlash = /.+\/$/; // e.g. "/some/" or "/some/page/" but not root "/"
const dynamicRouteRegex = /\/subpath\/\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/; // e.g /urs/some-uuid; // e.g. '/subpath/uuid'

exports.handler = function handler(event, context, callback) {
    const { request } = event.Records[0].cf;
    const { uri } = request;
    const { suffix, appendToDirs, removeTrailingSlash } = config;
    
    //Checks for dynamic route and retrieves the proper [id].html file
    if (uri.match(dynamicRouteRegex)) {
        request.uri = "/subpath/[id].html";
        callback(null, request);
        return;
    }
    
    
    // Append ".html" to origin request
    if (suffix && uri.match(regexSuffixless)) {
        request.uri = uri + suffix;
        callback(null, request);
        return;
    }
    
    // Append "index.html" to origin request
    if (appendToDirs && uri.match(regexTrailingSlash)) {
        request.uri = uri + appendToDirs;
        callback(null, request);
        return;
    }

    // Redirect (301) non-root requests ending in "/" to URI without trailing slash
    if (removeTrailingSlash && uri.match(/.+\/$/)) {
        const response = {
            // body: '',
            // bodyEncoding: 'text',
            headers: {
                'location': [{
                    key: 'Location',
                    value: uri.slice(0, -1)
                 }]
            },
            status: '301',
            statusDescription: 'Moved Permanently'
        };
        callback(null, response);
        return;
    }

    // If nothing matches, return request unchanged
    callback(null, request);
};

Many thanks to @LongZheng for his answer. For some reason his code did not work for me, but it might for some, so check his answer out. Also, big shoutout to Manc, the creator of this lambda-edge-nice-urls repo. My code is basically a mix of both.

like image 143
Pelicer Avatar answered Oct 26 '25 16:10

Pelicer