Deploy
All three apps target Cloudflare Workers. Backend native, Next.js apps via OpenNext.
Every app has its own wrangler.jsonc next to its source. Wrangler resolves
paths relative to that file — the monorepo root doesn't get involved.
Targets
| App | Adapter | Entry |
|---|---|---|
@darna/backend | native (Hono is fetch-native) | src/worker.ts |
@darna/admin | @opennextjs/cloudflare | .open-next/worker.js |
@darna/docs | @opennextjs/cloudflare | .open-next/worker.js |
Pages was deprecated for new Next.js deployments. OpenNext compiles a Next.js app into a single Worker plus a static-asset binding.
Prerequisites
- Node ≥ 22 (Wrangler v4 requires it).
- A Cloudflare account.
wrangler loginonce, orCLOUDFLARE_API_TOKEN+CLOUDFLARE_ACCOUNT_IDin CI.
Commands
pnpm deploy # backend → admin → docs
pnpm deploy:backend # individually
pnpm deploy:admin
pnpm deploy:docs
# Next.js local Workers preview (closer-to-prod than `next dev`)
pnpm --filter @darna/admin preview
pnpm --filter @darna/docs preview
# `pnpm dev:backend` already runs the real Workers runtime via wrangler dev.First deploy of each Worker creates a *.workers.dev subdomain. No DNS or
custom domain needed.
The pnpm + Turbopack gotcha
OpenNext invokes next build with NEXT_PRIVATE_OUTPUT_TRACE_ROOT set to
the app directory. That overrides turbopack.root, which means Turbopack
can't follow the symlink from apps/admin/node_modules/next into
node_modules/.pnpm/next@…/.
Fix: pin turbopack.root to the monorepo root in each Next.js app.
// apps/admin/next.config.ts
import path from "node:path";
const nextConfig: NextConfig = {
turbopack: {
root: path.resolve(process.cwd(), "../.."),
},
};// apps/docs/next.config.mjs
const config = {
turbopack: {
root: path.resolve(import.meta.dirname, "../.."),
},
};Don't use import.meta.dirname in next.config.ts. Next compiles .ts configs to CommonJS, but
referencing import.meta forces ESM output and the compiled file then collides with exports.
process.cwd() works in TS configs; import.meta.dirname works in .mjs.
The other foot-gun is stray per-app pnpm-lock.yaml files. create-next-app
and create-hono write one before they know they're inside a workspace.
OpenNext's findPackagerAndRoot stops at the first lockfile it finds, so
those strays make it think each app is its own monorepo. Delete them — the
root lockfile is the only one that should exist.
Environment
- Non-secret config —
varsblock inwrangler.jsonc. - Secrets —
wrangler secret put NAMEfrom the app directory (and again with--env stagingfor staging). - Local dev —
apps/backend/.env. Read bywrangler dev --env-file=.envfor both wrangler CLI vars and Worker bindings, and by drizzle-kit / migrate via Node's--env-file-if-exists. Next.js apps useapps/<app>/.env.local.
Cloudflare credentials
For interactive use:
pnpm --filter @darna/backend exec wrangler loginSaves a token in ~/.config/.wrangler/. One login covers all three apps.
For CI, create a token at
dash.cloudflare.com/profile/api-tokens
using the Edit Cloudflare Workers template, then export
CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.
Custom domains
Add a routes block to the relevant wrangler.jsonc:
"routes": [
{ "pattern": "api.example.com", "custom_domain": true }
]The zone has to already be on Cloudflare and in your account.