MENU
Static Export
Setting output: 'export' in next.config.js tells next build to produce a plain folder of static files -- HTML, CSS, JavaScript, and other assets -- instead of the hybrid server output used by self-hosting or Vercel. There is no Node.js server involved at runtime at all: the output can be served by literally any static file host, exactly like a folder of hand-written HTML files.
next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
}
module.exports = nextConfigterminal:
next build
# static site is written to ./outWhat Becomes Unavailable
Every feature that depends on code running on a live server at request time is off the table, because there is no server to run it. Attempting to use most of these while output: 'export' is set causes next build to fail with an explanatory error rather than silently producing a broken build.
- Server Actions -- there is no server process left to receive the request an action would normally trigger.
- Dynamic Route Handlers -- a route.js file can only export a static GET handler that returns the same response for every request (useful for generating a file like robots.txt at build time); anything that reads the incoming request, cookies, or headers is not supported.
- The Image Optimization API -- the on-demand /_next/image resizing endpoint requires a server. The <Image> component still works, but you must either set images.unoptimized: true and serve original file sizes, or point images.loader at a third-party image CDN that does the resizing itself.
- Middleware -- there is no request-interception point before a static file is served.
- ISR / on-demand revalidation -- pages are rendered exactly once, at build time. There is no revalidate window and no way to regenerate a single page without rebuilding the whole site; see Full Route Cache for how that caching model normally works with a live server.
Dynamic routes still work, but every possible value has to be known and enumerated at build time via generateStaticParams, since there is no server left to render an unlisted value on the fly.
app,blog,[slug],page.tsx:
export function generateStaticParams() {
return [{ slug: 'first-post' }, { slug: 'second-post' }, { slug: 'third-post' }]
}
export default function Page({ params }: { params: { slug: string } }) {
return <article>{params.slug}</article>
}When It Makes Sense
Static export is a good fit whenever the entire site can be fully determined at build time: marketing pages, documentation, blogs backed by markdown or a headless CMS fetched during the build, and internal tools where a rebuild-and-redeploy cycle for content changes is perfectly acceptable. It's a poor fit for anything that needs personalized responses, real-time data, or server-side auth checks on every request.
Because the output is just static files, it can be deployed to hosts that have no concept of a Node.js runtime at all: GitHub Pages, Amazon S3 (optionally behind CloudFront), or any plain static hosting bucket. It's also the simplest possible deployment target among the other platforms covered elsewhere in this chapter -- there's no adapter to install because there's no server-side behavior for an adapter to reproduce.
Configuring It
Two settings come up often enough to be worth calling out directly. basePath is required when the site is served from a subpath rather than the domain root -- for example, a GitHub Pages project site served at username.github.io/my-docs-site rather than a custom domain. images.unoptimized avoids the build-time error that would otherwise occur from using the default image loader without a server behind it.
next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
basePath: '/my-docs-site',
images: {
unoptimized: true,
},
}
module.exports = nextConfig