Skip to content

fix(deploy): serve framework static files without starting the Worker - #804

Merged
cpojer merged 1 commit into
voidzero-dev:mainfrom
morinokami:fix/framework-run-worker-first
Nov 12, 2026
Merged

cpojer merged 1 commit into
voidzero-dev:mainfrom
morinokami:fix/framework-run-worker-first

Conversation

@morinokami

Copy link
Copy Markdown
Collaborator

Closes #794

void deploy --platform cloudflare writes assets.run_worker_first: true for SvelteKit, Nuxt, Analog, and Astro. Every request starts the Worker, including fingerprinted build files (/_astro/*, /_app/immutable/*, /_nuxt/*) and prerendered pages, and each one counts as a Worker request. The framework Worker only passes those requests to ASSETS. The write also replaces a run_worker_first set in cloudflare.assets in void.config.ts, although docs/reference/config.md says Void passes cloudflare fields through.

Each framework's own Cloudflare config, and managed framework deploys, leave run_worker_first unset, so Cloudflare serves files without starting the Worker.

Fix

Without a run_worker_first in void.config.ts, framework deploys now write ["/__void", "/__void/*"]:

request ──► Workers Static Assets
             │
             ├─ /__void, /__void/* ──► Worker
             ├─ file matches ─────────► served, no Worker
             └─ no file ──────────────► Worker (not_found_handling: none)
  • Server-rendered pages and endpoints have no file, so they still reach the Worker. Void's readiness, job, and queue endpoints always do.
  • A run_worker_first set in void.config.ts is written as is. true keeps the previous routing.
  • The setting is read from void.config.ts, not from the generated config. void.lock.json can hold the true an earlier deploy wrote, and the generated config is restored from it.
  • writeCloudflareFrameworkDeployConfig in @void/deploy-cloudflare takes an optional runWorkerFirst. It still defaults to true.
  • docs/guide/edge/static-assets.md describes the default and the setting.

Running the Worker first gave these files nothing extra:

  • Void's framework wrapper handles only /__void/* and HTML document ISR.
  • The framework Workers pass static files and prerendered pages to ASSETS before rendering, so hooks and middleware do not run for them: SvelteKit's adapter Worker, Astro's (matchStaticAsset), and Nitro's cloudflare-module preset (isPublicAssetURL). A missing file still reaches the Worker, which answers as before.

SvelteKit's asset directory

SvelteKit uploads its Worker directory as its assets. With true, its Worker answered only its own files, so a module Void left there was stored but not served (#737). Cloudflare now serves any uploaded file from that directory, so .assetsignore must list every module Void adds. A comment at that list says so. The test that checks SvelteKit's uploaded files now enables ISR, so it covers __void_isr.mjs as well.

Projects deployed with v0.20

v0.20 wrote run_worker_first: true into the root wrangler.jsonc, and the migration to void.config.ts copied it into cloudflare.assets. Those projects keep true until they remove that line.

Tests

  • packages/deploy-cloudflare/test/unit/framework.test.ts: a given run_worker_first (a rule list, or false) replaces an earlier true, and writing it again changes nothing.
  • packages/void/test/unit/cloudflare-deploy.test.ts:
    • The SvelteKit case expects the new default.
    • An Astro project whose void.lock.json kept an earlier true gets the default.
    • ["/api/*"], true, and false set in void.config.ts are written as is.
    • The SvelteKit asset case enables ISR, so __void_isr.mjs must stay out of the upload too.

Without this fix, the default, lock, ["/api/*"], and false cases fail with run_worker_first: true.

Verification

  • vp run test (all 794 files): 793 pass, 1 skipped (16,695 tests pass, 30 skipped).
  • vp run check: 33/33 pass.
  • Miniflare, with a Worker that stands in for a wrapped framework Worker and marks its responses, and an /_astro/* immutable rule in _headers:
    • With true, /_astro/app.abc123.js, /, /about/, /smoke, and /__void/ready all start the Worker.
    • With the new rule, the file and the two prerendered pages are served without it, with the same status, Cache-Control, and body. /smoke renders in the Worker, and /__void/ready answers {"ready":true}.
  • Not run: void deploy against Cloudflare.

🤖 Generated with Claude Code

Direct Cloudflare deploys of SvelteKit, Nuxt, Analog, and Astro wrote
`assets.run_worker_first: true`. Every request for a built file or a
prerendered page started the Worker, and a `run_worker_first` set in
`cloudflare.assets` was overwritten.

These deploys now default to `["/__void", "/__void/*"]`, like each
framework's own Cloudflare config and managed framework deploys. Paths
without a file still reach the Worker through `not_found_handling:
"none"`. A `run_worker_first` set in void.config.ts is used as is. It is
read from void.config.ts because the generated config can still hold
the `true` an earlier deploy recorded in void.lock.json.

SvelteKit uploads its Worker directory as assets, so the test that
checks its uploaded files now covers the ISR module too.

Fixes voidzero-dev#794

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cpojer cpojer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Thank you.

@cpojer
cpojer merged commit edef7fd into voidzero-dev:main Nov 12, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Framework deploys hard-code assets.run_worker_first: true, so every static asset invokes the Worker

2 participants