How to host a Next.js static export

By PreviewSend Team · Aug 3, 2026 · 8 min read

To host a Next.js static export, set output: 'export' in next.config.js, run next build so Next.js writes finished HTML to an out folder, then zip the contents of out and upload that archive to a static file host. The export only covers routes that can be rendered ahead of time, so check images and dynamic routes before you package it.

The name of the framework does not tell you whether the project can export. Next.js can run a server for every request, but static export is the mode where it stops doing so and writes plain files instead. Before packaging anything, check the config, dynamic routes, and image handling.

Turn on static export and build

Next.js defaults to a server-backed build. You opt into a file-only build by setting the output mode. In next.config.js:

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export',
}

module.exports = nextConfig

With that set, Next.js's static-export guide says next build produces an out folder containing the HTML, CSS, and JavaScript for your app instead of starting a server. Run the build with whatever your lockfile points to:

npm run build       # writes ./out when output: 'export' is set
npx serve out       # serve the exported files locally to check them

The out folder is the thing you ship. Open it and you'll see an index.html, a hashed _next/ asset directory, and a copy of anything from your public/ folder. A static host serves those files; it does not need your source or the server-oriented .next output.

Serve out locally with a plain static server (npx serve out or similar) and click through the routes. Reload at least one nested URL directly rather than reaching every page from the homepage. That catches a missing route file before you upload.

What can and can't be in a Next.js static export

Export only captures work Next.js can do at build time. Anything that has to run when a visitor arrives is excluded. Match each feature your app uses against this table:

What your Next.js app usesSurvives static export?WhyNext step
Pages rendered at build timeYesThe HTML is written into out during the buildZip out and upload
Client components and browser interactivityYesThe JavaScript bundle runs on the visitor's machineShip the exported bundle as-is
Dynamic routes with generateStaticParams and dynamicParams = falseYesEvery allowed path is enumerated and renderedConfirm the params list is complete
next/image with images.unoptimized or a loaderYes, with configThe optimizer is bypassed or moved off the hostSet unoptimized: true or a custom loader
GET Route Handlers that do not read the incoming RequestYesTheir response is written to a file during the buildShip the generated file
Route Handlers that read Request or use a verb other than GETNoThey need request-time server codeRuntime host
Server ActionsNoThey execute on a server when the form is submittedRuntime host
redirects, rewrites, and headers in the configNoThese are resolved by a server, not baked into filesHandle at the host, or a runtime
Proxy or Intercepting RoutesNoNext.js lists both as unsupported for static exportRuntime host or redesign
Incremental Static Regeneration and draft modeNoBoth revalidate or render per requestRuntime host

Use this table as a first-pass qualification, not an exhaustive compatibility guarantee. A single "No" means that feature needs a runtime host, even if the rest of the app exports cleanly. Next.js's unsupported-features list and a successful next build are the final checks. The framework-agnostic static-versus-dynamic guide applies the same "where does the work happen" test across Next.js, Vite, and AI-builder exports if you want the reasoning behind the table.

Dynamic routes need generateStaticParams

A route can work in next dev and still be absent from out. In the App Router, a dynamic segment like app/blog/[slug]/page.tsx doesn't know which slugs exist until you tell it. Next.js's generateStaticParams reference uses that function to enumerate the paths to pre-render at build time. Static export also requires dynamicParams = false; the default true is unsupported because there is no runtime to render a path omitted from the build:

export const dynamicParams = false

export function generateStaticParams() {
  return [{ slug: 'first-post' }, { slug: 'second-post' }]
}

With dynamicParams = false, Next.js writes one HTML file per object returned and rejects paths outside that list. A slug you forgot to include gets no exported file, so a static host returns 404 for it. If a route's paths depend on request data and can't be listed at build time, it is not a static route. It belongs in the runtime column of the table above.

next/image and the default optimizer

next/image normally routes through a server-side optimizer that resizes and reformats on demand. There's no server on a file host, so the next/image reference has you either disable optimization or point at an external service:

const nextConfig = {
  output: 'export',
  images: {
    unoptimized: true,
  },
}

