Development
The pnext CLI carries the whole flow: dev while you build, analyze to inspect what ships, build and start for production.
pnext dev [directory] [--port 3000] [--hostname 127.0.0.1]
pnext build [directory] [--adapter vercel] [--verbose]
pnext start [directory] [--port 3000] [--hostname 127.0.0.1]
pnext analyze [route] [directory] [--brotli] [--files] [--json]
pnext typegen [directory]
pnext create <directory> [--no-install]
pnext migrate [directory] [--dry-run]
create scaffolds a new app with everything set up. migrate converts a Next.js project in place: it rewrites package.json and tsconfig.json and creates a pnext.config.ts with compat.next enabled, without editing app source; --dry-run previews. To set a project up by hand, add the package and scripts:
{
"devDependencies": {
"@wular/pnext": "latest"
},
"scripts": {
"dev": "pnext dev",
"build": "pnext build",
"start": "pnext start",
"analyze": "pnext analyze"
}
}
Dev server
pnext dev starts a Bun HTTP server that scans the route tree, renders matching pages on request, and serves static assets from public/. Client route entries are built on demand and cached in .pnext/cache. Browser pages reload on route-tree changes over a server-sent events stream at /__pnext/events; the other /__pnext/* endpoints are implementation details, not application routes.
The dev server does not typecheck in the request path; run your package's lint and typecheck scripts separately. pnext typegen regenerates the route types on demand; see Type Safety.
In Activity Monitor and ps, the dev server runs as pnext-dev, builds as pnext-build, and the bundler service as pnext-esbuild. The dev server re-execs itself when its memory passes PNEXT_DEV_MAX_RSS_MB (default 2048), and restarts the bundler past PNEXT_DEV_MAX_ESBUILD_RSS_MB (default 1024).
Analyze
pnext analyze reports the client JavaScript behind each route. Optionally pass a route, either a template like /users/[id] or a concrete path, to report on that route only. --files breaks the report into files, --brotli measures with brotli instead of gzip, and --json emits machine-readable output.
Build and run
pnext build makes the production build and pnext start serves it. Routes render on the server per request; ones that never read the request are prerendered to static HTML at build time. A compat.next build typechecks off-thread alongside bundling.
Debug flags: --experimental-build-mode compile|generate splits the build into its two phases, --debug-build-paths <paths> narrows diagnostics to matching paths, and --debug-prerender prints prerender diagnostics.
Deploy
pnext deploys anywhere Bun runs: a VPS, a container, or any host you control. Run pnext build on the machine or in CI, then pnext start serves the app on your port.
Vercel has a dedicated adapter: pnext build --adapter vercel writes Build Output to .vercel/output. Static pages and static route-handler responses are emitted as files; everything dynamic runs in a single _pnext function on Vercel's Bun runtime.
Environment variables
PNEXT_COMPAT=next: Next compatibility without a config file. See Compatibility.PNEXT_TYPECHECK=classic: in-process TypeScript checker instead of the native one.PNEXT_TRACE=<scopes>: turn on diagnostic output. Comma-separated scopes, eachscopeorscope=value;1meansboot. A scope is all-or-nothing — it turns on everything it covers. Example:PNEXT_TRACE=boot,server=/tmp/modules.json.
| scope | prints / does |
|---|---|
boot |
boot-phase timings and memory readings |
server |
dev request, render and build timings; per-module compile attribution; route-facts and global-css hit/miss; why each artifact was recompiled; load-plugin registrations |
vendor |
vendor saturation spans, esbuild plugin-callback attribution, and the preplan / native decision report |
client |
client-build phase timings, plus the esbuild metafile written into the output directory |
all |
the four above |
Two scopes take a =<path> value:
server=<path>dumps the per-module compile list as JSON. A.jsonlpath collects the heavy-package facade profile instead — the two are never wanted at once, so the suffix picks.vendor=<path>appends the JSONL workload tracebench/tools/vendor-analyze.tsreads.