Skip to content

Node target: production build keeps installed void external, API-only build crashes, and the CF-only guard misses dependencies #416

Closed

Description

@Brooooooklyn

Problem

Four problems with target: 'node':

What the user does What happens
a Installs void from npm, builds a pages app, runs vp preview A ValidationError thrown in an action gives 500
b Builds an app with no pages/ folder vp build crashes
c Uses a package that imports void/kv No guard error; the request fails later
d Reads packages/void/AGENTS.md The CF-only list does not match the code

a. The production build keeps an installed void external

A Node-target React pages app has an action that throws ValidationError from void/handler. With void installed (npm or pnpm), vp build then vp preview returns Internal Server Error for the form POST. The log shows the error was not handled:

ValidationError: Validation failed
    at .../dist/ssr/assets/form.server-*.js:5:8
    at handlePageMutation (.../dist/ssr/assets/app-*.js)
    ...
    at async .../node_modules/void/dist/index.mjs {
  errors: { email: 'Email already taken' }
}

The same app returns 200 in vp dev (after #413) and with a workspace-linked void. The built dist/ssr imports void/handler, void/sse, void/_env and @void/react at runtime. So the user code's ValidationError class is a different copy from the one Void checks with instanceof. Production also needs these dev dependencies: started with only hono, react and @hono/node-server present, it fails with ERR_MODULE_NOT_FOUND '@void/react'.

b. An API-only build crashes

dist/ssr/app.js   84.65 kB │ gzip: 23.52 kB
✓ built in 21ms
error during build:
TypeError: Cannot destructure property 'logger' of 'environment' as it is undefined.
    at buildEnvironment (.../vite/node/chunks/node.js)
    at Object.build (.../vite/node/chunks/node.js)
    at BasicMinimalPluginContext.buildApp (.../void/dist/index.mjs)

The ssr output is written, then the build fails. This happens with a workspace link too.

c. A dependency that imports void/kv skips the guard

The guard turns import 'void/kv' in app code into a clear error. A package in node_modules that imports void/kv stays external, and Node loads it without going through Vite. With a workspace link:

  • dev: the app's own void/kv route fails with target: 'void/kv' is not available with target 'node'. The package's route returns env: Cloudflare env is unavailable. Use void runtime bindings inside Nuxt/SvelteKit/Analog server request handlers or Worker handlers.
  • build: no guard error; dist/ssr keeps import ... from "xa-lib".

d. AGENTS.md lists the wrong imports

packages/void/AGENTS.md:40 says non-CF targets block void/ws. The code does not block it, and docs/reference/config.md:269 says it is not blocked on purpose. The code blocks void/sandbox, which the list leaves out.

Repro

a. Node-target pages app with void and @void/react installed from npm; a page action that throws new ValidationError({ email: 'Email already taken' }); vp build && vp preview; POST the form.

b.

// void.config.ts
import { defineConfig } from 'void/config';
export default defineConfig({ target: 'node' });

// routes/api/ping.ts  (no pages/ folder)
import { defineHandler } from 'void';
export const GET = defineHandler(() => ({ ok: true }));

Run vp build.

c. Add a package xa-lib in node_modules whose index.js imports { kv } from 'void/kv' and calls kv.put. Import it from a route. Run vp dev and request the route; run vp build.

Cause

  • a. packages/void/src/plugin-node-target.ts:144-151 inlines void (ssr.noExternal) only when the command is not build. In the build, Vite externalizes a bare import that resolves into node_modules. Void's generated code imports runtime files by path, so it gets a second copy. On main (:144-147) neither dev nor build inlines it. Note: adding noExternal: ['void', /^@void\//] to the build alone fails, because Rolldown cannot resolve cloudflare:workers from env-raw.
  • b. plugin-node-target.ts:180-185 returns build.ssr: true and no builder for API-only apps. Vite's CLI then uses the legacy builder, which creates only an ssr environment. buildApp (:191-196) still runs: the ssr build works, then builder.build(builder.environments.client) (:195) gets undefined. The comment at :188-190 says the hook does not run for API-only apps; that is no longer true. Same code on main (:187-192).
  • c. The guard is a resolveId hook (plugin-node-target.ts:118-126). Imports inside an external package never reach it.
  • d. CF_ONLY_IMPORTS at plugin-node-target.ts:9-17 against packages/void/AGENTS.md:40.

Suggested fix

  • a. Bundle void and @void/* into the Node-target production build, and give Node a resolution for cloudflare:workers in env-raw (a stub or alias). Add a packed-consumer check for a Node-target build with an installed void.
  • b. In buildApp, build only the environments that exist (skip client when it is missing), and fix the comment. Add a test that builds an API-only Node-target app.
  • c. Make the CF-only runtime modules throw the same "not available with target 'node'" error on first use under Node, or scan the build's dependencies for these imports.
  • d. Make the AGENTS.md list match CF_ONLY_IMPORTS.

Evidence

🤖 Generated with Claude Code

https://claude.ai/code/session_0123r5HnanmW2VUrZ7VWDufH

Activity

  1. added 5 commits that reference this issue on Oct 14, 2026
    4abec3c
    cdf8874
    85657c6
    39d8dab
    afe1470
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions