Local development
September 18, 2026 · View on GitHub
Prerequisites, install, and npm run dev notes. See the
setup index for checks, migrations, seeding, and preview deploys.
Prerequisites
- Node 26 and npm (used for installs and scripts).
Install
npm install- The repo root hosts the Nx workspace metadata; runtime packages live under
packages/.
Local development
- Cloudflare D1 and KV: Local development does not require creating or
linking remote D1 databases or KV namespaces.
npm run devruns the worker with local Wrangler persistence for D1/KV emulation. - Production and preview deploys: GitHub Actions do not rely on IDs baked
into the repo. They run
node tools/ci/production-resources.ts ensure(production) ornode tools/ci/preview-resources.ts ensure(per-preview worker name), which create or resolve app/audit D1 databases and the OAuth KV namespace, then write generated Wrangler configs with realdatabase_idand KVidvalues:packages/worker/wrangler-production.generated.jsonandpackages/worker/wrangler-preview.generated.json(gitignored). Preview and production also ensure sibling platform/runtime/jobs worker configs andJOBS_DB. KV titles follow the worker name: production defaults to<worker-name>-oauth; preview uses<preview-worker-name>-oauth-kv(seetools/ci/preview-resources.ts). - Exporting from an existing remote D1: export the remote database to a
local SQLite file with
tools/export-d1-remote-to-sqlite.sh, then copy only the tables you need into the local Kody database. - Copy
packages/worker/.env.exampletopackages/worker/.envbefore starting any work, then update secrets as needed. The example includes placeholder values forCOOKIE_SECRETandSECRET_STORE_KEY; all environments must set both secrets (seedocs/contributing/secret-rotation.md). npm run dev:ensurereuses a healthy origin/healthon 3742–3751 (printsApp running at http://localhost:<port>and exits 0), waits for a stale kody/workerd leftover that is listening but not serving before replacing it, then startsnpm run devand waits until/healthis ok. Agents should call this instead of reconstructing a startup playbook from terminal files.npm run devstarts the Cloudflare API mock, then Vite (@pitlane/dev+@cloudflare/vite-plugin) so origin SSR runs inside workerd with client HMR. Jobs and highlight join as Vite auxiliary workers in every serve, includingCLOUDFLARE_ENV=test. Platform and runtime join only outside the test env. Originenvbindings come from generatedpackages/worker/wrangler-local-dev.generated.json(WRANGLER_IS_LOCAL_DEVand mockCLOUDFLARE_API_*). It setsCLOUDFLARE_API_BASE_URL,CLOUDFLARE_API_TOKEN, andCLOUDFLARE_ACCOUNT_IDto the local Cloudflare API mock Worker for the internal Cloudflare API client, local email sending, and Artifacts REST repo create/get/list/token/fork calls. Those REST calls do not hit the live Cloudflare Artifacts control plane during normal local development. The mock covers only the REST control plane; repo-session git clone/pull/push flows need a real Git-capable Artifacts remote and are not fully simulated by the local mock. Password reset and email-verification messages send through the same Cloudflare Email API helper. Both send fromkody@<SYSTEM_EMAIL_DOMAIN>(falling back to theAPP_BASE_URLhostname) and put that same sending domain on action and asset links whenSYSTEM_EMAIL_DOMAINis set, so a staleAPP_BASE_URLcannot pin a retired hostname into the message. Localnpm run devkeeps those action and asset links on the request origin so they stay clickable. SetSKIP_CLOUDFLARE_MOCK=1to skip the local Cloudflare mock entirely. Vite streams origin logs live; the background mock buffers logs and only prints them if that child process exits with an error.- MCP
searchuses a deterministic offline ranker in tests and whenWRANGLER_IS_LOCAL_DEVis set (no Vectorize, embedding, or Jev Score calls required fornpm run testor unauthenticated local runs). Production uses Vectorize plus the Workers AI@cf/baai/bge-small-en-v1.5embedding model through theAIbinding, optionally routed through AI Gateway. Ranked search can also calltypesafe/jevwhenjev-search-rerankis on; seeenvironment-variables.mdand feature flags. - Add new mock API servers by following
mock-api-servers.md. npm run dev:client,npm run dev:vite, andnpm run dev:workerall start the same Vite origin (client + worker in one workerd graph).- Worker-side HMR: the browser graph hot-swaps components as usual. In the
Worker environments, an edit that touches a component module (or a module a
component module imports) reloads the whole worker graph
(
page reload … (whole worker graph)in the log; requests in flight wait for it), because Vite's incremental SSR update re-evaluates only the importer chain and splits Remix component identities across evaluations. Pure server chains (handlers, data loaders) keep the incremental update, so module-level registration side effects on that path must be idempotent. Seetools/vite-worker-whole-graph-reload.ts. The anonymous marketing HTML cache (caches.default) is bypassed underWRANGLER_IS_LOCAL_DEV, so an anonymous tab sees the edit on the next reload instead of a stored page. - Set
CLOUDFLARE_ENVto switch Wrangler environments (defaults toproduction). Playwright sets this totest.