Skip to content

Add standalone self-host output and align init production scripts#178

Open
sankalpmukim wants to merge 1 commit intocloudflare:mainfrom
sankalpmukim:feat/selfhost-standalone-build-start
Open

Add standalone self-host output and align init production scripts#178
sankalpmukim wants to merge 1 commit intocloudflare:mainfrom
sankalpmukim:feat/selfhost-standalone-build-start

Conversation

@sankalpmukim
Copy link

Summary

  • add output: \"standalone\" support in vinext build that emits dist/standalone with a runnable server.js, built artifacts, and copied runtime dependencies for self-hosted VPS deployments
  • add production-phase next.config loading for build mode, and extract computeLazyChunks into a shared utility so the production server no longer imports the full plugin module
  • update vinext init to add dev:vinext, build:vinext, and start:vinext scripts using the vinext CLI, plus docs/check metadata and new unit coverage

Testing

  • corepack pnpm vitest run tests/init.test.ts tests/standalone-build.test.ts
  • corepack pnpm exec oxlint packages/vinext/src/build/standalone.ts packages/vinext/src/cli.ts packages/vinext/src/init.ts packages/vinext/src/utils/lazy-chunks.ts tests/standalone-build.test.ts tests/init.test.ts packages/vinext/src/config/next-config.ts packages/vinext/src/server/prod-server.ts packages/vinext/src/index.ts packages/vinext/src/check.ts
  • corepack pnpm --filter vinext run build

@southpolesteve
Copy link
Collaborator

/bigbonk review this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 2, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@178

commit: f9236b8

Copy link
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Review: Standalone self-host output

The overall approach is solid — extracting computeLazyChunks to break the circular import, adding standalone output as a post-build step, and the transitive dependency copying are all well-structured. A few issues to address before merge:

Must-fix

  1. server.js mixes CJS and ESM in an incompatible way — uses require() and __dirname (CJS) alongside top-level await import() (ESM-ish). This will break if the standalone directory has a package.json with "type": "module" or if Node runs it as ESM. Needs to be consistently CJS or ESM.
  2. HOSTNAME env var collides with the system-set hostname — on Linux, HOSTNAME is set by the OS to the machine's hostname (e.g. ip-172-31-1-42), which would override the default 0.0.0.0 binding. Next.js standalone uses HOSTNAME too, but that's a known footgun. Consider using HOST or documenting the HOSTNAME pitfall.
  3. Regex objects are shared across files but only reset at the inner loop — the three regexes (importExportRE, etc.) are created once and reused across all files. The lastIndex = 0 reset on line 89 is correct, but only because the regexes are declared inside the function (not at module scope). This is fine but fragile — a future refactor moving them to module scope would silently break. A comment would help.

Should-fix

  1. No package.json written into standalone/ — without a package.json with "type": "module", the embedded vinext package (which has "type": "module") may have module resolution issues since Node walks up to find the nearest package.json.
  2. optionalDependencies failures are silently swallowedcopyPackageAndRuntimeDeps catches resolve failures and continues. This is correct for optional deps but also silently drops required deps that fail to resolve (e.g. if they use exports maps that don't expose package.json).
  3. Missing test for symlinked node_modulesdereference: true is used in cpSync, which is correct, but there's no test covering symlinked packages (common with pnpm).

@ask-bonk
Copy link
Contributor

ask-bonk bot commented Mar 2, 2026

Review posted with "Request Changes". Here's a summary of the key findings:

Must-fix issues:

  1. CJS/ESM mismatch in server.js — The generated entry script uses require() and __dirname (CJS) alongside await import(). If a package.json with "type": "module" exists up the directory tree, Node will parse it as ESM and crash. Needs either a {"type": "commonjs"} package.json in standalone/ or a full ESM rewrite (using import.meta.dirname, which is available since Node >=22 is required).

  2. HOSTNAME env var collision — On Linux, HOSTNAME is set by the OS to the machine's hostname, so process.env.HOSTNAME will almost always resolve to something like a container ID rather than falling through to "0.0.0.0". Should use HOST instead.

  3. Fragile regex reuse — The stateful regex objects work correctly due to lastIndex = 0 resets, but a future refactor could silently break this. Added a note.

Should-fix issues:

  1. Missing package.json in standalone directory for module resolution
  2. Silent swallowing of required dep resolution failures (not just optional deps)
  3. Missing test coverage for symlinked node_modules (pnpm)

Additional suggestions:

  • Use fs.readdirSync with { recursive: true } (available Node 18.17+) to simplify walkFiles
  • Define named constants for Next.js phase strings to avoid typos
  • Improve the import regex to handle export { x } from "foo" patterns
  • Add a comment in the test explaining why a devDependency is expected to be copied

github run

@sankalpmukim
Copy link
Author

Working on the suggestions

Generate dist/standalone from output: 'standalone' with a runnable server entry and runtime deps, and align init scripts with vinext build/start for production self-host workflows.
@sankalpmukim sankalpmukim force-pushed the feat/selfhost-standalone-build-start branch from e64eebc to 17a416e Compare March 2, 2026 02:08
@sankalpmukim
Copy link
Author

Implemented the requested updates and rebased on latest upstream main (commit 17a416e).

Addressed must-fix items:

  • resolved merge conflict in packages/vinext/src/cli.ts by keeping both upstream React-upgrade logic and standalone build output support
  • fixed standalone entry runtime consistency by emitting dist/standalone/package.json with type: commonjs
  • switched standalone host env fallback from HOSTNAME to HOST

Addressed key should-fix items:

  • added fallback package resolution when <pkg>/package.json is hidden by exports maps
  • required runtime deps now fail loudly if unresolved (optional deps still skip)
  • improved server import-specifier scanning (from + side-effect imports + dynamic imports + require)
  • added test coverage for exports-hidden package resolution and symlinked node_modules packages
  • added standalone package.json assertion and devDependency-copy rationale comment in tests
  • exported named phase constants and used PHASE_PRODUCTION_BUILD in CLI

Validation run:

  • corepack pnpm vitest run tests/standalone-build.test.ts tests/init.test.ts
  • corepack pnpm exec oxlint packages/vinext/src/build/standalone.ts packages/vinext/src/cli.ts packages/vinext/src/config/next-config.ts tests/standalone-build.test.ts
  • corepack pnpm --filter vinext run build

@sankalpmukim
Copy link
Author

@southpolesteve gentle nudge...

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