FlightControl
FlightControl Home

Secret knowledge to self-host Next.js

Brandon Bayer

“Self-hosting” Next.js is commonly used to describe hosting it anywhere besides Vercel. In reality, it’s all just hosting, but whatever. 

Vercel does a lot of magic for Next.js hosting, but they don’t share what they do or how you can replicate it.

Thankfully, Vercel has made some efforts to make sure all core functionality works with next start. But it’s not production ready out of the box unless you don’t have a CDN, only have 1 server instance, and don’t use image optimization.

This guide attempts to cover everything you need for deploying production Next.js outside Vercel to both servers and serverless.

Deploy Next.js to servers or serverless functions?

There’s no right answer here, only tradeoffs.

comparison table with serverless

There are some exceptions, but these are the general rules.

Servers sound intimidating to people who have grown up on Vercel, because you hear about how much easier serverless is. But this rhetoric is misleading. The old fashioned way of manually managing virtual private servers (VPS), where you have to apply operating system patches, etc, is absolutely harder than serverless, but that is no longer the modern way to use servers. Today, those low level concerns are abstracted away by containerization, and this eliminates most of the infrastructure complexity of servers.

Most people using servers today are using “serverless containers,” where you just connect your code, set your CPU and memory, and git push. So from this perspective, servers are just as easy to deploy as serverless.

The infrastructure complexity for deploying Next.js serverlessly is much higher than that of a server with a CDN because the framework is very complex. See the OpenNext architecture diagram.

The operational complexity of serverless is much higher, because serverless deployments are fine-grained, distributed, asynchronous applications. This article on the Serverless Illusion explains more of the tradeoffs.

Deploy to serverless functions

Next.js does not officially support deploying to serverless functions. Vercel does a lot of proprietary magic for their serverless deployments. And they seem to be happy for it to be difficult to deploy serverlessly elsewhere.

OpenNext is a community adapter for converting Next.js builds into assets that can be deployed to serverless functions. It has a good number of production users. Because it’s not officially supported, keeping it working with each new Next.js version is a constant game of whack-a-mole, and breaking changes can occur with Next.js patch or minor releases. 

See the OpenNext documentation for all the information on serverless deployments with it.

AWS Amplify is another option for serverless deployments that works for some people. But some functionality is different than Vercel, may lag behind Next.js versions, and the deployed infrastructure is a black-box and not customizable.

Deploy to servers

Deploying Next.js to servers is the only officially supported way to deploy Next.js outside of Vercel. But it’s not production-ready out of the box. 

Image optimization

Image optimization fully works, but you need to install an extra dependency. The Next.js server will process images on-demand.

Without this, image optimization will work but will be slow and memory hungry because it uses code designed only for local development.

Alternatively, you can use a third-party service for image optimization by configuring a custom loader in Next.js.

Caching Next.js data & ISR

By default, Next.js uses local filesystem cache. This is problematic for production when you have more than one instance running because each instance will have its own data and ISR cache. This can cause lots of weird behavior.

CDN

You almost certainly want a CDN in front of your Next.js app to ensure blazing fast loading of static content and to reduce load on your servers.

CDNs might seem magical, but they are very simple. Any http request can include a Cache-Controlheader with any number of commands on where and when to cache responses. For example Cache-Control: s-maxage=3600 tells the CDN to cache the response for 60 minutes.

Next.js automatically sets Cache-control headers for most things, but a few of them are actually set incorrectly :(

You need a CDN to take advantage of this, like AWS CloudFrontCloudflare, or Fastly

Important caveat if you are using ISR/stale-while-revalidate features: you need a CDN that supports the stale-while-revalidate cache-control header, and as of this writing, only AWS CloudFront and Fastly support this. Cloudflare does not support it.

ISR & Stale-while-revalidate

Frustratingly, Next.js does not conform to the stale-while-revalidate specification defined in RFC 5861. By default Next.js returns headers like s-maxage=60, stale-while-revalidate which does not work anywhere except on Vercel. The latter part must have a time defined like stale-while-revalidate=31536000 to work correctly.

If you do manual revalidation with the Next.js revalidate() API, you’ll also need to invalidate that path on your CDN.

Draft or Preview mode

Next.js draft and preview mode is a feature for bypassing the CDN and Next.js cache to force dynamic rendering and see changes before they are published. It’s often used in conjunction with content management systems.

It works by setting the __prerender_bypass=SECRETVALUE cookie in the user’s browser.

If you use this feature, you need to:

Middleware

Next.js middleware fully works out of the box with servers. The middleware just runs on the server instead of the CDN when using Vercel. 

Redirects & rewrites

Next.js redirects and rewrites fully work out of the box. These are handled by the Next.js server instead of the CDN when using Vercel.

Core web vitals

If you want to continue getting the core web vital metrics that Vercel provides, you can configure Next.js to send those metrics anywhere.

Version skew protection

Next.js has built-in skew mitigation. It will automatically trigger a reload of assets on page navigation when detected.

Digression: server components are frustrating because they make users experience version skew with every single deployment you make because it’s constantly making version-specific requests to the server. This does not happen nearly as often with Pages Router because most of the static assets are loaded once and don’t need to be refetched.

Other production tips

Minimum 2 instances

For production servers, you want a minimum of 2 instances running behind a load balancer so that if one crashes, there will still be a server serving traffic while the crashed one is automatically rebooted.

It’s better to have 2 smaller instances than 1 large one.

CPU & Memory consumption

For production, you likely want 1-2 CPU and 2-4 GB of memory. This range is a good starting point, and then you can adjust based on your specific compute usage.

Reduce deployment size

Next.js has a standalone output mode that will trace dependencies and generate a standalone output folder that contains only required files instead of all of node_modules.

I don’t have experience with it, and I haven’t seen many others talking about it. So I don’t know how well it works. If you’ve used it, let me know!

Join the conversation

Was this useful? Have a suggestion? Join the conversation on Twitter LinkedIn, or Reddit.

Deploy apps 2-6x faster, 50-75% cheaper & near perfect reliability

Learn more
App screenshotApp screenshot