Migrate from Next.js

bext natively serves Next.js applications. It replaces the Node.js runtime and next start with a Rust-native SSR engine, ISR cache, and HTTP server. Most Next.js apps migrate in under an hour with no code changes to your React components.

Feature Mapping

Next.js Feature bext Equivalent Notes
next start bext-server run Drop-in replacement. Auto-detects Next.js project.
ISR (revalidate) bext ISR cache Reads revalidate from page exports. bext.config.toml can override TTLs.
API Routes (/pages/api/) bext routes Served natively. No Node.js process required.
App Router (/app/) Supported Server Components, Server Actions, and streaming all work.
next.config.js rewrites [[route_rules]] Translate rewrite rules to bext route rules (see below).
next.config.js redirects [[route_rules]] Use action = "redirect" in route rules.
next.config.js headers [[route_rules]] Set custom headers per route pattern.
Middleware (middleware.ts) bext plugins Port to a QuickJS or WASM plugin, or use the built-in middleware.ts annotation fallback (comment-based @bext-redirect / @bext-rewrite). Real JS middleware evaluation was removed with the WebKit/JSC backend.
Image Optimization bext image Built-in. No external loader required. Configure in [image].
next/image Works as-is bext serves the /_next/image endpoint natively.
Static exports bext-server run bext serves the out/ directory as static files.
Environment variables bext.config.toml + env NEXT_PUBLIC_* vars work unchanged. Server-side vars passed through.

What Changes

  1. Remove next.config.js (or keep it for next build only). Runtime behavior is now configured in bext.config.toml.
  2. Add bext.config.toml to your project root.
  3. Replace next start with bext-server run in your deployment scripts.
  4. Remove the Node.js runtime from your production Docker image (optional but saves ~100MB).

What Stays the Same

  • Your pages/ or app/ directory structure
  • All React components, hooks, and client-side code
  • CSS Modules, Tailwind, styled-components
  • getStaticProps, getServerSideProps, generateStaticParams
  • Server Actions and Server Components
  • public/ static assets directory

Step-by-Step Migration

1. Install bext

curl -fsSL https://bext.dev/install | sh

2. Build your Next.js app

# Build as usual — bext consumes the Next.js build output
next build

Or let bext handle the build:

bext-server build

3. Create bext.config.toml

[server]
listen = "0.0.0.0:3000"
static_dir = ".next/static"

[render]
bundle_path = ".next/server"
workers = 4

[cache.isr]
max_entries = 10_000
default_ttl_ms = 60_000
default_swr_ms = 3_600_000

4. Translate rewrites and redirects

next.config.js:

module.exports = {
  async rewrites() {
    return [
      { source: '/blog/:slug', destination: '/posts/:slug' },
    ];
  },
  async redirects() {
    return [
      { source: '/old-path', destination: '/new-path', permanent: true },
    ];
  },
};

bext.config.toml:

[[route_rules]]
pattern = "/blog/:slug"
rewrite = "/posts/:slug"

[[route_rules]]
pattern = "/old-path"
action = "redirect"
redirect = "/new-path"
redirect_status = 301

5. Translate custom headers

next.config.js:

module.exports = {
  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [{ key: 'Cache-Control', value: 'no-store' }],
      },
    ];
  },
};

bext.config.toml:

[[route_rules]]
pattern = "/api/**"
render = "ssr"
headers = { "cache-control" = "no-store" }

6. Start bext

bext-server run

Visit http://localhost:3000 and verify your pages render correctly.

7. Run your test suite

Run your existing integration tests and E2E tests (Playwright, Cypress) against the bext server. The HTML output should be identical.

Common Gotchas

next/dynamic with ssr: false -- Works as expected. The component is skipped during SSR and rendered client-side. No changes needed.

Custom server.js -- If you have a custom Node.js server (server.js), you cannot use it with bext directly. Instead, move your custom server logic to bext plugins or use bext as a reverse proxy to your custom server:

[server]
upstream = "http://127.0.0.1:3001"   # your custom server.js

next.config.js webpack customization -- Webpack config only affects next build, which still runs in Node.js. bext does not replace the build step, only the runtime. Your webpack customizations continue to work.

getServerSideProps with database calls -- These run inside the V8 isolate. If you use Node.js-specific APIs (e.g., fs, net), they will not be available. Use bext's upstream proxy mode or move data fetching to an API route.

Middleware NextResponse.rewrite() -- bext does not evaluate middleware.ts as live JavaScript. It scans the file for comment annotations (// @bext-redirect, // @bext-rewrite) and applies those static rules. Redirect/rewrite middleware ports cleanly to annotations or [[route_rules]]; middleware that needs real request-time logic should move to a bext plugin (QuickJS or WASM).

Warning

getServerSideProps and Server Components that call Node.js-specific APIs (fs, net, child_process) will fail inside the V8 isolate — those APIs are not available. Move such data fetching to an API route or use bext's upstream proxy mode to keep that logic in a separate Node.js process.

Tip

Run your existing Playwright or Cypress test suite against the bext server before changing DNS. The HTML output is designed to be identical to next start, so a passing E2E suite is strong evidence the migration is complete.

Related

Links