knext

Bun & the compiled executable

knext's default build compiles your app into a single executable — bundled, minified, and bytecode-precompiled, with the Bun runtime baked in.

knext's default build does not ship a folder of JavaScript. It compiles your app into a single executable: the server bundle is minified, precompiled to bytecode ahead of time, and baked together with the Bun 1.4 runtime into one binary that the image runs directly. Precompiling to bytecode means the server boots in tens of milliseconds instead of parsing your source at startup — the boot phase where a Next.js app otherwise spends real CPU.

The numbers

All figures are the same app and the same route, responses verified byte-identical, ten samples each with a fresh local process and port — i.e. this measures the server's own boot, not an end-to-end deployment cold start:

TargetProcess boot (median)Requests/sec
Node standalone884 ms630
Bun 1.4 standalone703 ms714
Compiled executable (default)61 ms1103

The compiled executable boots 14× faster and serves 75% more requests per second than the Node standalone it replaces. It wins both axes at once because the whole bundle is compiled together: minification and bundling remove the per-module boundary cost that made earlier file-by-file bytecode approaches trade throughput for cold start.

These are process-boot numbers from a fresh local process. On Knative scale-from-zero the end-to-end cold start a user pays is dominated by the platform — activation, scheduling, and container start — which on a real cluster is measured comparable across targets, so the boot win is one component of that budget rather than the whole of it. The compiled executable's boot advantage pays off most where that platform overhead is small or pre-warmed. See Scale-to-zero & cold starts for the full budget.

Nothing to configure

This is the default. A minimal config builds and deploys the compiled executable:

kn-next.config.ts
const config: KnativeNextConfig = {
  name: 'acme',
  registry: 'registry.example.com/acme',
  // ...
};

kn-next build produces the binary (plus the native image-optimization module that ships beside it), and kn-next deploy builds it into the image. The NextApp resource records the artifact (spec.build: "vinext"), and the operator starts the container through the image's own entrypoint — the executable is the server.

Building from source requires Bun 1.4 or newer on your PATH. The kn-next CLI itself runs under plain Node — only this compile step shells out to bun. If Bun is missing, the build stops and tells you how to install it; if Bun is present but bun --version fails for another reason, the build reports that underlying error instead of claiming Bun is not installed. Older versions are refused rather than silently producing a binary that boots roughly twice as slowly — upgrade with bun upgrade.

Warm-on-boot (optional)

When a scaled-to-zero app wakes, the very first request pays the full cost of a cold process — JIT warm-up and the first render. To keep a real visitor from paying it, the runtime fires a synthetic request at your own app the moment it boots, overlapped with becoming ready, so the hot path is already warm when the first user arrives. This is on by default; you don't need to configure anything.

Two environment variables tune it:

  • KNEXT_WARM_PATH — a comma-separated list of paths to warm, in order (default /api/health). The first fires immediately; the rest follow one at a time, so a contended cold CPU isn't fighting several warm requests at once. Point it at the routes whose first render is expensive:

    kn-next.config.ts
    const config: KnativeNextConfig = {
      name: 'acme',
      env: {
        KNEXT_WARM_PATH: '/,/api/health',
      },
    };
  • KNEXT_EAGER_WARM=0 — turns warm-on-boot off entirely.

Only warm paths that render the same for every anonymous visitor. The warm request is synthetic — it carries no cookies and no authentication — and its response is cached. Warming a per-user or authenticated page would prime the cache with the logged-out render and serve it to the first real user. / is a good choice only when your home page is identical for everyone; otherwise leave the default /api/health.

Set these in your kn-next.config.ts env: block (they become spec.env on the NextApp), the same way as any other runtime environment variable.

Your app must be an ES module

The compiled build requires your app to be an ES module — your package.json must set:

package.json
{
  "type": "module"
}

kn-next create sets this for you, so a freshly scaffolded app already satisfies it and you never have to think about it. The requirement matters only when you point the compiled build at an existing Next.js app that was written as CommonJS: add "type": "module" to its package.json before you build. Without it, the build fails while wiring up React Server Components — the server/client module graph cannot be resolved — rather than producing a broken binary.

If your project still contains CommonJS files that rely on require() or a .js extension meaning CommonJS, rename them to .cjs (or convert them to import/export) so they keep working under "type": "module".

Image optimization is included

The compiled binary cannot load native modules from inside itself, so knext ships the image codec (sharp) beside the executable and loads it by path. /_next/image serves optimized AVIF/WebP exactly as it does on a folder deployment — verified in CI against the production image. See Image optimization.

What the executable is, plainly

  • One file. The server, every route it reaches, and the Bun runtime, compiled together (~70 MB). Static assets and the native image codec ship alongside it in the image.
  • Bytecode ahead of time. The engine never parses your server source at boot — that work happened at build time, which is where most of the 14× comes from.
  • Linux x64. The build cross-compiles for the deployment platform regardless of the machine you build on.
  • Runtime is baked in. The runtime config field only affects the standalone target (below); for the default compiled build the runtime ships inside the binary and there is nothing to select.

Verification status, stated honestly

knext's official-suite credential — 778/778 nightly — was earned on the Node standalone lane. The compiled executable is a different artifact, and its own suite lane is being stood up; until it is green, treat the compatibility claim as measured-per-feature rather than suite-verified, with gaps published on the compatibility matrix rather than papered over.

The standalone target on Bun is compiled too

The selectable standalone targets (build: 'turbopack' or build: 'webpack' — identical artifacts, different next build bundler; see the build pipeline) run next build's own server. With runtime: 'bun', kn-next build compiles that server into a Bun single executable with bytecode as well, and the image runs the executable instead of bun server.js:

  • What is inside. Next's server entry and everything it loads up front — the request pipeline, the router, the render entry points — bundled, minified and precompiled to bytecode.
  • What stays beside it. The standalone folder itself. Next loads each route's compiled chunk from .next/server by path when the route is first requested, so the executable sits inside that folder in the image and serves the tree next to it.
  • One copy of every shared module. Any module that both the executable and the files beside it load — Next's internal singletons, React, the Pages Router runtime — is left on disk and loaded from there by both, so each exists exactly once in the process. That includes the modules your custom cacheHandler (and any cacheHandlers entry) imports, so a handler may use Next internals safely. Write those imports as plain string literals (require('next/dist/…')): a module path computed at runtime cannot be seen at build time.
  • The cache handler must ship with the app. Configure cacheHandler as a file inside your project so next build copies it into the standalone folder. If the configured handler is not there, kn-next build fails and names the file, because it cannot check what that handler imports.
  • Bytecode is checked, not assumed. After compiling, kn-next build inspects the executable and fails the build if the server was not compiled to bytecode. A binary without it would boot and serve — just slower on every cold start — so it is refused rather than shipped.
  • Build requirements. Bun 1.4 or newer on the machine running kn-next build, and output: 'standalone' in next.config.ts. A build with no standalone server fails instead of producing an image without the executable.

With runtime: 'node' nothing is compiled: Node's bytecode caching for this target is the V8 compile cache baked into the image (see bytecode caching).

The retired per-file bytecode pass

Earlier knext versions offered an optional per-file bytecode pass for the standalone folder under runtime: 'bun'. That pass is retired: it improved boot (554 ms vs 703 ms) but reduced throughput (537 req/s against Node's 630), because converting each module individually taxes every module boundary on every request. Whole-bundle compilation — of the vinext build by default, and of the standalone server on Bun as described above — replaced it.

On this page