Skip to content

From Next.js (15 & 16)

This guide takes a standard Next.js app and runs it on the brrrd runtime. It is the runtime half of a migration; if your data is in Cloudflare D1, do the Cloudflare guide instead (it includes this runtime work plus the database move).

Install
pnpm add @brrrd/adapter

Wire it into your Next config via Next’s adapterPath. If your config is ESM (next.config.ts / .mjs), resolve the adapter with createRequire:

next.config.ts
import type { NextConfig } from "next";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const nextConfig: NextConfig = {
adapterPath: require.resolve("@brrrd/adapter"),
// If you talk to libSQL over websockets, keep the ws client external:
serverExternalPackages: ["@libsql/isomorphic-ws"],
};
export default nextConfig;

Remove any other hosting adapter (e.g. OpenNext / @opennextjs/cloudflare init) — brrrd is now your target.

Build for brrrd
next build --webpack

The adapter emits a deployable package at dist/brrrd. That directory is what you hand to comwit deploy.

3. (If you need a database) create one on Louhi

Section titled “3. (If you need a database) create one on Louhi”
Create a Louhi database
comwit databases create --project <projectId> --name app-db

Copy the one-time database_token from the output (it is shown once). Your app connects with the returned database_url + token — see Create & connect a database. You will set these as environment variables in step 5.

brrrd runs your app in V8 isolates. These are the constraints that actually bite, each with a one-line fix. Skim them now — they are cheaper to fix before the first deploy than to debug after.

  • No native .node addons. next/og’s ImageResponse pulls in sharp on the Node runtime, which isolates can’t load. Make OG/icon routes run on the edge runtime (a WASM encoder is used instead):

    app/opengraph-image.tsx (and icon routes)
    export const runtime = "edge";

    @brrrd/adapter already forces images.unoptimized for you (there is no sharp in the isolate), so you don’t strictly need to set it — adding images: { unoptimized: true } yourself is harmless and just makes the intent explicit.

  • Middleware matchers: no regex look-around. brrrd’s matcher engine rejects negative lookahead like /((?!api|_next/static).*) (you’ll see “look-around not supported”). Use a match-all matcher and filter inside the function:

    middleware.ts
    export const config = { matcher: "/:path*" };
    // …then early-return for paths you want to skip inside middleware().
  • 30s request wall-clock. A single request may run up to ~30s. Move anything longer (heavy jobs, long streams) off the request path.

  • public/ assets are served by the adapter, with correct MIME types for common file types (images, video, audio, fonts, PDF, CSV, ZIP…) and HTTP Range support, so <video>/<audio> stream and seek. Only unrecognized/exotic extensions fall back to application/octet-stream.

Create the app, set any env it needs, and deploy the build:

Create, configure, deploy
comwit apps create --project <projectId> --name web
# Plain env values only (no secret store yet). For a Louhi DB:
# PUT /v1/projects/{projectId}/apps/{appId}/environment/DATABASE_URL { "value": "...", "secret": false }
# PUT /v1/projects/{projectId}/apps/{appId}/environment/DATABASE_AUTH_TOKEN { "value": "...", "secret": false }
comwit deploy \
--project <projectId> \
--app <appId> \
--package ./dist/brrrd \
--host app.example.com

See Deploy an app for all deploy flags, and Environment variables for how env is applied (plain values only — keep real secrets out for now). To attach a custom domain, see Custom hostnames.

Because the adapter requires Next ^16.2, bump first:

Upgrade Next + React
pnpm add next@^16.2 react@^19 react-dom@^19

Then run next build --webpack and fix anything the Next 16 upgrade surfaces before applying the steps above. The Next.js codemods (npx @next/codemod@latest upgrade) handle most breaking changes; the brrrd-specific work is the same as for any Next 16 app.

After deploy, hit your hostname and check the pages render (not just that the build passed — isolate-only runtime errors don’t show up at build time). Then roll forward normally with comwit deploy, or automate it from GitHub Actions.