Concept
The beginner framing: a Next.js app can be deployed a handful of different ways, as a Node.js server, in a Docker container, as a fully static export, or via a platform-specific adapter, and the right choice depends on which framework features the app actually needs at runtime.
The precise mental model: three of these options (Node.js server, Docker, Adapters) support the full feature set, Route Handlers, Server Actions, "use cache", dynamic rendering, everything covered throughout this domain. Static export is meaningfully different: it produces plain HTML/CSS/JS files with no live server behind them at all, which means anything requiring server-side computation at request time simply isn't available.
| Deployment option | Feature support |
|---|---|
| Node.js server | All |
| Docker container | All |
| Static export | Limited |
| Adapters | Varies (verified adapters run the full compatibility suite) |
Node.js server: the baseline
// package.json
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}next build followed by next start supports every Next.js feature, deployable to any provider that runs Node.js. This is also the option that supports ejecting to a fully custom server, if genuinely needed.
Docker: two very different output modes
output: "standalone" → a minimal, production-ready image with only the required runtime files
output: "export" → a fully STATIC export, servable from any static host or lightweight containerBoth are legitimate Docker deployment patterns, but they inherit the same distinction as the table above, standalone gets full feature support; export inherits static export's limitations.
Static export: the one genuinely limited option
A static export produces HTML/CSS/JS that can be hosted on any static file server (S3, Nginx, GitHub Pages), no Node.js runtime required at all. The tradeoff: anything that needs a live server doesn't work. This directly excludes, or meaningfully restricts, most of what this domain has covered: dynamic Route Handlers, Server Actions, "use cache" (explicitly unsupported per its own platform-support table, see Caching), on-demand revalidation, and reading cookies()/headers() at request time.
Adapters: platform-specific builds
Verified adapters (run the FULL compatibility test suite): Vercel, Bun
Other platforms with their OWN (non-adapter) integrations: Cloudflare, NetlifyThe Deployment Adapter API lets a hosting platform customize exactly how a Next.js app is built and deployed for its own infrastructure. "Verified" specifically means the Next.js team coordinates testing against that adapter using the official compatibility suite before major releases, a meaningfully stronger guarantee than a platform's own, independently-built integration.
Try It
Predict what happens before checking the solution.
// app/actions.ts
"use server";
export async function createPost(formData: FormData) {
await db.post.create({ data: { title: formData.get("title") } });
}This app is deployed via static export (output: "export"). Does the createPost Server Action work in production?
Solution
No, it doesn't work at all in a static export. Server Actions require a live server to actually receive and process the POST request they generate; a static export has no server behind it whatsoever, only pre-built static files. Anything requiring server-side computation at request time, Server Actions, dynamic Route Handlers, "use cache", cookie-based session checks, needs one of the other deployment options (Node.js server, Docker with standalone output, or a verified adapter).
Implement It Yourself
Model the feature-support decision a deployment target choice actually makes:
function checkFeatureSupport(deploymentTarget, feature) {
const staticExportUnsupported = new Set([
"server-actions",
"dynamic-route-handlers",
"use-cache",
"on-demand-revalidation",
"request-time-cookies-headers",
]);
if (deploymentTarget === "static-export" && staticExportUnsupported.has(feature)) {
return { supported: false, reason: "requires a live server at request time, static export has none" };
}
return { supported: true };
}
checkFeatureSupport("static-export", "server-actions");
This is the essential decision: choosing static export isn't a purely cosmetic hosting choice, it structurally removes access to everything in this domain that assumes a live server exists to handle a request.
Under the Hood
Static export's limitations follow directly from Rendering: SSR/SSG/ISR/CSR/Streaming, a static export is, definitionally, everything prerendered at build time with nothing left for a server to compute later; anything requiring request-time server computation (SSR, Server Actions, dynamic Route Handlers) has no runtime left to execute in once deployed this way. And "use cache"'s exclusion from static export specifically follows from Caching's own platform-support table, the caching system assumes a server process exists to maintain its in-memory (or remote) cache state, which a purely static deployment doesn't have.
Common Mistakes
1. Choosing static export, then reaching for Server Actions or dynamic Route Handlers anyway
Covered in Try It, these require a live server and simply don't function in a static export, regardless of how the code is written.
2. Assuming "verified adapter" and "platform has a Next.js integration" mean the same level of guarantee
A verified adapter (currently Vercel, Bun) runs the Next.js team's own compatibility test suite, coordinated ahead of major releases. A platform's own, independently-built integration (Cloudflare, Netlify, as of this writing) may have different feature coverage or compatibility characteristics not verified against that same suite.
3. Using Docker's output: "export" while expecting full feature support
This Docker output mode is still a static export under the hood, it inherits every one of static export's limitations, just packaged in a container instead of a plain file host.
Best Practices
- Choose static export only when the app genuinely needs no server-side computation at request time, a purely marketing/content site with no forms, no personalization, no dynamic data.
- Default to a Node.js server or Docker's
standaloneoutput for anything using Server Actions, dynamic Route Handlers,"use cache", or cookie-based auth. - Check whether your target platform is a verified adapter before assuming full, tested compatibility, an unverified integration may have gaps.
- Match the deployment choice to the feature set the app actually uses, not the other way around, deciding on static export first and then discovering a needed feature doesn't work is a costly, late-stage mistake to make.
Performance Tips
- Static export's HTML/CSS/JS can be served directly from a CDN with essentially the lowest possible latency for pure content, no server process to even spin up.
- Docker's
standaloneoutput produces a minimal image containing only the runtime files actually needed, meaningfully faster to build, ship, and cold-start than a full, unprunednode_modulesimage.
