Reputation: 21544
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
Upvotes: 14
Views: 9298
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
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
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
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:
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.
Upvotes: 30