Documentation
Differences from Next.js
The compatibility boundaries to understand before adopting vinext.
vinext targets the stable Next.js 16 API surface, not undocumented Vercel behavior or every experimental feature. Run
vinext checkagainst your own application before adopting it.
Toolchain
Development, bundling, and production builds run on Vite 8 instead of the Next.js compiler toolchain. Vite plugins can extend the build, while webpack plugins and framework-specific build internals do not carry over automatically.
React
The Next.js App Router runs on a copy of React bundled inside the next package, so React updates reach your app when a new Next.js release ships. vinext uses the react, react-dom, and react-server-dom-webpack installed in your project instead. When React ships a fix, including a security fix, you can upgrade right away without waiting for a vinext release:
npm install react@latest react-dom@latest react-server-dom-webpack@latest
Keep react-server-dom-webpack in your own package.json, as vinext init sets it up. Without it, the App Router falls back to the copy bundled with @vitejs/plugin-rsc.
Deployment
Cloudflare Workers is the primary native target. Standalone Node output and Nitro adapters cover other hosts. Next.js route config values such as runtime and preferredRegion don't choose where code runs, because runtime placement belongs to the selected deployment adapter. Outside cacheComponents, runtime = "edge" still opts an App Router page out of ISR, as it does in Next.js.
On Cloudflare Workers, middleware does not run for the /_next/static build output, and runs for public/ files only when runWorkerFirst routes them through the Worker, as the config generated by vinext init does. See Middleware and static files.
On Nitro, middleware runs for public/ files on presets where Nitro's own server serves them, but not on hosts that serve the static output first. See Nitro middleware and static files.
Caching
These differences apply to builds without cacheComponents, which follow their own caching rules.
At request time, vinext decides whether a render is dynamic while it renders. (The opt-in prerender and a staged deploy's cacheability probe render the HTML, so they do detect these reads, and the probe's manifest keeps Workers Cache from storing most such paths.) A client navigation requests only the RSC payload, whose render doesn't run client components, so it can't see a "use client" page reading its searchParams, or a useSearchParams() call with no <Suspense> boundary above it. On an otherwise static route, vinext can then store that RSC payload, while Next.js treats the route as dynamic, or fails its build, and stores nothing. Under dynamic = "force-static", both read empty values and the route is static in both, so this doesn't apply. The payload doesn't contain the query, but server-rendered data on the route can stay stale during client navigations until it's revalidated. To match Next.js:
- export
dynamic = "force-dynamic"from the route'slayout.tsx(a"use client"page can't export segment config), or readsearchParamsin a server component page and pass the values down; - wrap
useSearchParams()in<Suspense>, as Next.js requires.
Next.js also treats an otherwise static route as SSG when a layout above its last dynamic segment returns every param from generateStaticParams. vinext doesn't run the generator to classify requests, so it renders such a route on every request unless it exports dynamic = "force-static" or "error". Adding generateStaticParams to the last dynamic segment, even one that returns [], makes the route static in both.
Under dynamicParams = false, Next.js checks a request against the params its build generated and runs no generateStaticParams per request. vinext runs the route's generators when the cache doesn't answer a request, including a request for params the route doesn't list. A child segment's generator runs only for the parent params that match the request, but a generator in a parallel route slot runs for every combination its parents generate. If a child's generator returns a different value for a parent param, vinext runs it for every parent combination once it sees that change. If the generator changes a parent param only for parent values that don't match the request, vinext can miss the change and answer 404 where Next.js renders the page. A cached page or image metadata response is served without this check, so with a pinned generateBuildId and a cache that persists across deploys, an entry an earlier deploy stored can outlive the removal of its params. Keep generators cheap, for example by caching the data they fetch.
Routing config
vinext checks redirects, rewrites and headers sources and middleware matchers for patterns that could backtrack catastrophically on a crafted path, such as /:id((?:a+)+b). Next.js accepts them. vinext skips such a source, warning when the config loads, and fails a middleware matcher. The check is conservative, so some linear patterns are rejected and their rules skipped too, such as the slug source /blog/:slug([a-z0-9]+(?:-[a-z0-9]+)*) or a pattern with very long repeats. It does not yet catch polynomial backtracking across several params, such as /:a(.*)-:b(.*)-:c(.*).
Compatibility
App Router, Pages Router, RSC, Server Actions, middleware, Route Handlers, static generation, ISR, and the commonly used next/* modules are implemented. Cache Components and Partial Prerendering remain incomplete, and build-time image and font optimization do not yet reproduce the full Next.js pipeline.
How to read the docs
If a feature is not listed as a vinext difference, start with the corresponding Next.js documentation. For adoption decisions, the compatibility dashboard is the live view of test coverage, and the issue tracker records known gaps and focused reproductions.