Skip to content

fix(vite): skip server bundle for static presets - #4509

Open
birkskyum wants to merge 3 commits into
nitrojs:mainfrom
birkskyum:fix/vite-static-preset-server-build
Open

fix(vite): skip server bundle for static presets#4509
birkskyum wants to merge 3 commits into
nitrojs:mainfrom
birkskyum:fix/vite-static-preset-server-build

Conversation

@birkskyum

Copy link
Copy Markdown
Member

🔗 Linked issue

❓ Type of change

  • 📖 Documentation (updates to the documentation, readme, or JSdoc annotations)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

buildEnvironments builds the nitro environment unconditionally. Static presets define no entry, so the environment has no input, Vite falls back to resolving index.html, and rejects it for a server consumer:

rolldownOptions.input should not be an html file when building for SSR. Please specify a dedicated SSR entry.

This affects every static preset (static, github-pages, gitlab-pages, vercel-static, cloudflare-pages-static) on the Vite builder. It fires after prerendering, so .output/public is already complete and correct when the build dies.

Reproduced on current main with the reporter's repro from #3843 (the vite-ssr-vue-router example plus nitro: { preset: "static", prerender: { routes: ["/", "/about"] } }): fails before this change, builds clean afterwards with both routes prerendered and no server bundle.

Why this fix

The rolldown and rollup builders already guard the server build with if (!nitro.options.static) (src/build/rolldown/prod.ts, src/build/rollup/prod.ts); this brings the Vite builder in line. writeBuildInfo already accepts output: ... | undefined, so no signature change was needed.

The environment is also marked as built. Vite's default builder.buildApp is a no-op, and buildApp() treats "no environment was built" as "no plugin handled the build", falling back to building all of them. Without that line, a static build with no other environments hits the same error again, from Vite's own frame.

Two alternatives were tried and rejected:

  • Omit the nitro environment when static. Redirects the fallback at client, which also has no input, giving UNRESOLVED_ENTRY: Cannot resolve entry module index.html. It would also need a !options.dev guard, since dev.ts reads server.environments.nitro.
  • Fix it in Vite instead. Arguably the better long-term shape, since the fallback could skip environments with no resolvable input, or key off whether a buildApp hook was registered rather than off mutated state. But every static preset is broken today, and this guard is correct regardless of what Vite does.

Tests

Adds test/vite/static-preset.test.ts with a minimal fixture, asserting the build succeeds, no server bundle is emitted, and routes are prerendered to the public dir. It fails before this change with the error above.

Reported downstream by SolidStart users building SPAs (solidStart({ ssr: false }) + preset: "static"), where this is currently the only blocker to a server-free static deploy.

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

`buildEnvironments` built the `nitro` environment unconditionally. Static
presets define no entry, so the environment has no input and Vite falls back
to resolving `index.html`, which it then rejects for a server consumer:

    rolldownOptions.input should not be an html file when building for SSR.
    Please specify a dedicated SSR entry.

This affects every static preset (`static`, `github-pages`, `gitlab-pages`,
`vercel-static`, `cloudflare-pages-static`, ...) when using the Vite builder,
and it fires after prerendering, so the output in `.output/public` is already
complete when the build fails.

The rolldown and rollup builders already guard the server build with
`if (!nitro.options.static)`, so this brings the Vite builder in line.

The environment is also marked as built, because `buildApp()` in Vite builds
every environment when none of them were built, which would otherwise
re-trigger the same error for a static build with no other environments.

Closes nitrojs#3843
@birkskyum
birkskyum requested a review from pi0 as a code owner August 7, 2026 01:47
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

@birkskyum is attempting to deploy a commit to the Nitro Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0797e5b0-6ba0-4eb5-96a1-75ef64fd683a

📥 Commits

Reviewing files that changed from the base of the PR and between 30ff965 and a2afe18.

📒 Files selected for processing (1)
  • test/vite/static-preset.test.ts

📝 Walkthrough

Walkthrough

Nitro’s Vite production build skips server bundling for static presets. A fixture and parameterized tests verify prerendered output for four static presets.

Changes

Static Vite build

