Adapters and other platforms
See what the Build Adapters API hands a platform after next build, and how Vercel, Netlify, Cloudflare and AWS run Next.js 16, checked against their docs.
- 01Read an adapterName the adapter's two hooks, when Next.js calls them, and what adapterPath and NEXT_ADAPTER_PATH do.
- 02Read what it receivesTell the build's outputs apart: functions, prerenders with their revalidate and postponed state, static files, proxy.ts and the routing rules.
- 03Choose a platform honestlyCompare how Vercel, Netlify, Cloudflare and AWS run Next.js 16 today, and check a platform against the features your app uses.
One build, handed to a platform
Deploying Next.js named the fourth way to run an app: a platform, a hosting company that runs it on its own computers. A platform usually splits the app: code that runs when a request needs it, files on its CDN, and rules that send each request to the right one. The code that does that split is the platform's adapter, and Next.js has a public API for writing one.
Here is the tea shop with a tiny adapter of its own, my-adapter.js. It doesn't deploy anything: it prints what Next.js hands it. This example is a real Next.js 16.4.3 project, built with next build and started with next start. The adapter's file is open; click the others in the tree.
my-adapter.js/** @type {import("next").NextAdapter} */const adapter = { name: "tea-shop-adapter", modifyConfig(config, { phase }) { console.log(`tea-shop-adapter: modifyConfig in ${phase}`); return config; }, onBuildComplete({ outputs, routing }) { const paths = (list) => list.map((output) => output.pathname).filter((path) => !path.endsWith(".rsc")).sort(); const proxy = outputs.middleware; const pages = outputs.prerenders.filter((output) => !/\.rsc$|\.segments\//.test(output.pathname)); const htmlFiles = outputs.staticFiles.filter((file) => file.filePath.endsWith(".html")); console.log("tea-shop-adapter: onBuildComplete"); console.log(" appPages:", paths(outputs.appPages).join(", ") || "none"); console.log(" appRoutes:", paths(outputs.appRoutes).join(", ") || "none"); console.log(" middleware:", proxy ? `${proxy.runtime}, for ${proxy.config.matchers.map((m) => m.source)}` : "none"); for (const page of pages) { const file = !page.fallback?.filePath ? "no file" : page.fallback.postponedState ? "shell + postponed state" : "whole file"; console.log(` prerender ${page.pathname}: ${file}, revalidate ${page.fallback?.initialRevalidate ?? "none"}`); } console.log(" staticFiles:", outputs.staticFiles.length, "files, HTML:", paths(htmlFiles).join(", ") || "none"); console.log(" dynamicRoutes:", routing.dynamicRoutes.map((route) => route.source).join(", ") || "none"); },}; module.exports = adapter;$ next build▲ Next.js 16.4.3 (Turbopack) Applying modifyConfig from tea-shop-adaptertea-shop-adapter: modifyConfig in phase-production-build- Cache Components enabled- Partial Prefetching enabled Creating an optimized production build ...✓ Compiled successfully Running TypeScript ... Finished TypeScript ... Collecting page data ...✓ Generating static pages (9/9) Finalizing page optimization ... Running onBuildComplete from tea-shop-adaptertea-shop-adapter: onBuildComplete appPages: /, /_global-error, /_not-found, /cart, /menu, /teas/[slug] appRoutes: /api/prices middleware: nodejs, for /teas prerender /: whole file, revalidate false prerender /_global-error: whole file, revalidate false prerender /api/prices: whole file, revalidate false prerender /cart: shell + postponed state, revalidate false prerender /menu: whole file, revalidate 3600 prerender /teas/green: whole file, revalidate false prerender /teas/mint: whole file, revalidate false prerender /teas/[slug]: no file, revalidate none staticFiles: 13 files, HTML: /404, /500 dynamicRoutes: /teas/[slug] Route (app) Revalidate Expire┌ ○ /├ ○ /_not-found├ ○ /api/prices├ ◐ /cart├ ○ /menu 1h 1d└ /teas/[slug] ├ ◐ /teas/[slug] ├ ○ /teas/mint └ ○ /teas/green ƒ Proxy (Middleware) ○ (Static) prerendered as static content◐ (Partial Prerender) prerendered as static HTML with dynamic server-streamed content $ next start▲ Next.js 16.4.3- Local: http://127.0.0.1:3000- Network: http://127.0.0.1:3000✓ Ready Applying modifyConfig from tea-shop-adaptertea-shop-adapter: modifyConfig in phase-production-serverOpen the Terminal tab and read it from the top. Next.js prints Applying modifyConfig from tea-shop-adapter before it builds anything, and the adapter answers with modifyConfig in phase-production-build. Near the end, Running onBuildComplete from tea-shop-adapter, and the adapter lists what it received, just before the route table. Under $ next start, the adapter speaks once more: modifyConfig in phase-production-server.
modifyConfig runs whenever a command loads next.config, and learns which phase it's in. onBuildComplete runs once, at the end of next build, with everything the build made. That's where a platform's adapter writes what its computers run.A tea shop packs its goods once and writes one detailed packing list: what's fresh, what keeps, what must be brewed when it arrives. Every delivery company gets the same list. Each one loads its own vans its own way, and some can't carry everything on the list.
- In real life: The shop packs its goods once
- In Next.js:
next build - In real life: The detailed packing list
- In Next.js:
outputsandrouting, handed toonBuildComplete - In real life: A delivery company's loader
- In Next.js: A platform's adapter
- In real life: Fridge vans, shelf vans, a van with a kitchen
- In Next.js: CDN files, functions, edge functions
- In real life: What a company can carry
- In Next.js: Which Next.js features a platform supports
An adapter is a platform's code inside your build. It may change the config whenever a Next.js command loads it, and it receives everything next build made when the build ends. It turns that into the platform's own functions, files and routing rules. What works on a platform depends on what its adapter, and its computers, do with each part.
The Build Adapters API: adapterPath and two hooks
Next.js's team proposed the API in April 2025, in an RFC (a public "request for comments") called "Deployment Adapters API", discussion 77740 in Next.js's GitHub repository. It shipped as experimental.adapterPath, and Next.js 16.2.0 made it stable as the top-level adapterPath, which 16.4.3 uses. This is the shop's config:
import type { NextConfig } from "next"; const nextConfig: NextConfig = { cacheComponents: true, partialPrefetching: true, adapterPath: require.resolve("./my-adapter.js"),}; export default nextConfig; Lines 4 and 5 are what create-next-app writes. Line 6 names the adapter's file: require.resolve turns ./my-adapter.js into the full path Next.js loads it from. A platform doesn't ask you to edit your config. It sets an environment variable in its build instead:
NEXT_ADAPTER_PATH=/opt/platform/adapter.js npm run build
With NEXT_ADAPTER_PATH set, next build loads that adapter as if line 6 named it. Now the adapter itself, open in the example above. Its lines, in order:
- Line 1 gives the object the
NextAdaptertype thatnextexports, so an editor knows its shape. - Line 3 is its
name, which Next.js prints:Running onBuildComplete from tea-shop-adapter. - Lines 5 to 8 are
modifyConfig. Next.js calls it with the whole config and the phase, a name for what's running (phase-production-buildfornext build), and uses the config it returns. This one only prints the phase and changes nothing. A real adapter changes what its platform needs: Vercel's, for example, sets adeploymentIdwhen Vercel's skew protection is on. - Lines 10 to 25 are
onBuildComplete, called once, after the build, withoutputsandrouting, among other things: the project's folder, the final config, the Next.js version and the build's id. - Line 11 lists the outputs' addresses, leaving out those ending in
.rsc: each page also has an output for its RSC payload, the address the router asks for. Line 13 keeps the prerenders of whole pages, without their payload pieces. - Lines 15 to 24 print the parts, and line 28 exports the adapter, the way the docs' example does.
Next.js has a compatibility test suite for adapters, which the docs' Testing Adapters page explains, and the Next.js team coordinates testing with the verified adapters' platforms before major releases. The types live in Next.js's build/adapter/build-complete.ts.
What onBuildComplete receives
The interesting part is outputs. Each button below builds a version of the shop with the same adapter. Before you click, guess which lines of the printout will change.
my-adapter.js/** @type {import("next").NextAdapter} */const adapter = { name: "tea-shop-adapter", modifyConfig(config, { phase }) { console.log(`tea-shop-adapter: modifyConfig in ${phase}`); return config; }, onBuildComplete({ outputs, routing }) { const paths = (list) => list.map((output) => output.pathname).filter((path) => !path.endsWith(".rsc")).sort(); const proxy = outputs.middleware; const pages = outputs.prerenders.filter((output) => !/\.rsc$|\.segments\//.test(output.pathname)); const htmlFiles = outputs.staticFiles.filter((file) => file.filePath.endsWith(".html")); console.log("tea-shop-adapter: onBuildComplete"); console.log(" appPages:", paths(outputs.appPages).join(", ") || "none"); console.log(" appRoutes:", paths(outputs.appRoutes).join(", ") || "none"); console.log(" middleware:", proxy ? `${proxy.runtime}, for ${proxy.config.matchers.map((m) => m.source)}` : "none"); for (const page of pages) { const file = !page.fallback?.filePath ? "no file" : page.fallback.postponedState ? "shell + postponed state" : "whole file"; console.log(` prerender ${page.pathname}: ${file}, revalidate ${page.fallback?.initialRevalidate ?? "none"}`); } console.log(" staticFiles:", outputs.staticFiles.length, "files, HTML:", paths(htmlFiles).join(", ") || "none"); console.log(" dynamicRoutes:", routing.dynamicRoutes.map((route) => route.source).join(", ") || "none"); },}; module.exports = adapter;$ next build▲ Next.js 16.4.3 (Turbopack) Applying modifyConfig from tea-shop-adaptertea-shop-adapter: modifyConfig in phase-production-build- Cache Components enabled- Partial Prefetching enabled Creating an optimized production build ...✓ Compiled successfully Running TypeScript ... Finished TypeScript ... Collecting page data ...✓ Generating static pages (9/9) Finalizing page optimization ... Running onBuildComplete from tea-shop-adaptertea-shop-adapter: onBuildComplete appPages: /, /_global-error, /_not-found, /cart, /menu, /teas/[slug] appRoutes: /api/prices middleware: nodejs, for /teas prerender /: whole file, revalidate false prerender /_global-error: whole file, revalidate false prerender /api/prices: whole file, revalidate false prerender /cart: shell + postponed state, revalidate false prerender /menu: whole file, revalidate 3600 prerender /teas/green: whole file, revalidate false prerender /teas/mint: whole file, revalidate false prerender /teas/[slug]: no file, revalidate none staticFiles: 13 files, HTML: /404, /500 dynamicRoutes: /teas/[slug] Route (app) Revalidate Expire┌ ○ /├ ○ /_not-found├ ○ /api/prices├ ◐ /cart├ ○ /menu 1h 1d└ /teas/[slug] ├ ◐ /teas/[slug] ├ ○ /teas/mint └ ○ /teas/green ƒ Proxy (Middleware) ○ (Static) prerendered as static content◐ (Partial Prerender) prerendered as static HTML with dynamic server-streamed content $ next start▲ Next.js 16.4.3- Local: http://127.0.0.1:3000- Network: http://127.0.0.1:3000✓ Ready Applying modifyConfig from tea-shop-adaptertea-shop-adapter: modifyConfig in phase-production-serverEvery kind of output: pages and a Route Handler to run, proxy.ts on Node.js, prerenders (the cart as a shell with postponed state), static files and a dynamic route.
Each row is one part of what the adapter received, and what the whole shop's recording printed for it:
| Part | What it holds | In the shop's recording |
|---|---|---|
appPages | The server code for each page, which renders it when a request needs it. | /, /cart, /menu, /teas/[slug], and Next.js's own /_not-found and /_global-error. |
appRoutes | The code for each Route Handler. | /api/prices. |
middleware | proxy.ts, with its runtime and the paths it runs for. | nodejs, for /teas. |
prerenders | What the build made ahead of time, and when it may be made again. | /menu with revalidate 3600; /cart as a shell with postponed state. |
staticFiles | Files that can be sent as they are, by any CDN. | 13 files: the build's JavaScript and CSS, and /404 and /500 as HTML. |
routing | The rules that send each request to the right output: redirects, rewrites, dynamic routes, headers. | dynamicRoutes: /teas/[slug]. |
appPages, appRoutes, middleware), pages made ahead of time (prerenders), and files to send as they are (staticFiles). Each platform decides where each group goes.Three details in the printout matter for a platform:
- Revalidate.
/menucame withrevalidate 3600: itscacheLife("hours")lets it be made again after an hour (Incremental Static Regeneration). The other pages sayfalse: never. A platform needs this number to know when its copy is old. - Postponed state.
/cartreads a cookie insideSuspense, so its prerender is a shell plus postponed state: what React saved to finish the page later (Partial Prerendering). A platform can send the shell from its CDN at once and ask its function for the rest. Deploying Next.js called that resuming. - Runtime.
proxy.tsarrived asmiddleware: nodejs. Since Next.js 16, it always runs on Node.js (Proxy), so a platform must run Node.js code before its routes, not only edge code.
The static export got none of the code: appPages: none, and 32 static files, the HTML pages among them (Static exports). Notice /index in that list: the export's index.html, named by its file. An adapter that serves it has to know that /index means /.
Vercel: an adapter on the public API
Vercel maintains Next.js, and its adapter uses the same public API as every other adapter: the Next.js docs say there are no private hooks. Its adapter is open source, in the nextjs/adapter-vercel repository, and published as @next-community/adapter-vercel. On October 30, 2026, npm had it only under the beta tag, at 0.0.1-beta.35. You don't install it: Vercel sets it for you when it builds the app.
Its onBuildComplete writes the folder Vercel deploys from, which Vercel calls its Build Output API:
static/: the static files and the prerendered pages, for Vercel's CDN.functions/: one.funcfolder for each page or Route Handler that needs code, run as Vercel Functions.config.json: the routing rules, made fromroutingand the outputs.
next build, so it isn't here.Vercel's Next.js page describes how the features land there. A ◐ route's static shell is served from the CDN, while a Vercel Function renders the rest and streams it in the same response. proxy.ts becomes Vercel's Routing Middleware, deployed to every region. Images are resized by Vercel's image service. Next.js's Deploying page lists Vercel's adapter as verified, beside Bun's, which builds a folder that runs the app with the Bun runtime. Verified means public code and Next.js's compatibility tests; the page says public test results for each adapter are coming soon.
Netlify and Cloudflare
The Next.js docs list Netlify and Cloudflare under "other platforms": their integrations aren't built on the public API yet, and both are working on verified adapters that are. Everything below is from their own docs and changelogs, as of October 30, 2026.
Netlify adds its adapter to every Next.js build: @netlify/plugin-nextjs, at 5.16.5. It's part of OpenNext, an open-source project that turns next build's output into packages for other platforms. Netlify's Next.js page says the adapter:
- puts the pages, Route Handlers and Server Actions in one Netlify Function;
- puts
proxy.tsin a Netlify Edge Function; - keeps prerendered pages in Netlify's cache, revalidated by path or by tag;
- sends
next/imagethrough Netlify Image CDN.
The feature table on that page marks Cache Components, streaming and after() as fully supported. Its list of limits names a few: a Node.js proxy.ts can't use the file system or C++ addons, and headers and redirects from the config run after proxy.ts, not before as in next start.
Cloudflare runs code in Workers: small programs that start on its servers worldwide. Its Next.js guide gives two ways:
- vinext (1.4.0), the one the guide recommends. It's a Vite plugin that reimplements Next.js's API: your
appfolder stays, but Next.js doesn't build it. The guide calls it beta and points to its compatibility list. - The OpenNext adapter,
@opennextjs/cloudflare(1.20.14), for apps that can't move yet (its features page). It builds with Next.js, then reshapes the output into one Worker, with the files in Workers Static Assets. Revalidation needs Cloudflare storage: a cache store (R2, the docs advise), a queue, and a tag cache.
One detail shows why you check dates. OpenNext's changelog added proxy.ts support in 1.20.3 (August 26, 2026), marked experimental and needing the nodejs_compat flag, while its features page still listed Node.js middleware as not supported on October 30, 2026. A Worker also has a size limit: 3 MiB compressed on the free plan, 10 MiB on the paid one.
AWS: OpenNext and Amplify
AWS has no Next.js adapter of its own on the public API. There are two common ways onto it:
- OpenNext for AWS,
@opennextjs/aws(4.1.13), kept up by people from the SST community. It builds the app instandalonemode (Self-hosting) and wraps the server in an AWS Lambda function (its architecture page). It also makes a function for image optimization, one for revalidation with a queue in front, and a folder of assets,.open-next/assets, for a CDN. It doesn't create anything in your AWS account: SST, AWS CDK or Terraform does that. Its npm package accepts Next.js 16.4.1 and later in the 16 line, and its changelog lists each release's Next.js 16 fixes. - AWS Amplify Hosting, AWS's managed service. Its "Amplify support for Next.js" page, on October 30, 2026, supports Next.js up through 15, and lists Next.js streaming, on-demand ISR and
after()as not supported. For a Next.js 16 app with Cache Components, that page doesn't say yes.
Choosing a platform
No platform is right for every app. Start from what your app does, then read each platform's own page. Here are the four, side by side, as their docs and changelogs described them on October 30, 2026:
| Platform | How Next.js plugs in | Where server code runs | Worth knowing |
|---|---|---|---|
| Vercel | Its adapter, @next-community/adapter-vercel, built on the public API and set for you when you deploy. | Vercel Functions; files and prerendered pages on its CDN. | A verified adapter. It writes Vercel's deploy folder: static files, one .func folder per function, and a config.json of routes. |
| Netlify | Its OpenNext adapter, @netlify/plugin-nextjs, added to every Next.js build. Not built on the public API yet. | One Netlify Function for pages, Route Handlers and Server Actions; proxy.ts in a Netlify Edge Function. | Its table marks Cache Components, streaming and after() as fully supported. A Node.js proxy.ts can't use the file system or C++ addons. |
| Cloudflare | vinext first: a Vite plugin that reimplements Next.js's API, in beta. Or the OpenNext adapter, @opennextjs/cloudflare. | A Cloudflare Worker; files from Workers Static Assets. | With OpenNext, proxy.ts support is experimental (since 1.20.3) and needs the nodejs_compat flag. Revalidation needs a cache store (R2, the docs advise), a queue and a tag cache. |
| AWS | OpenNext, @opennextjs/aws, with SST, AWS CDK or Terraform to create the resources. Or AWS Amplify Hosting. | Lambda functions: the server, image optimization, revalidation. Files from .open-next/assets go to a CDN in the recommended setup. | Amplify's own page says Next.js up through 15, without streaming, on-demand ISR or after(). |
And a short guide. Each row is a situation, and a way that fits it:
| If | Consider | Why |
|---|---|---|
| Nothing in the app needs a request. | A static export, on any static host. | No servers, no functions: only files. Static exports covers what to check. |
| You need every Next.js feature, with nothing left out. | Self-hosting with next start or Docker, or Vercel where its documented support covers what you use. | The docs say a Node.js server and Docker support every feature. A verified adapter has public code and runs the compatibility tests, but support still varies: check what it covers. |
| You want nobody to run servers. | A platform, ideally one with a verified adapter. | Check its feature table against your app, and test a preview there: verification isn't a promise of every feature. |
| Your company already uses Netlify, Cloudflare or AWS. | That platform's own Next.js integration. | Read its feature table against what your app uses: proxy.ts, Cache Components, after(), image optimization. |
| You already run servers or Kubernetes. | Self-hosting: next start or the standalone server. | Every feature works. You run the shared cache, the scaling and the shutdowns. |
Then check the platform against your own build, in four steps:
- Read its Next.js page: which Next.js versions it supports, and its feature table.
- Read your route table: a
◐route needs streaming,ƒ Proxy (Middleware)needs Node.js code before the routes, and anafter()needs time after the answer. - Deploy a preview of your own app and test the pages that use those features.
- Prefer a verified adapter when you can: its code is public, and it runs Next.js's compatibility tests. Still check what it supports: verification isn't a promise of every feature.
To practise reading the outputs, sort what the shop's adapter received:
- The code that renders
/cartfor a request. app/api/prices/route.ts./menu, with revalidate 3600./cart's shell and its postponed state.- The JavaScript files in
_next/static. - A static export's
teas/mint.html. proxy.ts, for/teas.- The pattern that matches
/teas/oolongto/teas/[slug].
Sort each piece of the tea shop's build into the part of onBuildComplete's outputs it arrives in.
Common mix-ups
- "Vercel runs Next.js through private hooks." No. The docs say its adapter uses the same public API as every other, and its code is open source.
- "An adapter runs on every request." No. It runs while Next.js commands run:
modifyConfigwhen the config loads,onBuildCompleteafter the build. What answers requests is what it built. - "Every platform supports every feature the same way." No. Amplify's page stops at Next.js 15, OpenNext's
proxy.tssupport on Cloudflare is experimental, and Netlify lists its own limits. - "vinext is Next.js on Cloudflare." Not quite. It reimplements Next.js's API on Vite; your app isn't built by Next.js there, so check its compatibility list.
- "A static export needs an adapter." No. It's only files, which any host sends. An adapter on an export only receives static files.
- "Platform claims stay true." No. They change with every release. Check the date on any comparison, this one included.
Practice exercises
These exercises are about Next.js projects, which can't run inside this page. Answer first, then use the hints.
Which line of this next.config.ts names the adapter?
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
partialPrefetching: true,
adapterPath: require.resolve("./my-adapter.js"),
};
export default nextConfig;
Line 6: adapterPath: require.resolve("./my-adapter.js"). A platform can set NEXT_ADAPTER_PATH instead.
After the build, the shop runs next start. Which of this adapter's hooks prints something then?
/** @type {import("next").NextAdapter} */
const adapter = {
name: "tea-shop-adapter",
modifyConfig(config, { phase }) {
console.log(`tea-shop-adapter: modifyConfig in ${phase}`);
return config;
},
onBuildComplete({ outputs, routing }) {
const paths = (list) => list.map((output) => output.pathname).filter((path) => !path.endsWith(".rsc")).sort();
const proxy = outputs.middleware;
const pages = outputs.prerenders.filter((output) => !/\.rsc$|\.segments\//.test(output.pathname));
const htmlFiles = outputs.staticFiles.filter((file) => file.filePath.endsWith(".html"));
console.log("tea-shop-adapter: onBuildComplete");
console.log(" appPages:", paths(outputs.appPages).join(", ") || "none");
console.log(" appRoutes:", paths(outputs.appRoutes).join(", ") || "none");
console.log(" middleware:", proxy ? `${proxy.runtime}, for ${proxy.config.matchers.map((m) => m.source)}` : "none");
for (const page of pages) {
const file = !page.fallback?.filePath ? "no file" : page.fallback.postponedState ? "shell + postponed state" : "whole file";
console.log(` prerender ${page.pathname}: ${file}, revalidate ${page.fallback?.initialRevalidate ?? "none"}`);
}
console.log(" staticFiles:", outputs.staticFiles.length, "files, HTML:", paths(htmlFiles).join(", ") || "none");
console.log(" dynamicRoutes:", routing.dynamicRoutes.map((route) => route.source).join(", ") || "none");
},
};
module.exports = adapter;
modifyConfig. The recording's next start printed modifyConfig in phase-production-server. onBuildComplete only runs at the end of next build.
With this config, which part of outputs is the only one with anything in it?
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "export",
adapterPath: require.resolve("./my-adapter.js"),
};
export default nextConfig;
staticFiles. The recorded export printed appPages: none, appRoutes: none, middleware: none, no prerenders, and its static files, with the HTML pages among them.
Which of the shop's routes did the adapter receive with postponed state?
import { Suspense } from "react";
import { cookies } from "next/headers";
async function CartItem() {
const tea = (await cookies()).get("cart")?.value;
return <p>{tea ? `In your cart: ${tea}` : "Your cart is empty."}</p>;
}
export default function CartPage() {
return (
<main>
<h1>Your cart</h1>
<Suspense fallback={<p>Loading your cart…</p>}>
<CartItem />
</Suspense>
</main>
);
}
/cart. It reads a cookie inside Suspense, so the build made a shell and postponed state: prerender /cart: shell + postponed state.
The shop moves to Cloudflare with the OpenNext adapter, and keeps this proxy.ts. Which Workers compatibility flag does it need?
import { NextResponse, type NextRequest } from "next/server";
// The shop's old address for its teas still works: it shows the home page.
export function proxy(request: NextRequest) {
return NextResponse.rewrite(new URL("/", request.url));
}
export const config = { matcher: "/teas" };
nodejs_compat. OpenNext for Cloudflare added proxy.ts support in 1.20.3, as experimental, and it needs that compatibility flag.
The shop emails a receipt with after(), once the order page has been sent. Of the two AWS options, which one's docs say after() isn't supported?
AWS Amplify Hosting. Its Next.js page, on the date this comparison was checked, supported Next.js up through 15 and listed after() as not supported. OpenNext for AWS lists support for after.
Check your understanding
For each question, ask when the adapter runs, what it receives, and whose docs say so.
Question 1 of 7In which Next.js version did
adapterPathbecome stable?Choose an answer to see the explanation.
Question 2 of 7When does Next.js call an adapter's
modifyConfig?Choose an answer to see the explanation.
Question 3 of 7The same adapter runs on the shop as a static export. What does
onBuildCompletereceive?Choose an answer to see the explanation.
Question 4 of 7What tells an adapter that
/cartis a static shell with a part made per request?Choose an answer to see the explanation.
Question 5 of 7Which two adapters does the docs' Deploying page list as verified?
Choose an answer to see the explanation.
Question 6 of 7Which way does Cloudflare's own Next.js guide recommend first, as of October 30, 2026?
Choose an answer to see the explanation.
Question 7 of 7How does Netlify run the shop's
proxy.ts?Choose an answer to see the explanation.
Key takeaways
- An adapter is a module named by
adapterPathorNEXT_ADAPTER_PATH, stable since Next.js 16.2.0. modifyConfigruns whenever a command loads the config;onBuildCompleteruns once afternext build, with the outputs and the routing rules.- The outputs are code to run (
appPages,appRoutes,middleware), pages made ahead of time with their revalidate and postponed state (prerenders), and files (staticFiles). An export has only files. - Vercel's adapter is verified and uses the public API. Netlify and Cloudflare (OpenNext, or vinext) and AWS (OpenNext, or Amplify) have their own integrations, with their own limits.
- Choose from your app's needs, check the platform's own dated docs, and test a preview of your app there.
Remember the one-liner.
Next.js builds once and hands the result to an adapter; what works on a platform is what its adapter and its computers do with each part.
These links point at release v16.4.3, the version every example on this page was built with. The NextAdapter interface is in lines 505 to 578 of build-complete.ts, and the export branch that hands an adapter only static files is on lines 683 to 702.
Coming next: The production checklist. Wherever the shop runs, it checks the same things before launch: performance, security, caching, errors, SEO and monitoring.