Skip to content

Build & Deployment

What npm run build actually does

npm run build
 ├── node tools/lint-content.mjs     # gate: slugs, sizes, banned includes
 ├── node tools/build-content.mjs    # shards indexes, copies bodies, splits sidecars
 ├── node tools/copy-sql-wasm.mjs    # ships sql.js WASM into public/
 └── vite build                      # SPA → dist/ (public/ copied through)

Content generation is inside the build on purpose: a deploy can never ship an index that disagrees with the bodies, because both are produced by the same run.

The deploy pipeline

deploy.yml is a workflow_dispatch that calls a reusable workflow in a separate ci-infra repo (cloudflare-frontend-engine.yml), with working-directory: ediky (the app is one level down). The reusable workflow builds and pushes to Cloudflare Pages. ci.yml separately runs lint + build on every push/PR as a gate.

The two-repo env-var lesson

Build-time VITE_FIREBASE_* / VITE_CLOUDINARY_* values are inlined by Vite at build time — they must exist in the environment of the reusable workflow's build step. The gotcha: secrets don't flow implicitly across repos. The caller must pass them explicitly (secrets: / vars: in the uses: block) and the reusable workflow must declare them. The failure mode was builds that succeed but produce an app that can't reach Firebase — blank login, no build error. If you add a new VITE_* var, it goes in three places: .env.example, the caller's pass-through, and the reusable workflow's inputs.

The dual-pipeline race

For a while the repo had both Cloudflare's native Git integration and the GitHub Actions deploy active. Two builds raced on every push; whichever finished last won, sometimes deploying without the Actions-only env context. Fix: exactly one deploy path (Actions), native Git integration disabled. If deploys ever look nondeterministic, check this first.

Stale-SPA fix

public/_headers sends no-cache for index.html and long-cache for hashed assets. Before it, a deploy could strand users on an old index.html referencing purged chunk hashes — white screen until hard refresh.

Runtime configuration (not build-time)

Pages Functions read secrets from the Pages project env at request time — AI provider keys, JUDGE0_URL/JUDGE0_TOKEN, GITHUB_TOKEN. These never touch the client bundle and can be rotated without a rebuild. The split matters: VITE_* = public, baked in; Function env = secret, live.

One infra footnote that bites here: Functions couldn't reach Judge0 by raw IP (egress policy) — the judge is addressed via a DNS A record hostname.

Workers deploy separately

worker/ and worker-reminders/ each deploy with wrangler deploy from their own directory, on their own cadence. Their secrets (service-account key, RESEND_API_KEY) live in Worker env via wrangler secret put. A website deploy never restarts a Worker.

This docs site

Ediky Workflow deploys the same way conceptually: npm run build (which first regenerates the AI retrieval index) → Pages, with functions/ at the repo root auto-deployed alongside. Its deploy notes live in the repo's DEPLOY.md.

Ediky Workflow — internal engineering documentation.