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
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:
example.com/Pricing/
- note the /
on the endindex.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.