Search code examples
amazon-web-servicesamazon-s3next.jsamazon-cloudfront

Next.js: How to make links work with exported sites when hosted on AWS Cloudfront?


I'm trying to get a prototype Next.js project up by doing a Static html export (i.e. next export) and then copying the generated output to AWS S3 and serving it via Cloudfront.

I've got the following two pages in the /pages directory:

  • index.tsx
  • Pricing.tsx

Then, following along from the routing doco I added a Link to the pricing page from the index page, like so:

<Link href="/Pricing">
  <a>Pricing</a>
</Link>

This results in a link that looks like example.com/Pricing (when you hover over it and when you click the link, the page does change to the pricing page and the browser shows example.com/Pricing in the URL bar).

The problem is, that link is not real - it cannot be bookmarked or navigated to directly via the url bar.

The problem seems to be that when I do a next export, Next.js generates a .html file for each page, but the router doesn't use those .html suffixes.

So when using the site, if the user tries to bookmark example.com/Pricing; loading that bookmark later will fail because Cloudfront will return a 404 (because CF only knows about the .html file).

I then tried changing my Link to look like:

<Link href="/Pricing.html">
  <a>Pricing</a>
</Link>

That causes the router to use example.com/Pricing.html and that works fine with Cloudfront - but it actually causes a 404 during local development (i.e. using next dev)!

Other workarounds I could try are renaming all the .html files and removing the extension before I upload them to S3 (and make sure they get a content-type: text/html header) - or introducing a Cloudfront lambda that does the renaming on the fly when .html resources are requested. I don't really want to do the lambda thing, but the renaming before uploading shouldn't be too difficult.

But it feels like I'm really working uphill here. Am I doing something wrong at a basic level? How is Next.js linking supposed to work with a static html export?

Next.js version: 9.5.3-canary.23


Solution

  • Alternate answer if you want your URLs to be "clean" and not have .html on the end.

    To get Next.js default URL links working properly with S3/Cloudfront, you must configure the "add a trailing slash" option in your next.config.js:

    module.exports = {
      trailingSlash: true,
    }
    

    As per the documentation

    export pages as index.html files and require trailing slashes, /about becomes /about/index.html and is routable via /about/. This was the default behavior prior to Next.js 9.

    So now you can leave your Link definition as:

    <Link href="/Pricing">
      <a>Pricing</a>
    </Link>
    

    This causes Next.js to do two things:

    • use the url example.com/Pricing/ - note the / on the end
    • generate each page as index.html in it's own directory - e.g. /Pricing/index.html

    Many HTML servers, in their default configuration, will serve up the index.html from inside the matching directory if they see a trailing / character in the URL.

    S3 will do this also, if you have it set up to serve as a website and IFF you access the URL through the website endpoint, as opposed to the REST endpoint.

    So your Cloudfront distribution origin must be configured as a Origin type = Custom Origin pointing at a domain something like example.com.s3-website.us-east-1.amazonaws.com, not as an S3 Origin.

    If you have your Cloudfront/S3 mis-configured, when you hit a "trailing slash" style URL - you will probably see your browser download a file of type binary/octet-stream containing 0 bytes.


    Edit: Beware pages with . characters, as per issue 16617.