vite: prerendering silently emits no HTML when the SSR entry is outside the auto-detected directories
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 55/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- node.js, react, typescript, vite
- Lĩnh vực
- backend, build-system
Hướng nghiên cứu
Bắt đầu với src/build/vite/plugin.ts ở các dòng 427-440 và 198-206, sau đó chạy npm ci && npm run repro trong bản tái hiện được liên kết. Theo dõi cách một entry SSR bị thiếu khiến ctx.services.ssr không được thiết lập và cách prerendering xử lý [404]. Hoàn tất khi hành vi được chọn đã được bao phủ cho một entry không theo chuẩn và tài liệu hoặc chẩn đoán phù hợp với quyết định đó.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Environment
- Nitro:
3.0.260903-beta - Vite:
8.2.2 - React:
19.2.0, Node.js:v24.20.0 - macOS:
26.6.2, arm64 - Minimal React SSR app using
nitro/vitewithprerender.routes: ["/"]. No Nuxt or other framework. - Also inspected Nitro main at
c5177e9218cdd113c6c6a6a74b2924b2354af120; the detection logic below is unchanged there. The reproduction was executed against the published beta listed above.
Reproduction
Ready-to-run repository: https://github.com/cprecioso/nitro-prerender-entry-repro
git clone https://github.com/cprecioso/nitro-prerender-entry-repro.git
cd nitro-prerender-entry-repro
npm ci
npm run repro
The check builds the same app three times, changing only the Vite config, and exits with code 1 while the behaviour reproduces.
The app is a trivial React SSR setup. The SSR entry is still named entry-server.tsx; the only variable is that it sits at src/ssr/ rather than src/.
Describe the bug
Nitro auto-detects the Vite SSR entry by probing for ./entry-server in <rootDir|scanDirs>/{app,src,}/. When the entry is anywhere else, no ssr service is registered and no renderer is wired up, but the build still exits 0. The prerenderer resolves every route to [404], emits no HTML at all, and prints no warning that the app has no renderer.
.output/public/ ends up containing only the client JS chunk, with no HTML entry point, while the build reports success and suggests npx vite preview. Moving the file up one directory, with no other change, prerenders correctly.
Expected: one of
- Nitro warns or errors when there is no renderer and no
ssrservice, rather than emitting a successful build with zero HTML. - Every prerendered route resolving to
[404]is treated as a build failure. - There is a discoverable, documented way to point at an SSR entry in a non-standard location.
Workaround. Declaring the entry as the ssr environment input works:
environments: {
ssr: {
build: { rollupOptions: { input: "src/ssr/entry-server.tsx" } },
},
client: {
build: { rollupOptions: { input: "src/entry-client.tsx" } },
},
},
setupNitroContext skips auto-detection when userConfig.environments.ssr is defined and resolves the env's input as the ssr service instead, after which configResolved still auto-wires renderer.handler to the internal ssr-renderer.
This was hard to arrive at. environments.ssr does appear in the Solid and Vue Router examples, but always pointing at a file named entry-server, and it is introduced there as a framework requirement ("SolidJS requires explicit ssr and client environment configuration") rather than as the way to relocate the SSR entry. docs/1.docs/61.vite.md documents the auto-detection rule and environments.client.build.rollupOptions.input for the client entry, with no ssr counterpart mentioned.
Additional context
Secondary, and possibly the more useful fix: renderer.handler looks like the right option and fails confusingly.
The renderer docs describe renderer.handler as "Path to a custom renderer handler module", so it is the natural thing to reach for when the handler is not auto-detected. Setting renderer: { handler: "src/ssr/entry-server.tsx" } routes the file into Nitro's own rolldown bundle, which has no Vite plugins, so the ?assets= virtual modules (and JSX, and CSS imports) cannot be resolved. It fails identically regardless of where the file lives, including the auto-detected location. Logs below.
It would help if renderer.handler rejected a path belonging to a Vite environment with an explanatory error, or if the docs noted that under the Vite plugin the SSR entry is configured via environments.ssr and never via renderer.handler.
Relevant source:
- Entry auto-detection:
src/build/vite/plugin.ts#L427-L440 - Renderer auto-wiring, which requires
ctx.services.ssr?.entry:src/build/vite/plugin.ts#L198-L206
Happy to open a PR for whichever direction you prefer.
Prepared with AI assistance; the reproduction was executed locally.
Logs
# npx vite build (entry at src/ssr/entry-server.tsx, no environments.ssr)
[nitro] ◐ Building [Nitro] (preset: node-server, compatibility: 2026-09-07)
[nitro] ✔ Generated public .output/public
[nitro] ℹ Initializing prerenderer
[nitro] ℹ Prerendering 1 routes
[nitro] ├─ / (109ms)
│ └── [404]
[nitro] ℹ Prerendered 0 routes in 0.123 seconds
[nitro] ✔ You can preview this build using npx vite preview
$ echo $?
0
$ find .output/public -type f
.output/public/assets/entry-client-Ba_RLnZH.js
# npx vite build --config vite.config.renderer-handler.ts
error during build:
Error: Build failed with 2 errors:
[UNLOADABLE_DEPENDENCY] Could not load src/ssr/entry-server.tsx?assets=ssr
╭─[ src/ssr/entry-server.tsx:7:26 ]
│
7 │ import serverAssets from "./entry-server?assets=ssr";
│ ────────────┬────────────
│ ╰──────────── No such file or directory (os error 2)
───╯
[UNLOADABLE_DEPENDENCY] Could not load src/entry-client.tsx?assets=client
╭─[ src/ssr/entry-server.tsx:6:26 ]
│
6 │ import clientAssets from "../entry-client?assets=client";
│ ───────────────┬───────────────
│ ╰─────────────── No such file or directory (os error 2)
───╯
at aggregateBindingErrorsIntoJsError (node_modules/rolldown/dist/shared/error-BP8lJHle.mjs:48:18)
at async prerender (node_modules/nitro/dist/_chunks/nitro.mjs:743:2)
at async buildEnvironments (node_modules/nitro/dist/vite.mjs:121:2)
- Ngôn ngữ chính
- TypeScript
- Star
- 11.2k
- Fork
- 905
- Merge trung bình
- 1 ngày 4 giờ
- Pull request đã merge (30 ngày)
- 54
Chuẩn bị môi trường
Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của nitrojs/nitro
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
Maintainer thường phản hồi trong vòng 1 ngày
-
pending triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
nitrojs/nitro#4611 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug v2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
nitrojs/nitro#4555 · 5 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
v2 v3
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của nitrojs/nitro
Issue tương tự
-
documentation
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
inu-appcenter/memorIN-frontend#106 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
kind/bug
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
Maintainer thường phản hồi trong vòng 7 ngày
-
[Bug] @deck.gl/arcgis dist import resolves to unpublished @deck.gl/core source path (9.3.11, 9.4.0)Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
fix: CopyFilters ignores tabĐang mởbug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
CSCfi/sd-search-ui#145 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Add: Cbeebies pl SDĐang mởcheck:passed streams:add
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
Maintainer thường phản hồi trong vòng 1 ngày