Skip to content

feat(node): let a server supply the ASSETS that serve the client build - #749

Merged
cpojer merged 2 commits into
voidzero-dev:mainfrom
Saeris:feat/node-supplied-assets
Nov 9, 2026
Merged

cpojer merged 2 commits into
voidzero-dev:mainfrom
Saeris:feat/node-supplied-assets

Conversation

@Saeris

@Saeris Saeris commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Why

The generated Node app (dist/ssr/app.js) always serves the client build from dist/client, found relative to the emitted chunk. A server that embeds the client build has no such directory. A Node single executable application and a bun build --compile executable are two examples. In that case the page renders without its script tags, because the manifest is fetched through the filesystem ASSETS, and it never hydrates. Today the only workarounds are setting globalThis.__VITE_MANIFEST__ and routing /assets/* in front of the app.

We hit this shipping a Void app as one executable to Raspberry Pi menu boards, where a release is a single file and an update is replacing it.

What changes

  • A server can pass its own ASSETS. app.js now uses an ASSETS object (with a fetch(request) method) passed in the Hono env, and otherwise falls back to the dist/client provider as before. The supplied object serves /assets/*, and it reaches the routes as env.ASSETS. So the manifest, hydration tags and public/ files all go through it.
  • The filesystem provider is created on first use. A server that supplies ASSETS never resolves dist/client, and no longer logs serveStatic: root path … is not found at startup.
  • Defaults are unchanged. index.js, vite preview and the prerender runner pass no ASSETS, and behave as before.

Tests

  • New integration test in pages-target-assets.test.ts, for the Node and Bun targets. It moves dist/client away and starts a server that supplies ASSETS from the moved copy. It asserts that every script on the page is served by the supplied provider, that public/ files and API routes still work, and that nothing warns about a missing client directory. Without the change, it fails: the page has no script tags.
  • pages-target-assets.test.ts: 8 passed on Node and Bun 1.4.4; Deno is not installed here, so its 4 were skipped and the new test is not run for it.
  • compile.test.ts and plugin-routing.test.ts: pass.
  • void typecheck, vp fmt and vp lint: clean.

Also checked by hand, each executable run from an empty directory:

  • With this change, a Node 26 SEA (on Windows) and a bun build --compile executable (client files embedded through type: "file" imports), each passing ASSETS as in the new docs: the page had its script tags, every asset was served from inside the binary, and nothing warned at startup.
  • Before it, with the __VITE_MANIFEST__ workaround: the same app as a Node SEA hydrated in Chrome, and cross-built for linux-arm64 it ran on a Raspberry Pi 400 (Debian trixie), ready in 366 ms.

Docs

  • docs/integrations/nodejs-bun-deno.md: a short "Serving the client build from somewhere else" section, with a node:sea example and a Bun one (embedding with type: "file" imports, and passing server so getConnInfo() keeps working).
  • packages/void/AGENTS.md: one sentence on the override.

@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 for the PR 👍

@cpojer
cpojer merged commit e164e4e into voidzero-dev:main Nov 9, 2026
2 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.

2 participants