With unoptimized: true, next/image still renders the <img> and honors the width and height you set — it just serves the original file rather than a resized variant. If you need real optimization, configure a custom loader that hands the work to an external image service instead. Either way, the default optimizer can't be part of an export.

trailingSlash and serving the routes directly

By default an export writes about.html for the /about route, and hosts differ on whether a request for /about maps to that file without extra rules. Setting trailingSlash to true changes the export to write about/index.html instead, which directory-style hosts resolve cleanly:

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

This is the more portable choice across static hosts, and it's worth setting before you rely on multi-page routing.

PreviewSend's current serving layer tries <path>/index.html for an extensionless URL before it falls back to the root page. An export with trailingSlash: true therefore gives /about a matching about/index.html instead of relying on host-specific .html rewrites.

Zip the contents of out, then upload

Package the export so index.html lands at the top of the archive, not inside an out/ wrapper:

cd out
zip -r ../site.zip .

The trailing . compresses the folder's contents, not the folder itself. Without a terminal, open out, select everything inside, and compress the selection. Keeping index.html at the archive root is the most portable layout across static hosts, the same rule the ZIP hosting guide walks through when an upload reports "index.html not found."

Static hosts that accept prebuilt files directly, without a repository or CI pipeline, include Cloudflare Pages Direct Upload and Netlify Drop; their access controls and limits vary. For a client-facing draft that needs a stable review URL, start a PreviewSend workspace and upload the archive (up to 100 MiB compressed and 256 ZIP entries). It validates the ZIP, extracts the files, serves your site from the domain root so a default _next/ asset layout resolves, and returns a stable URL. It does not execute Next.js server code, so any route from the runtime column above still won't run there.

Because the URL is stable, updating the site means rebuilding, re-zipping out, and uploading again. The new build becomes the active version at the same address, so everyone holding the link sees the current site. New projects also turn on account-free review by default: a reviewer types a name and can pin comments or request changes on the page without a PreviewSend account, and each note stays tied to the version they saw. If the draft needs to stay private, add a project password, which is checked server-side before any file is served. Served preview files, robots.txt, and password challenges carry an X-Robots-Tag: noindex, nofollow header, but that's a request to search engines rather than access control, so treat the password as the real gate.

When export is the wrong tool

If the table above turned up a request-dependent or non-GET Route Handler, a Server Action, Proxy, Intercepting Route, ISR, or a redirect your app relies on at request time, stop before forcing an export. Those features exist because part of your app runs code for each visitor, and a static file host cannot run it. That includes PreviewSend. Deploy through a platform that runs the Next.js server, or split the app: export the static front end as files and host the request-time pieces separately behind an API.

Run next build with output: 'export' and inspect out. If every route you need exists there and the local static preview works after a direct reload, zip it. If the build still depends on request-time code, use a host that can run the Next.js server.

FAQ

Where does a Next.js static export write its files?

Running the build with output set to export writes the deployable static site to an out folder by default, or to the directory named by distDir. Next.js may still use .next for build artifacts; the export directory holds the HTML, the _next asset directory, and your public files, and it is the folder you zip and upload.

Why is a dynamic route missing from the out folder?

For an App Router dynamic segment such as [slug], static export requires generateStaticParams to list the paths and dynamicParams set to false to reject paths outside that list. A missing slug gets no exported file and returns 404. If the route cannot list its paths ahead of time because they depend on request data, it is not exportable and needs a runtime.

Does next/image work in a static export?

The default image optimizer runs on a server, so it cannot run on a file host. A static export needs images.unoptimized set to true, or a custom loader pointing at an external optimization service. With unoptimized on, next/image still renders the img and applies width and height, it just serves the original file without server-side resizing.

Do I need trailingSlash for a Next.js static export?

It is the most portable choice. With trailingSlash on, the export writes about/index.html rather than about.html, and directory-style hosts resolve /about to that folder index cleanly. Without it, some hosts need extra rewrite rules to serve the extensionless route, which is easy to get wrong.

Have a site to host? Upload it and get a live link in seconds.

Get it free

← Back to the blog