Layer / File(s) Summary
Static build branching
src/build/vite/prod.ts
Static configurations mark the Nitro environment as built without calling builder.build. Non-static configurations keep bundle building and output capture.
Static preset fixture and validation
test/vite/static-preset-fixture/*, test/vite/static-preset.test.ts
The fixture enables the static preset, prerenders /, and uses the Nitro Vite plugin. Tests verify that no server bundle is emitted and that prerendered HTML is written to each preset’s public directory.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • nitrojs/nitro#3999: Modifies Vite production build output handling in src/build/vite/prod.ts.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits and accurately describes skipping the Vite server bundle for static presets.
Description check ✅ Passed The description explains the Vite static preset failure, the fix, and the added test coverage.
Linked Issues check ✅ Passed The changes address issue #3843 by allowing static Vite builds to complete without a server bundle and by verifying prerendered output.
Out of Scope Changes check ✅ Passed The implementation and tests are directly related to static preset build handling and issue #3843 requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Aug 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/nitro@4509

commit: a2afe18

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
src/build/vite/prod.ts (1)

109-110: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the explanatory comment from the branch.

The comment explains the following assignment. Keep the code self-describing, or move the Vite behavior to documentation.

As per coding guidelines, do not add comments explaining what the line does unless prompted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/build/vite/prod.ts` around lines 109 - 110, Remove the explanatory
comment immediately preceding the static preset handling branch, leaving the
assignment and surrounding behavior unchanged.

Source: Coding guidelines

test/vite/static-preset.test.ts (1)

13-23: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Make the build setup independent of test order.

The tests at Lines 25-32 read outDir, but only the test at Lines 13-23 creates it. If an assertion test runs alone, it fails before checking the behavior. Move cleanup and build to beforeAll, or combine setup and assertions in one test.

Also applies to: 25-32

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/vite/static-preset.test.ts` around lines 13 - 23, Make the shared build
setup in the test suite independent of test order by moving the outDir cleanup,
directory creation, Nitro creation, prepare, and build steps from the "build"
test into a beforeAll hook. Keep the existing assertions in the tests covering
outDir unchanged so they can run independently after the one-time setup.
test/vite/static-preset-fixture/nitro.config.ts (1)

3-5: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Cover all affected static presets.

The PR objectives list static, vercel-static, and cloudflare-pages-static. This fixture hardcodes only static, so the test cannot detect a regression in either alias. Parameterize the fixture or add equivalent cases for the other two presets.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/vite/static-preset-fixture/nitro.config.ts` around lines 3 - 5, Update
the defineConfig fixture to cover all affected presets: static, vercel-static,
and cloudflare-pages-static. Parameterize the preset input or add equivalent
fixture cases while preserving the existing prerender route configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/build/vite/prod.ts`:
- Around line 109-110: Remove the explanatory comment immediately preceding the
static preset handling branch, leaving the assignment and surrounding behavior
unchanged.

In `@test/vite/static-preset-fixture/nitro.config.ts`:
- Around line 3-5: Update the defineConfig fixture to cover all affected
presets: static, vercel-static, and cloudflare-pages-static. Parameterize the
preset input or add equivalent fixture cases while preserving the existing
prerender route configuration.

In `@test/vite/static-preset.test.ts`:
- Around line 13-23: Make the shared build setup in the test suite independent
of test order by moving the outDir cleanup, directory creation, Nitro creation,
prepare, and build steps from the "build" test into a beforeAll hook. Keep the
existing assertions in the tests covering outDir unchanged so they can run
independently after the one-time setup.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3516be55-43f1-4e10-8e8c-6ab1edd81b06

📥 Commits

Reviewing files that changed from the base of the PR and between 52abde8 and f0eb4e7.

📒 Files selected for processing (5)
  • src/build/vite/prod.ts
  • test/vite/static-preset-fixture/nitro.config.ts
  • test/vite/static-preset-fixture/server.ts
  • test/vite/static-preset-fixture/vite.config.ts
  • test/vite/static-preset.test.ts

Address review feedback:

- Drop the comment above the `isBuilt` assignment, per AGENTS.md. The
  reasoning stays in the PR description.
- Move the build into `beforeAll` so each case is independent of test order.
- Parameterize over `static`, `github-pages`, `vercel-static` and
  `cloudflare-pages-static`, each with its own output layout.

`netlify-static` is left out because it writes outside the configured output
dir, which would make the test depend on the fixture directory state.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/vite/static-preset.test.ts`:
- Around line 24-31: Wrap the setup and build flow after createNitro in a
try/finally block, and invoke nitro.close() in the finally block so every preset
build releases its hooks and resources even when prepare or build fails.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 09ce518d-9ec5-4fd4-b1be-0e74c4ab7e6b

📥 Commits

Reviewing files that changed from the base of the PR and between f0eb4e7 and 30ff965.

📒 Files selected for processing (2)
  • src/build/vite/prod.ts
  • test/vite/static-preset.test.ts
💤 Files with no reviewable changes (1)
  • src/build/vite/prod.ts

Comment thread test/vite/static-preset.test.ts Outdated
`build()` closes the instance on the vite path, but a throw from `prepare` or
`build` left the env runner open and the instance registered in `nitroInstances`.
moreal added a commit to moreal/lean-korean-wordle-solver that referenced this pull request Aug 8, 2026
Replace web/ (raw HTML/JS: emitjs.html, index.html, app.js) with a
SolidStart 2.0 (SolidJS) app, ported 1:1 from the old UI and verified
against it with a scripted browser harness: identical DOM behavior and
pixel output (only the footer's load-time figure differs), clean
hydration, and a working /<repo>/ subpath deploy.

- Yarn berry via corepack with nodeLinker: pnpm (no PnP); Node >= 24
  (SolidStart 2.0 requirement; .node-version + engines pin it).
- The emitted Lean module stays out of the bundle: public/emitjs/ is
  loaded at runtime, so re-emitting Lean needs no frontend rebuild.
  New package scripts wrap the pipeline: yarn emit (scripts/emit-web.sh),
  yarn fetch-words.
- Two upstream 2.0 gaps are worked around: nitro v3's static presets
  crash the Vite builder (nitrojs/nitro#4509), so build with no preset
  and deploy .output/public only; and the SSR manifest hardcodes
  root-absolute asset URLs, so scripts/postbuild.mjs rewrites them
  relative, which also removes the need for BASE_PATH injection.
- Remove the Std.Http server demo entirely (WebMain.lean, kwordle-web
  target, README section); the pure logic in KoreanWordle/Api.lean
  stays and keeps serving WebJS.lean.
- Deploy workflow now builds the app (Node 24 + corepack yarn) and
  uploads its static output.

Co-Authored-By: Claude <noreply@anthropic.com>
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.

Static preset doesn't work [Nitro 3.0.1-alpha.1]

1 participant