Shorn
Shorn

Reputation: 21544

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:

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

Upvotes: 14

Views: 9298

Answers (4)

Andres Collart
Andres Collart

Reputation: 11

Adding to these answers above, I had a hard time with trailingSlash:true and output:'export' working well together while hosted on AWS S3.

I had this in my next.config.ts:

const nextConfig: NextConfig = {
    trailingSlash: true,
    output: 'export',
...
};

but was using window.location.href = /booking?${queryParams.toString()}; to handle my routing, and on SSG pages it was breaking - either not passing the queryParams or with different settings not finding the page when deployed on AWS. This was fixed by using router.push like below:

import {useRouter, useSearchParams} from 'next/navigation';
const router = useRouter();
const pathname = `/booking?${queryParams.toString()}`;
router.push(pathname);

This lets you use NextJS SSG, trailingSlash:true, output:'export', and pass through the queryParams.

Upvotes: 1

nicholas chun
nicholas chun

Reputation: 19

To use static pages for nextjs using s3/cloudfront, you need enable website hosting on the s3 bucket. Copy the Bucket website endpoint and paste that as the cloudfront origin. NOTE: you will have to make the s3 bucket public for this to work. Optionally, you can use a header referrer to restrict access to the s3 bucket. Follow this documentation to see how you can host a static website with bucket endpoints.

https://repost.aws/knowledge-center/cloudfront-serve-static-website

Upvotes: 0

rayman
rayman

Reputation: 21

Followup to Shorn's self-answer of using the as field in the next/link component. This worked for me, however it would fail if I refreshed the page I was on.

Instead, I used exportPathMap to link my pages to a page.html equivalent that would be created when running next export.

The downside of this approach is that when running next start, those .html files will not be created or accessible. They will, however, from next dev. As I am creating a purely static website, I've now just been using next dev.

While making this change I was validating by manually copying my built assets from next export into S3 and hosting in CloudFront as Shorn was doing -- I no longer do this to validate and haven't had issues so far.

If anyone knows, let me know what I else may be missing by ignoring next start as part of development. This solution has worked for me so far though.

Upvotes: 2

Shorn
Shorn

Reputation: 21544

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.

Upvotes: 30

Related Questions