Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

nitro with vite preview#5983

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
birkskyum wants to merge8 commits intomain
base:main
Choose a base branch
Loading
fromnitro-with-vite-preview
Draft

Conversation

@birkskyum
Copy link
Member

@birkskyumbirkskyum commentedNov 27, 2025
edited
Loading

Note

Awaiting fetch handler in nitro v3

Caution

Not compatible with nitro v2 plugin

Adds Nitro v3 support for prerendering using vite.preview()

  • Add prerender-nitro.ts - prerendering implementation using vite.preview() for Nitro builds
  • Register a Nitro module via the plugin.nitrointerop (Nitrov3.0.1-alpha.1 feature, sowould break for any prior releases incl. v2)
  • Skip default prerendering when Nitro is detected, defer to post-Nitro-build hook

Add e2e tests for Nitro integration:

  • react-start/basic-nitro - SSR + prerendering with Nitro
  • react-start/basic-nitro-spa - SPA shell prerendering with Nitro
  • solid-start/basic-nitro - SSR + prerendering with Nitro
  • solid-start/basic-nitro-spa - SPA shell prerendering with Nitro

closes#5967

might close

Summary by CodeRabbit

  • New Features

    • New React and Solid e2e templates: Nitro-powered SPA/SSR with app shell, routing, error & not-found UIs, SEO helper, PWA manifest, and global styling.
  • Core

    • Core plugin now integrates Nitro-aware prerender support and emits Nitro build info to enable correct preview/prerender flows.
  • Tests

    • Playwright e2e suites added to verify prerendering, server/runtime responses, and client-side navigation.
  • Chores

    • Project configs, package manifests, build/test scripts, PostCSS/Vite/Nitro setup, and ignore rules added.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitaibot commentedNov 27, 2025
edited
Loading

Walkthrough

Adds multiple e2e example projects (React & Solid; SPA and Nitro variants) with routes, components, configs, and Playwright tests; and extends start-plugin-core to detect Nitro and support Nitro-backed prerendering alongside Vite preview.

Changes

Cohort / File(s)Summary
React Start SPA example
e2e/react-start/basic-nitro-spa/.gitignore,e2e/react-start/basic-nitro-spa/package.json,e2e/react-start/basic-nitro-spa/playwright.config.ts,e2e/react-start/basic-nitro-spa/postcss.config.mjs,e2e/react-start/basic-nitro-spa/public/site.webmanifest,e2e/react-start/basic-nitro-spa/tsconfig.json,e2e/react-start/basic-nitro-spa/vite.config.ts
New SPA project scaffolding: package manifest, Playwright config, PostCSS/Tailwind config, manifest, TS config, Vite config wired to TanStack Start and Nitro.
React Start SPA app code & tests
e2e/react-start/basic-nitro-spa/src/routes/__root.tsx,.../src/routes/index.tsx,.../src/routes/static.tsx,.../src/components/DefaultCatchBoundary.tsx,.../src/components/NotFound.tsx,.../src/router.tsx,.../src/routeTree.gen.ts,.../src/styles/app.css,.../src/utils/seo.ts,.../tests/app.spec.ts
Adds file-based routes, generated route tree/types, router factory, error/not-found components, SEO util, styles, and Playwright e2e tests for SPA prerender and client navigation.
React Start Nitro example
e2e/react-start/basic-nitro/.gitignore,e2e/react-start/basic-nitro/.prettierignore,e2e/react-start/basic-nitro/package.json,e2e/react-start/basic-nitro/playwright.config.ts,e2e/react-start/basic-nitro/postcss.config.mjs,e2e/react-start/basic-nitro/public/site.webmanifest,e2e/react-start/basic-nitro/tsconfig.json,e2e/react-start/basic-nitro/vite.config.ts
Full-stack Nitro example scaffolding with Playwright config, Vite/Nitro integration, and formatting ignores for generated files.
React Start Nitro app code & tests
e2e/react-start/basic-nitro/src/routes/__root.tsx,.../src/routes/index.tsx,.../src/routes/static.tsx,.../src/components/DefaultCatchBoundary.tsx,.../src/components/NotFound.tsx,.../src/router.tsx,.../src/routeTree.gen.ts,.../src/styles/app.css,.../src/utils/seo.ts,.../tests/app.spec.ts,.../tests/setup/global.setup.ts,.../tests/setup/global.teardown.ts
Adds Nitro-backed routes, generated route tree with typings, router factory, components, styles, SEO util, Playwright tests validating Nitro runtime and prerendered assets, plus test setup/teardown placeholders.
Solid Start SPA example
e2e/solid-start/basic-nitro-spa/.gitignore,e2e/solid-start/basic-nitro-spa/package.json,e2e/solid-start/basic-nitro-spa/playwright.config.ts,e2e/solid-start/basic-nitro-spa/postcss.config.mjs,e2e/solid-start/basic-nitro-spa/public/site.webmanifest,e2e/solid-start/basic-nitro-spa/tsconfig.json,e2e/solid-start/basic-nitro-spa/vite.config.ts
New Solid Start SPA project scaffolding: package manifest, Playwright config, PostCSS/Tailwind, TS config, and Vite + Solid plugin wiring.
Solid Start SPA app code & tests
e2e/solid-start/basic-nitro-spa/src/routes/__root.tsx,.../src/routes/index.tsx,.../src/routes/static.tsx,.../src/components/DefaultCatchBoundary.tsx,.../src/components/NotFound.tsx,.../src/router.tsx,.../src/routeTree.gen.ts,.../src/styles/app.css,.../src/utils/seo.ts,.../tests/app.spec.ts
Adds Solid file-based routes, generated route tree/types, router factory, error/not-found components, SEO util, styles, and Playwright e2e tests for SPA prerender/navigation.
Solid Start Nitro example
e2e/solid-start/basic-nitro/.gitignore,e2e/solid-start/basic-nitro/.prettierignore,e2e/solid-start/basic-nitro/package.json,e2e/solid-start/basic-nitro/playwright.config.ts,e2e/solid-start/basic-nitro/postcss.config.mjs,e2e/solid-start/basic-nitro/public/site.webmanifest,e2e/solid-start/basic-nitro/tsconfig.json,e2e/solid-start/basic-nitro/vite.config.ts
Full-stack Solid + Nitro example scaffolding with Playwright config, Vite/Nitro plugin setup, and Prettier ignore for generated route trees.
Solid Start Nitro app code & tests
e2e/solid-start/basic-nitro/src/routes/__root.tsx,.../src/routes/index.tsx,.../src/routes/static.tsx,.../src/components/DefaultCatchBoundary.tsx,.../src/components/NotFound.tsx,.../src/router.tsx,.../src/routeTree.gen.ts,.../src/styles/app.css,.../src/utils/seo.ts,.../tests/app.spec.ts
Adds Solid Nitro routes, generated route tree with type augmentations, router factory, components, styles, SEO helper, and Playwright tests for prerendered static pages and client navigation.
Start Plugin Core — prerender & Nitro integration
packages/start-plugin-core/src/plugin.ts,packages/start-plugin-core/src/post-server-build.ts,packages/start-plugin-core/src/prerender.ts
AddsviteConfigFile to resolved config and configResolved hook; detects Nitro plugin presence; introduces Nitro-specific prerender plugin andpostServerBuildForNitro; refactors postServerBuild to accept skipPrerender; updates prerender to optionally start a Nitro subprocess (startNitroServer) or use Vite preview, plus improved redirect/cleanup handling and nitro.json write-out.

Sequence Diagram(s)

mermaid
sequenceDiagram
autonumber
participant Vite as Vite (build/config)
participant Plugin as tanstack-start-core plugin
participant Nitro as Nitro server (child process)
participant Prerender as Prerender worker
participant FS as Filesystem (.output)

Vite->>Plugin: configResolved (configFile)Vite->>Plugin: build finishedPlugin->>Plugin: detect Nitro plugin (plugin.name startsWith "nitro:")Plugin->>Prerender: invoke postServerBuildForNitro(startConfig, outputDir, configFile)Prerender->>Nitro: startNitroServer(nitroServerPath, PORT)Nitro-->>Prerender: ready (base URL)Prerender->>Nitro: crawl pages (HTTP requests)Prerender->>FS: write prerendered files into outputDirPrerender->>Nitro: close()Prerender-->>Plugin: prerender complete / sitemap generation

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • packages/start-plugin-core/src/prerender.ts — Nitro subprocess orchestration, waitForServerReady, redirect handling, cleanup.
    • packages/start-plugin-core/src/plugin.ts — configResolved hook, viteConfigFile propagation, Nitro detection and plugin lifecycle changes.
    • packages/start-plugin-core/src/post-server-build.ts — postServerBuildForNitro, skipPrerender propagation, nitro.json write-out.
    • Generated routeTree.gen.ts files across examples — type augmentations, consistent IDs/parent relationships.
    • Playwright webServer configs in e2e examples — port resolution, reuseExistingServer behavior, and server start commands.

Possibly related PRs

Suggested labels

ssr

Suggested reviewers

  • schiller-manuel

Poem

🐰 I hopped through routes and built a plot,
Brought Nitro warmth and prerendered lot.
React and Solid, tests all in a row,
Pages pre-made, and devtools a-glow.
Hooray — carrots for everyone, let's go! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 2.94% which is insufficient. The required threshold is 80.00%.You can run@coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'nitro with vite preview' directly refers to the main purpose of the PR, which adds Nitro v3 support for prerendering using vite.preview().
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branchnitro-with-vite-preview

Thanks for usingCodeRabbit! 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 and usage tips.

morellodev reacted with heart emoji

@nx-cloud
Copy link

nx-cloudbot commentedNov 27, 2025
edited
Loading

View yourCI Pipeline Execution ↗ for commitb17e4b6

CommandStatusDurationResult
nx affected --targets=test:eslint,test:unit,tes...✅ Succeeded7m 27sView ↗
nx run-many --target=build --exclude=examples/*...✅ Succeeded18sView ↗

☁️Nx Cloud last updated this comment at2025-11-28 01:41:56 UTC

@pkg-pr-new
Copy link

pkg-pr-newbot commentedNov 27, 2025
edited
Loading

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@5983

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@5983

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@5983

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@5983

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/nitro-v2-vite-plugin@5983

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@5983

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@5983

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@5983

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@5983

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@5983

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@5983

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@5983

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@5983

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@5983

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@5983

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@5983

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@5983

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@5983

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@5983

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@5983

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@5983

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@5983

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@5983

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-ssr-query@5983

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@5983

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@5983

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@5983

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@5983

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@5983

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@5983

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-static-server-functions@5983

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@5983

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@5983

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@5983

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@5983

commit:b17e4b6

@birkskyumbirkskyum marked this pull request as ready for reviewNovember 28, 2025 00:08
Copy link
Contributor

@coderabbitaicoderabbitaibot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (17)
packages/start-plugin-core/src/prerender-nitro.ts (4)

28-82:Consider extracting shared prerender configuration logic.

The prerender configuration setup (lines 28-61) duplicates logic frompost-server-build.ts. Consider extracting a shared helper to reduce maintenance burden:

// In a shared utility file:exportfunctionsetupPrerenderConfig(startConfig:TanStackStartOutputConfig):void{// Shared prerender.enabled and SPA page logic}

This would centralize the prerender setup logic and prevent divergence between the standard and Nitro prerender paths.


86-101:Hardcoded Nitro version may become stale.

Theversions: { nitro: '3.0.0' } on line 94 is hardcoded. Consider deriving this from the actual Nitro package version or making it configurable to prevent the metadata from becoming misleading over time.


151-162:Hardcoded shutdown timeout may be insufficient or excessive.

The 3500ms wait on line 157 is arbitrary. Consider:

  1. Making this configurable viastartConfig.prerender.shutdownTimeout
  2. Or implementing a more robust shutdown detection (e.g., polling for process termination)

The current approach may fail on slower systems or waste time on faster ones.


165-190:Consider failing instead of proceeding when server isn't ready.

On line 187-189, the code logs a warning but proceeds when the server doesn't become ready. This could lead to confusing downstream failures during prerendering. Consider making this a hard failure or at least configurable:

-  logger.warn(-    `Server may not be fully ready after ${maxAttempts * delay}ms, proceeding anyway...`,-  )+  throw new Error(+    `Server did not become ready after ${maxAttempts * delay}ms. Check that the Nitro build completed successfully.`,+  )
e2e/solid-start/basic-nitro/src/styles/app.css (1)

27-29:Confirm.using-mouse focus handling to avoid a11y regressions

Removing outlines with.using-mouse * { outline: none !important; } can hide focus indicators if that class is ever present for keyboard users. Make sure the.using-mouse class is only applied for true pointer/mouse interaction, or consider narrowing the selector (e.g., to specific components) so keyboard focus remains clearly visible.

e2e/solid-start/basic-nitro-spa/src/components/NotFound.tsx (1)

1-25:Tightenchildren typing instead of usingany

To keep TS strictness and avoidany, you can typechildren using Solid’s JSX types:

-import { Link } from '@tanstack/solid-router'+import type { JSX } from 'solid-js'+import { Link } from '@tanstack/solid-router'-export function NotFound({ children }: { children?: any }) {+export function NotFound({ children }: { children?: JSX.Element }) {

This keeps the component strongly typed without changing behavior.

e2e/react-start/basic-nitro/src/components/NotFound.tsx (1)

1-25:UseReactNode instead ofany forchildren

For stricter typing, you can avoidany by typingchildren as a React node:

-import { Link } from '@tanstack/react-router'+import type { ReactNode } from 'react'+import { Link } from '@tanstack/react-router'-export function NotFound({ children }: { children?: any }) {+export function NotFound({ children }: { children?: ReactNode }) {

This keeps the component API the same but aligns better with TypeScript strict mode.

e2e/solid-start/basic-nitro-spa/src/utils/seo.ts (1)

1-33:Consider filtering out metas with empty/undefined content

description andkeywords are optional but their metas are always emitted, so you may end up with tags like<meta name="description" content="undefined"> if callers omit them. A small filter pass overtags (e.g. dropping entries withcontent == null or empty string) would keep the head cleaner, especially in tests that validate meta tags.

e2e/solid-start/basic-nitro/src/routes/__root.tsx (1)

16-94:MoveHeadContent into<head> and consider addinglang on<html>

Right now<HeadContent /> is rendered inside<body>, while it’s intended to manage<head> tags. Moving it into the<head> block (alongsideHydrationScript) will produce more correct HTML and better reflect recommended usage. You may also want to add alang attribute on<html> (e.g.<html lang="en">) for accessibility/SEO completeness in this reference fixture.

e2e/solid-start/basic-nitro-spa/src/styles/app.css (1)

1-30:Be mindful of focus outlines with.using-mouse helper

The.using-mouse * { outline: none !important; } rule is fine if you reliably toggle.using-mouse only for true mouse interaction. Just ensure keyboard users never land in a state where focus outlines are suppressed, or consider scoping this more narrowly if you reuse the pattern beyond tests.

e2e/react-start/basic-nitro-spa/src/utils/seo.ts (1)

12-30:Consider filtering tags with undefined content values.

When optional parameters (description,keywords) are not provided, the tags array will include entries withcontent: undefined. Depending on how the meta tags are rendered, this could result in attributes likecontent="undefined" in the HTML.

Apply this refactor to filter out undefined values:

 export const seo = ({   title,   description,   keywords,   image, }: {   title: string   description?: string   image?: string   keywords?: string }) => {   const tags = [     { title },-    { name: 'description', content: description },-    { name: 'keywords', content: keywords },+    ...(description ? [{ name: 'description', content: description }] : []),+    ...(keywords ? [{ name: 'keywords', content: keywords }] : []),     { name: 'twitter:title', content: title },-    { name: 'twitter:description', content: description },+    ...(description ? [{ name: 'twitter:description', content: description }] : []),     { name: 'twitter:creator', content: '@tannerlinsley' },     { name: 'twitter:site', content: '@tannerlinsley' },     { name: 'og:type', content: 'website' },     { name: 'og:title', content: title },-    { name: 'og:description', content: description },+    ...(description ? [{ name: 'og:description', content: description }] : []),     ...(image       ? [           { name: 'twitter:image', content: image },           { name: 'twitter:card', content: 'summary_large_image' },           { name: 'og:image', content: image },         ]       : []),   ]   return tags }
e2e/react-start/basic-nitro/src/utils/seo.ts (1)

1-33:Note: Code duplication across e2e fixtures.

This SEO utility is duplicated across multiple e2e test directories (basic-nitro andbasic-nitro-spa). While this keeps each test self-contained, it creates maintenance overhead if changes are needed.

The same undefined content value issue noted ine2e/react-start/basic-nitro-spa/src/utils/seo.ts applies here.

e2e/solid-start/basic-nitro-spa/src/routes/__root.tsx (1)

31-34:Consider adding manifest link.

The project includes asite.webmanifest file, but the root route'slinks array doesn't reference it. While this won't break functionality, including the manifest link would complete the PWA setup.

 links: [   { rel: 'stylesheet', href: appCss },   { rel: 'icon', href: '/favicon.ico' },+  { rel: 'manifest', href: '/site.webmanifest' }, ],
e2e/react-start/basic-nitro-spa/src/routes/__root.tsx (1)

30-33:Consider adding manifest link.

Similar to the Solid Start version, this root route could reference thesite.webmanifest file to complete the PWA configuration.

 links: [   { rel: 'stylesheet', href: appCss },   { rel: 'icon', href: '/favicon.ico' },+  { rel: 'manifest', href: '/site.webmanifest' }, ],
e2e/solid-start/basic-nitro/src/routes/index.tsx (1)

4-26:Index route + server function are wired correctly

The file-based route,createServerFn usage, andRoute.useLoaderData() pattern all look correct for Solid Start + Nitro. If you want to trim a tiny bit of boilerplate, you could change the route definition to:

exportconstRoute=createFileRoute('/')({loader:getData,component:Home,})

but the current version is perfectly fine.

e2e/solid-start/basic-nitro-spa/src/routes/static.tsx (1)

4-26:Static route is well-structured for prerender tests

The/static route cleanly separates the server function (getStaticData) from the component and exposes stable selectors viadata-testid for Playwright. As with the index route, you could inlineloader: getStaticData instead of() => getStaticData(), but the current implementation is already clear.

e2e/react-start/basic-nitro/src/routes/__root.tsx (1)

15-92:Root route + shell are correctly wired; optional hex color tweak

The root route setup (head metadata, error/not-found components, andRootDocument shell) looks correct and consistent with other React Start examples. One tiny nit: if thatcolor field on the manifest link is meant to be a hex color, you probably want'#ffffff' instead of'#fffff', though it won’t affect the tests.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and betweenaf02265 and8162cc6.

⛔ Files ignored due to path filters (29)
  • e2e/react-start/basic-nitro-spa/public/android-chrome-192x192.png is excluded by!**/*.png
  • e2e/react-start/basic-nitro-spa/public/android-chrome-512x512.png is excluded by!**/*.png
  • e2e/react-start/basic-nitro-spa/public/apple-touch-icon.png is excluded by!**/*.png
  • e2e/react-start/basic-nitro-spa/public/favicon-16x16.png is excluded by!**/*.png
  • e2e/react-start/basic-nitro-spa/public/favicon-32x32.png is excluded by!**/*.png
  • e2e/react-start/basic-nitro-spa/public/favicon.ico is excluded by!**/*.ico
  • e2e/react-start/basic-nitro-spa/public/favicon.png is excluded by!**/*.png
  • e2e/react-start/basic-nitro/public/android-chrome-192x192.png is excluded by!**/*.png
  • e2e/react-start/basic-nitro/public/android-chrome-512x512.png is excluded by!**/*.png
  • e2e/react-start/basic-nitro/public/apple-touch-icon.png is excluded by!**/*.png
  • e2e/react-start/basic-nitro/public/favicon-16x16.png is excluded by!**/*.png
  • e2e/react-start/basic-nitro/public/favicon-32x32.png is excluded by!**/*.png
  • e2e/react-start/basic-nitro/public/favicon.ico is excluded by!**/*.ico
  • e2e/react-start/basic-nitro/public/favicon.png is excluded by!**/*.png
  • e2e/solid-start/basic-nitro-spa/public/android-chrome-192x192.png is excluded by!**/*.png
  • e2e/solid-start/basic-nitro-spa/public/android-chrome-512x512.png is excluded by!**/*.png
  • e2e/solid-start/basic-nitro-spa/public/apple-touch-icon.png is excluded by!**/*.png
  • e2e/solid-start/basic-nitro-spa/public/favicon-16x16.png is excluded by!**/*.png
  • e2e/solid-start/basic-nitro-spa/public/favicon-32x32.png is excluded by!**/*.png
  • e2e/solid-start/basic-nitro-spa/public/favicon.ico is excluded by!**/*.ico
  • e2e/solid-start/basic-nitro-spa/public/favicon.png is excluded by!**/*.png
  • e2e/solid-start/basic-nitro/public/android-chrome-192x192.png is excluded by!**/*.png
  • e2e/solid-start/basic-nitro/public/android-chrome-512x512.png is excluded by!**/*.png
  • e2e/solid-start/basic-nitro/public/apple-touch-icon.png is excluded by!**/*.png
  • e2e/solid-start/basic-nitro/public/favicon-16x16.png is excluded by!**/*.png
  • e2e/solid-start/basic-nitro/public/favicon-32x32.png is excluded by!**/*.png
  • e2e/solid-start/basic-nitro/public/favicon.ico is excluded by!**/*.ico
  • e2e/solid-start/basic-nitro/public/favicon.png is excluded by!**/*.png
  • pnpm-lock.yaml is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (75)
  • e2e/react-start/basic-nitro-spa/.gitignore (1 hunks)
  • e2e/react-start/basic-nitro-spa/package.json (1 hunks)
  • e2e/react-start/basic-nitro-spa/playwright.config.ts (1 hunks)
  • e2e/react-start/basic-nitro-spa/postcss.config.mjs (1 hunks)
  • e2e/react-start/basic-nitro-spa/public/site.webmanifest (1 hunks)
  • e2e/react-start/basic-nitro-spa/src/components/DefaultCatchBoundary.tsx (1 hunks)
  • e2e/react-start/basic-nitro-spa/src/components/NotFound.tsx (1 hunks)
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts (1 hunks)
  • e2e/react-start/basic-nitro-spa/src/router.tsx (1 hunks)
  • e2e/react-start/basic-nitro-spa/src/routes/__root.tsx (1 hunks)
  • e2e/react-start/basic-nitro-spa/src/routes/index.tsx (1 hunks)
  • e2e/react-start/basic-nitro-spa/src/routes/static.tsx (1 hunks)
  • e2e/react-start/basic-nitro-spa/src/styles/app.css (1 hunks)
  • e2e/react-start/basic-nitro-spa/src/utils/seo.ts (1 hunks)
  • e2e/react-start/basic-nitro-spa/tests/app.spec.ts (1 hunks)
  • e2e/react-start/basic-nitro-spa/tsconfig.json (1 hunks)
  • e2e/react-start/basic-nitro-spa/vite.config.ts (1 hunks)
  • e2e/react-start/basic-nitro/.gitignore (1 hunks)
  • e2e/react-start/basic-nitro/.prettierignore (1 hunks)
  • e2e/react-start/basic-nitro/package.json (1 hunks)
  • e2e/react-start/basic-nitro/playwright.config.ts (1 hunks)
  • e2e/react-start/basic-nitro/postcss.config.mjs (1 hunks)
  • e2e/react-start/basic-nitro/public/site.webmanifest (1 hunks)
  • e2e/react-start/basic-nitro/src/components/DefaultCatchBoundary.tsx (1 hunks)
  • e2e/react-start/basic-nitro/src/components/NotFound.tsx (1 hunks)
  • e2e/react-start/basic-nitro/src/routeTree.gen.ts (1 hunks)
  • e2e/react-start/basic-nitro/src/router.tsx (1 hunks)
  • e2e/react-start/basic-nitro/src/routes/__root.tsx (1 hunks)
  • e2e/react-start/basic-nitro/src/routes/index.tsx (1 hunks)
  • e2e/react-start/basic-nitro/src/routes/static.tsx (1 hunks)
  • e2e/react-start/basic-nitro/src/styles/app.css (1 hunks)
  • e2e/react-start/basic-nitro/src/utils/seo.ts (1 hunks)
  • e2e/react-start/basic-nitro/tests/app.spec.ts (1 hunks)
  • e2e/react-start/basic-nitro/tests/setup/global.setup.ts (1 hunks)
  • e2e/react-start/basic-nitro/tests/setup/global.teardown.ts (1 hunks)
  • e2e/react-start/basic-nitro/tsconfig.json (1 hunks)
  • e2e/react-start/basic-nitro/vite.config.ts (1 hunks)
  • e2e/solid-start/basic-nitro-spa/.gitignore (1 hunks)
  • e2e/solid-start/basic-nitro-spa/package.json (1 hunks)
  • e2e/solid-start/basic-nitro-spa/playwright.config.ts (1 hunks)
  • e2e/solid-start/basic-nitro-spa/postcss.config.mjs (1 hunks)
  • e2e/solid-start/basic-nitro-spa/public/site.webmanifest (1 hunks)
  • e2e/solid-start/basic-nitro-spa/src/components/DefaultCatchBoundary.tsx (1 hunks)
  • e2e/solid-start/basic-nitro-spa/src/components/NotFound.tsx (1 hunks)
  • e2e/solid-start/basic-nitro-spa/src/routeTree.gen.ts (1 hunks)
  • e2e/solid-start/basic-nitro-spa/src/router.tsx (1 hunks)
  • e2e/solid-start/basic-nitro-spa/src/routes/__root.tsx (1 hunks)
  • e2e/solid-start/basic-nitro-spa/src/routes/index.tsx (1 hunks)
  • e2e/solid-start/basic-nitro-spa/src/routes/static.tsx (1 hunks)
  • e2e/solid-start/basic-nitro-spa/src/styles/app.css (1 hunks)
  • e2e/solid-start/basic-nitro-spa/src/utils/seo.ts (1 hunks)
  • e2e/solid-start/basic-nitro-spa/tests/app.spec.ts (1 hunks)
  • e2e/solid-start/basic-nitro-spa/tsconfig.json (1 hunks)
  • e2e/solid-start/basic-nitro-spa/vite.config.ts (1 hunks)
  • e2e/solid-start/basic-nitro/.gitignore (1 hunks)
  • e2e/solid-start/basic-nitro/.prettierignore (1 hunks)
  • e2e/solid-start/basic-nitro/package.json (1 hunks)
  • e2e/solid-start/basic-nitro/playwright.config.ts (1 hunks)
  • e2e/solid-start/basic-nitro/postcss.config.mjs (1 hunks)
  • e2e/solid-start/basic-nitro/public/site.webmanifest (1 hunks)
  • e2e/solid-start/basic-nitro/src/components/DefaultCatchBoundary.tsx (1 hunks)
  • e2e/solid-start/basic-nitro/src/components/NotFound.tsx (1 hunks)
  • e2e/solid-start/basic-nitro/src/routeTree.gen.ts (1 hunks)
  • e2e/solid-start/basic-nitro/src/router.tsx (1 hunks)
  • e2e/solid-start/basic-nitro/src/routes/__root.tsx (1 hunks)
  • e2e/solid-start/basic-nitro/src/routes/index.tsx (1 hunks)
  • e2e/solid-start/basic-nitro/src/routes/static.tsx (1 hunks)
  • e2e/solid-start/basic-nitro/src/styles/app.css (1 hunks)
  • e2e/solid-start/basic-nitro/src/utils/seo.ts (1 hunks)
  • e2e/solid-start/basic-nitro/tests/app.spec.ts (1 hunks)
  • e2e/solid-start/basic-nitro/tsconfig.json (1 hunks)
  • e2e/solid-start/basic-nitro/vite.config.ts (1 hunks)
  • packages/start-plugin-core/src/plugin.ts (1 hunks)
  • packages/start-plugin-core/src/post-server-build.ts (2 hunks)
  • packages/start-plugin-core/src/prerender-nitro.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety throughout the codebase

Files:

  • e2e/react-start/basic-nitro/tests/setup/global.teardown.ts
  • e2e/react-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/tests/app.spec.ts
  • e2e/react-start/basic-nitro-spa/src/components/NotFound.tsx
  • e2e/react-start/basic-nitro/playwright.config.ts
  • e2e/solid-start/basic-nitro/src/components/NotFound.tsx
  • e2e/react-start/basic-nitro-spa/src/components/DefaultCatchBoundary.tsx
  • e2e/react-start/basic-nitro/src/components/NotFound.tsx
  • e2e/react-start/basic-nitro-spa/playwright.config.ts
  • e2e/react-start/basic-nitro/src/router.tsx
  • e2e/react-start/basic-nitro/src/routes/static.tsx
  • e2e/react-start/basic-nitro-spa/src/router.tsx
  • e2e/react-start/basic-nitro/src/components/DefaultCatchBoundary.tsx
  • e2e/react-start/basic-nitro-spa/vite.config.ts
  • e2e/solid-start/basic-nitro-spa/src/components/DefaultCatchBoundary.tsx
  • e2e/solid-start/basic-nitro-spa/src/utils/seo.ts
  • packages/start-plugin-core/src/post-server-build.ts
  • e2e/solid-start/basic-nitro-spa/vite.config.ts
  • e2e/solid-start/basic-nitro/tests/app.spec.ts
  • e2e/solid-start/basic-nitro/src/components/DefaultCatchBoundary.tsx
  • e2e/solid-start/basic-nitro-spa/src/routes/index.tsx
  • e2e/solid-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/solid-start/basic-nitro/src/utils/seo.ts
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/react-start/basic-nitro/src/utils/seo.ts
  • e2e/solid-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/solid-start/basic-nitro-spa/src/router.tsx
  • e2e/react-start/basic-nitro-spa/tests/app.spec.ts
  • e2e/solid-start/basic-nitro/src/routes/index.tsx
  • e2e/solid-start/basic-nitro/src/routes/static.tsx
  • e2e/solid-start/basic-nitro/playwright.config.ts
  • e2e/solid-start/basic-nitro/src/router.tsx
  • packages/start-plugin-core/src/prerender-nitro.ts
  • e2e/solid-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/solid-start/basic-nitro-spa/src/components/NotFound.tsx
  • e2e/react-start/basic-nitro-spa/src/routes/index.tsx
  • e2e/solid-start/basic-nitro-spa/playwright.config.ts
  • e2e/solid-start/basic-nitro/vite.config.ts
  • e2e/react-start/basic-nitro/src/routes/index.tsx
  • e2e/react-start/basic-nitro-spa/src/utils/seo.ts
  • e2e/solid-start/basic-nitro-spa/tests/app.spec.ts
  • e2e/solid-start/basic-nitro/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro/vite.config.ts
  • e2e/solid-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/tests/setup/global.setup.ts
  • packages/start-plugin-core/src/plugin.ts
  • e2e/react-start/basic-nitro/src/routeTree.gen.ts
**/package.json

📄 CodeRabbit inference engine (AGENTS.md)

Use workspace protocol for internal dependencies (workspace:*)

Files:

  • e2e/react-start/basic-nitro/package.json
  • e2e/react-start/basic-nitro-spa/package.json
  • e2e/solid-start/basic-nitro/package.json
  • e2e/solid-start/basic-nitro-spa/package.json
**/src/routes/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use file-based routing in src/routes/ directories or code-based routing with route definitions

Files:

  • e2e/react-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routes/static.tsx
  • e2e/solid-start/basic-nitro-spa/src/routes/index.tsx
  • e2e/solid-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/solid-start/basic-nitro/src/routes/index.tsx
  • e2e/solid-start/basic-nitro/src/routes/static.tsx
  • e2e/solid-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/react-start/basic-nitro-spa/src/routes/index.tsx
  • e2e/react-start/basic-nitro/src/routes/index.tsx
  • e2e/solid-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routes/__root.tsx
🧠 Learnings (11)
📚 Learning: 2025-11-25T00:18:21.282Z
Learnt from: CRRepo: TanStack/router PR: 0File: AGENTS.md:0-0Timestamp: 2025-11-25T00:18:21.282ZLearning: Applies to packages/react-router/**/*.{ts,tsx} : React Router components and hooks should use the tanstack/react-router package

Applied to files:

  • e2e/react-start/basic-nitro/package.json
  • e2e/react-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro-spa/src/components/NotFound.tsx
  • e2e/react-start/basic-nitro-spa/src/components/DefaultCatchBoundary.tsx
  • e2e/react-start/basic-nitro-spa/package.json
  • e2e/react-start/basic-nitro/src/components/NotFound.tsx
  • e2e/react-start/basic-nitro/src/router.tsx
  • e2e/react-start/basic-nitro/src/routes/static.tsx
  • e2e/react-start/basic-nitro-spa/src/router.tsx
  • e2e/react-start/basic-nitro/src/components/DefaultCatchBoundary.tsx
  • e2e/solid-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/solid-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/solid-start/basic-nitro-spa/src/router.tsx
  • e2e/solid-start/basic-nitro/src/routes/index.tsx
  • e2e/solid-start/basic-nitro/src/router.tsx
  • e2e/react-start/basic-nitro-spa/src/routes/index.tsx
  • e2e/react-start/basic-nitro/src/routes/index.tsx
  • e2e/solid-start/basic-nitro/src/routeTree.gen.ts
  • e2e/solid-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routeTree.gen.ts
📚 Learning: 2025-11-02T16:16:24.898Z
Learnt from: nlynzaadRepo: TanStack/router PR: 5732File: packages/start-client-core/src/client/hydrateStart.ts:6-9Timestamp: 2025-11-02T16:16:24.898ZLearning: In packages/start-client-core/src/client/hydrateStart.ts, the `import/no-duplicates` ESLint disable is necessary for imports from `#tanstack-router-entry` and `#tanstack-start-entry` because both aliases resolve to the same placeholder file (`fake-start-entry.js`) in package.json during static analysis, even though they resolve to different files at runtime.

Applied to files:

  • e2e/react-start/basic-nitro/package.json
  • e2e/react-start/basic-nitro/.prettierignore
  • e2e/react-start/basic-nitro-spa/package.json
  • e2e/react-start/basic-nitro-spa/tsconfig.json
  • e2e/react-start/basic-nitro/tsconfig.json
  • e2e/solid-start/basic-nitro-spa/src/routes/index.tsx
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/solid-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/solid-start/basic-nitro/tsconfig.json
  • e2e/solid-start/basic-nitro-spa/tsconfig.json
  • e2e/solid-start/basic-nitro/package.json
  • e2e/solid-start/basic-nitro/.prettierignore
  • e2e/react-start/basic-nitro/src/routes/index.tsx
  • e2e/solid-start/basic-nitro-spa/package.json
  • e2e/solid-start/basic-nitro/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro/src/routeTree.gen.ts
📚 Learning: 2025-11-25T00:18:21.282Z
Learnt from: CRRepo: TanStack/router PR: 0File: AGENTS.md:0-0Timestamp: 2025-11-25T00:18:21.282ZLearning: Applies to packages/solid-router/**/*.{ts,tsx} : Solid Router components and primitives should use the tanstack/solid-router package

Applied to files:

  • e2e/react-start/basic-nitro/package.json
  • e2e/react-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro-spa/package.json
  • e2e/react-start/basic-nitro/src/router.tsx
  • e2e/react-start/basic-nitro/src/routes/static.tsx
  • e2e/react-start/basic-nitro-spa/src/router.tsx
  • e2e/react-start/basic-nitro/src/components/DefaultCatchBoundary.tsx
  • e2e/solid-start/basic-nitro-spa/vite.config.ts
  • e2e/solid-start/basic-nitro-spa/src/routes/index.tsx
  • e2e/solid-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/solid-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/solid-start/basic-nitro-spa/src/router.tsx
  • e2e/solid-start/basic-nitro/src/routes/index.tsx
  • e2e/solid-start/basic-nitro/src/routes/static.tsx
  • e2e/solid-start/basic-nitro/tsconfig.json
  • e2e/solid-start/basic-nitro/src/router.tsx
  • e2e/solid-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/solid-start/basic-nitro-spa/tsconfig.json
  • e2e/solid-start/basic-nitro/package.json
  • e2e/solid-start/basic-nitro/.prettierignore
  • e2e/react-start/basic-nitro/src/routes/index.tsx
  • e2e/solid-start/basic-nitro-spa/package.json
  • e2e/solid-start/basic-nitro/src/routeTree.gen.ts
  • e2e/solid-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routeTree.gen.ts
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuelRepo: TanStack/router PR: 5330File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61Timestamp: 2025-10-01T18:31:35.420ZLearning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.

Applied to files:

  • e2e/react-start/basic-nitro/.prettierignore
  • e2e/react-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routes/static.tsx
  • e2e/react-start/basic-nitro-spa/src/router.tsx
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/solid-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/solid-start/basic-nitro/src/router.tsx
  • e2e/solid-start/basic-nitro/.prettierignore
  • e2e/react-start/basic-nitro/src/routes/index.tsx
  • e2e/solid-start/basic-nitro/src/routeTree.gen.ts
  • e2e/solid-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routeTree.gen.ts
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaadRepo: TanStack/router PR: 5402File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21Timestamp: 2025-10-08T08:11:47.088ZLearning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • e2e/react-start/basic-nitro/.prettierignore
  • e2e/react-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/tests/app.spec.ts
  • e2e/react-start/basic-nitro/src/routes/static.tsx
  • e2e/react-start/basic-nitro-spa/src/router.tsx
  • e2e/solid-start/basic-nitro/tests/app.spec.ts
  • e2e/solid-start/basic-nitro-spa/src/routes/index.tsx
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/solid-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/solid-start/basic-nitro-spa/src/router.tsx
  • e2e/react-start/basic-nitro-spa/tests/app.spec.ts
  • e2e/solid-start/basic-nitro/src/routes/index.tsx
  • e2e/solid-start/basic-nitro/src/routes/static.tsx
  • e2e/solid-start/basic-nitro/src/router.tsx
  • e2e/solid-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/solid-start/basic-nitro/.prettierignore
  • e2e/react-start/basic-nitro/src/routes/index.tsx
  • e2e/solid-start/basic-nitro-spa/tests/app.spec.ts
  • e2e/solid-start/basic-nitro/src/routeTree.gen.ts
  • e2e/solid-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routeTree.gen.ts
📚 Learning: 2025-11-25T00:18:21.282Z
Learnt from: CRRepo: TanStack/router PR: 0File: AGENTS.md:0-0Timestamp: 2025-11-25T00:18:21.282ZLearning: Applies to **/src/routes/**/*.{ts,tsx} : Use file-based routing in src/routes/ directories or code-based routing with route definitions

Applied to files:

  • e2e/react-start/basic-nitro/.prettierignore
  • e2e/react-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/router.tsx
  • e2e/react-start/basic-nitro/src/routes/static.tsx
  • e2e/react-start/basic-nitro-spa/src/router.tsx
  • e2e/solid-start/basic-nitro-spa/src/routes/index.tsx
  • e2e/solid-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/solid-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/solid-start/basic-nitro-spa/src/router.tsx
  • e2e/solid-start/basic-nitro/src/routes/index.tsx
  • e2e/solid-start/basic-nitro/src/routes/static.tsx
  • e2e/solid-start/basic-nitro/src/router.tsx
  • e2e/solid-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/react-start/basic-nitro-spa/src/routes/index.tsx
  • e2e/solid-start/basic-nitro/.prettierignore
  • e2e/react-start/basic-nitro/src/routes/index.tsx
  • e2e/solid-start/basic-nitro/src/routeTree.gen.ts
  • e2e/solid-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routeTree.gen.ts
📚 Learning: 2025-10-14T18:59:33.990Z
Learnt from: FatahChanRepo: TanStack/router PR: 5475File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0Timestamp: 2025-10-14T18:59:33.990ZLearning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.

Applied to files:

  • e2e/react-start/basic-nitro-spa/src/routes/__root.tsx
📚 Learning: 2025-10-09T12:59:02.129Z
Learnt from: hokkyssRepo: TanStack/router PR: 5418File: e2e/react-start/custom-identifier-prefix/src/styles/app.css:19-21Timestamp: 2025-10-09T12:59:02.129ZLearning: In e2e test directories (paths containing `e2e/`), accessibility concerns like outline suppression patterns are less critical since the code is for testing purposes, not production use.

Applied to files:

  • e2e/react-start/basic-nitro/tests/app.spec.ts
📚 Learning: 2025-10-09T12:59:14.842Z
Learnt from: hokkyssRepo: TanStack/router PR: 5418File: e2e/react-start/custom-identifier-prefix/public/site.webmanifest:2-3Timestamp: 2025-10-09T12:59:14.842ZLearning: In e2e test fixtures (files under e2e directories), empty or placeholder values in configuration files like site.webmanifest are acceptable and should not be flagged unless the test specifically validates those fields.

Applied to files:

  • e2e/react-start/basic-nitro/tests/app.spec.ts
  • e2e/react-start/basic-nitro/playwright.config.ts
  • e2e/react-start/basic-nitro-spa/playwright.config.ts
  • e2e/react-start/basic-nitro-spa/public/site.webmanifest
  • e2e/solid-start/basic-nitro/playwright.config.ts
  • e2e/solid-start/basic-nitro/package.json
  • e2e/solid-start/basic-nitro/public/site.webmanifest
  • e2e/react-start/basic-nitro/public/site.webmanifest
  • e2e/solid-start/basic-nitro-spa/playwright.config.ts
  • e2e/solid-start/basic-nitro-spa/public/site.webmanifest
📚 Learning: 2025-11-25T00:18:21.282Z
Learnt from: CRRepo: TanStack/router PR: 0File: AGENTS.md:0-0Timestamp: 2025-11-25T00:18:21.282ZLearning: Applies to **/package.json : Use workspace protocol for internal dependencies (workspace:*)

Applied to files:

  • e2e/react-start/basic-nitro-spa/package.json
  • e2e/solid-start/basic-nitro-spa/package.json
📚 Learning: 2025-11-25T00:18:21.282Z
Learnt from: CRRepo: TanStack/router PR: 0File: AGENTS.md:0-0Timestamp: 2025-11-25T00:18:21.282ZLearning: Applies to **/*.{ts,tsx} : Use TypeScript strict mode with extensive type safety throughout the codebase

Applied to files:

  • e2e/react-start/basic-nitro-spa/tsconfig.json
  • e2e/react-start/basic-nitro/tsconfig.json
  • e2e/solid-start/basic-nitro/tsconfig.json
  • e2e/solid-start/basic-nitro-spa/tsconfig.json
🧬 Code graph analysis (17)
e2e/react-start/basic-nitro-spa/src/components/NotFound.tsx (2)
e2e/react-start/server-routes/src/components/NotFound.tsx (1)
  • NotFound (3-25)
e2e/react-start/basic-cloudflare/src/components/NotFound.tsx (1)
  • NotFound (3-25)
e2e/react-start/basic-nitro/playwright.config.ts (1)
scripts/set-ts-version.js (1)
  • packageJson (33-33)
e2e/react-start/basic-nitro-spa/src/components/DefaultCatchBoundary.tsx (2)
e2e/react-start/server-routes/src/components/DefaultCatchBoundary.tsx (1)
  • DefaultCatchBoundary (10-53)
examples/react/start-basic-cloudflare/src/components/DefaultCatchBoundary.tsx (1)
  • DefaultCatchBoundary (10-53)
e2e/react-start/basic-nitro-spa/playwright.config.ts (1)
scripts/set-ts-version.js (1)
  • packageJson (33-33)
e2e/react-start/basic-nitro/src/router.tsx (2)
e2e/react-start/basic-cloudflare/src/router.tsx (1)
  • getRouter (6-16)
e2e/solid-start/basic-tsr-config/src/app/router.tsx (1)
  • createRouter (4-11)
e2e/react-start/basic-nitro/src/components/DefaultCatchBoundary.tsx (2)
packages/router-core/src/router.ts (1)
  • state (1088-1090)
e2e/react-start/server-routes/src/components/DefaultCatchBoundary.tsx (1)
  • DefaultCatchBoundary (10-53)
e2e/solid-start/basic-nitro-spa/src/utils/seo.ts (1)
scripts/llms-generate.mjs (1)
  • title (101-101)
packages/start-plugin-core/src/post-server-build.ts (1)
packages/start-plugin-core/src/schema.ts (1)
  • TanStackStartOutputConfig (207-207)
e2e/solid-start/basic-nitro/src/components/DefaultCatchBoundary.tsx (2)
examples/solid/start-basic-nitro/src/components/DefaultCatchBoundary.tsx (1)
  • DefaultCatchBoundary (10-53)
e2e/solid-start/server-routes/src/components/DefaultCatchBoundary.tsx (1)
  • DefaultCatchBoundary (10-53)
e2e/solid-start/basic-nitro-spa/src/routes/index.tsx (2)
examples/solid/kitchen-sink/src/useMutation.tsx (1)
  • data (47-49)
packages/router-core/src/ssr/tsrScript.ts (1)
  • p (11-13)
e2e/solid-start/basic-nitro-spa/src/routes/__root.tsx (4)
e2e/solid-start/server-functions/src/routes/__root.tsx (1)
  • RootComponent (33-47)
e2e/solid-start/custom-basepath/src/routes/__root.tsx (1)
  • RootComponent (56-114)
e2e/solid-start/serialization-adapters/src/routes/__root.tsx (1)
  • RootDocument (35-74)
e2e/solid-start/website/src/routes/__root.tsx (1)
  • RootComponent (59-73)
e2e/solid-start/basic-nitro/src/utils/seo.ts (1)
examples/solid/start-basic-nitro/src/utils/seo.ts (1)
  • title (1-33)
e2e/solid-start/basic-nitro-spa/src/router.tsx (1)
examples/solid/start-basic-nitro/src/router.tsx (1)
  • getRouter (6-16)
packages/start-plugin-core/src/prerender-nitro.ts (3)
packages/start-plugin-core/src/schema.ts (2)
  • TanStackStartOutputConfig (207-207)
  • Page (202-202)
packages/router-core/src/route.ts (1)
  • path (1553-1555)
packages/history/src/index.ts (1)
  • location (162-164)
e2e/solid-start/basic-nitro-spa/src/routes/static.tsx (1)
examples/solid/kitchen-sink/src/useMutation.tsx (1)
  • data (47-49)
e2e/solid-start/basic-nitro-spa/playwright.config.ts (1)
scripts/set-ts-version.js (1)
  • packageJson (33-33)
e2e/react-start/basic-nitro-spa/src/utils/seo.ts (1)
scripts/llms-generate.mjs (1)
  • title (101-101)

Comment on lines 113 to 133
asyncfunctionlocalFetch(
urlPath:string,
options?:RequestInit,
maxRedirects:number=5,
):Promise<Response>{
consturl=newURL(urlPath,baseUrl)
constrequest=newRequest(url,options)
constresponse=awaitfetch(request)

if(isRedirectResponse(response)&&maxRedirects>0){
constlocation=response.headers.get('location')!
if(location.startsWith('http://localhost')||location.startsWith('/')){
constnewUrl=location.replace('http://localhost','')
returnlocalFetch(newUrl,options,maxRedirects-1)
}else{
logger.warn(`Skipping redirect to external location:${location}`)
}
}

returnresponse
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue |🟡 Minor

Redirect URL handling may not preserve port numbers correctly.

Line 125 replaces'http://localhost' but redirects may include port numbers like'http://localhost:3000/path'. The current logic would result in:3000/path being passed to the nextlocalFetch call.

       if (location.startsWith('http://localhost') || location.startsWith('/')) {-        const newUrl = location.replace('http://localhost', '')+        // Parse the URL to properly extract the pathname+        const redirectUrl = new URL(location, baseUrl)+        const newUrl = redirectUrl.pathname + redirectUrl.search + redirectUrl.hash         return localFetch(newUrl, options, maxRedirects - 1)

This ensures the path is correctly extracted regardless of the port in the redirect URL.

🤖 Prompt for AI Agents
In packages/start-plugin-core/src/prerender-nitro.ts around lines 113 to 133,the redirect handling currently strips only the exact 'http://localhost' prefixand will leave port numbers (e.g. ':3000/path') in the next localFetch call;instead parse the redirect Location header into a URL and extract the pathname +search + hash so port and origin are removed cleanly before recursing. Changethe branch that handles local redirects to detect http(s)://localhost (withoptional port), construct a URL object from location (or relative to baseUrl ifneeded), build a new path using url.pathname + url.search + url.hash, and calllocalFetch with that new path; keep the existing maxRedirects decrement andwarning behavior for external origins.

Comment on lines 365 to 372
}catch(error){
if(retries<(prerenderOptions.retryCount??0)){
logger.warn(`Encountered error, retrying:${page.path} in 500ms`)
awaitnewPromise((resolve)=>
setTimeout(resolve,prerenderOptions.retryDelay),
)
retriesByPath.set(page.path,retries+1)
addCrawlPageTask(page)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue |🟡 Minor

Log message doesn't match actual retry delay.

Line 367 logs"retrying: ${page.path} in 500ms" but line 369 usesprerenderOptions.retryDelay, which may differ from 500ms.

-          logger.warn(`Encountered error, retrying: ${page.path} in 500ms`)+          logger.warn(`Encountered error, retrying: ${page.path} in ${prerenderOptions.retryDelay ?? 500}ms`)
🤖 Prompt for AI Agents
In packages/start-plugin-core/src/prerender-nitro.ts around lines 365 to 372,the log message hardcodes "in 500ms" but the actual delay usesprerenderOptions.retryDelay; update the logger to reflect the actual value (e.g.use the retryDelay variable or prerenderOptions.retryDelay) so the messagematches the real delay, and ensure retryDelay has a sensible default whenundefined before logging.

Copy link
Contributor

@coderabbitaicoderabbitaibot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/start-plugin-core/src/prerender.ts (2)

2-2:Fix import ordering and use top-level type import.

The static analysis correctly flags thatChildProcess should be sorted beforespawn and should use a top-level type-only import for consistency.

-import { spawn, type ChildProcess } from 'node:child_process'+import type { ChildProcess } from 'node:child_process'+import { spawn } from 'node:child_process'

317-321:Consider more robust process shutdown handling.

The fixed 500ms wait may not be sufficient for graceful shutdown, and there's no fallback if the process doesn't terminate. Consider waiting for the'exit' event or adding a SIGKILL fallback.

             close: async () => {-              child.kill('SIGTERM')-              // Wait a bit for graceful shutdown-              await new Promise((r) => setTimeout(r, 500))+              return new Promise<void>((resolve) => {+                const forceKillTimeout = setTimeout(() => {+                  child.kill('SIGKILL')+                }, 5000)+                child.once('exit', () => {+                  clearTimeout(forceKillTimeout)+                  resolve()+                })+                child.kill('SIGTERM')+              })             },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between8162cc6 and6c19468.

📒 Files selected for processing (5)
  • e2e/react-start/basic-nitro-spa/tests/app.spec.ts (1 hunks)
  • e2e/solid-start/basic-nitro-spa/tests/app.spec.ts (1 hunks)
  • packages/start-plugin-core/src/plugin.ts (3 hunks)
  • packages/start-plugin-core/src/post-server-build.ts (3 hunks)
  • packages/start-plugin-core/src/prerender.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • e2e/react-start/basic-nitro-spa/tests/app.spec.ts
  • e2e/solid-start/basic-nitro-spa/tests/app.spec.ts
  • packages/start-plugin-core/src/plugin.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety throughout the codebase

Files:

  • packages/start-plugin-core/src/post-server-build.ts
  • packages/start-plugin-core/src/prerender.ts
🧬 Code graph analysis (1)
packages/start-plugin-core/src/prerender.ts (2)
packages/start-plugin-core/src/schema.ts (1)
  • TanStackStartOutputConfig (207-207)
packages/start-plugin-core/src/constants.ts (1)
  • VITE_ENVIRONMENT_NAMES (1-6)
🪛 ESLint
packages/start-plugin-core/src/prerender.ts

[error] 2-2: Member 'ChildProcess' of the import declaration should be sorted alphabetically.

(sort-imports)


[error] 2-2: Prefer using a top-level type-only import instead of inline type specifiers.

(import/consistent-type-specifier-style)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (8)
packages/start-plugin-core/src/prerender.ts (5)

12-65:LGTM - Well-structured function signature and output resolution.

The updated function signature properly handles the dual-mode operation (Nitro vs Vite), with clear type definitions and appropriate error handling when neitherbuilder noroutputDir is provided.


69-87:LGTM - Clean dual-backend abstraction.

The server startup logic properly abstracts both Nitro and Vite preview backends with a unified cleanup interface, enabling consistent resource management in thefinally block.


309-330:Server readiness check accepts 4xx responses.

The checkres.ok || res.status < 500 will consider the server ready even for 4xx client errors (e.g., 404, 401). If the root endpoint requires authentication or doesn't exist, this could lead to premature resolution. Consider checking only forres.ok or specific expected status codes if the root path is guaranteed to return a success response.


115-128:LGTM - Proper error handling and cleanup.

Good improvement: errors are now logged and rethrown rather than swallowed, and the cleanup function is consistently called in thefinally block regardless of success or failure.


348-369:LGTM!

Clean update to accept an optionalconfigFile parameter with proper error wrapping.

packages/start-plugin-core/src/post-server-build.ts (3)

8-42:LGTM - Good extraction of shared configuration logic.

ThesetupPrerenderConfig helper cleanly consolidates the prerender configuration logic that was previously duplicated, enabling reuse betweenpostServerBuild andpostServerBuildForNitro.


44-70:LGTM!

Clean refactoring with the newskipPrerender parameter enabling the caller to defer prerendering to a later stage (e.g., post-Nitro build).


72-97:LGTM - Well-structured Nitro-specific post-build handler.

The function correctly mirrors the structure ofpostServerBuild while using Nitro-specific parameters (outputDir,nitroServerPath) instead of the Vite builder. The reuse ofsetupPrerenderConfig ensures consistent behavior across both paths.

Comment on lines +288 to +299
asyncfunctionstartNitroServer(
serverPath:string,
):Promise<{url:URL;close:()=>Promise<void>}>{
returnnewPromise((resolve,reject)=>{
// Find a random port
constport=3000+Math.floor(Math.random()*10000)
constenv={ ...process.env,PORT:String(port)}

constchild:ChildProcess=spawn('node',[serverPath],{
env,
stdio:['ignore','pipe','pipe'],
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue |🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the prerender.ts filefind. -type f -name"prerender.ts"| head -20

Repository: TanStack/router

Length of output: 226


🏁 Script executed:

# Get file line count and examine the functionwc -l ./packages/start-plugin-core/src/prerender.ts

Repository: TanStack/router

Length of output: 108


🏁 Script executed:

# Read the complete function starting from line 280sed -n'280,350p' ./packages/start-plugin-core/src/prerender.ts

Repository: TanStack/router

Length of output: 1764


🏁 Script executed:

# Check if get-port is already a dependencycat ./packages/start-plugin-core/package.json| grep -A 20"dependencies"

Repository: TanStack/router

Length of output: 803


🏁 Script executed:

# Search for all usages of startNitroServer functionrg -n"startNitroServer" ./packages/start-plugin-core/src/prerender.ts

Repository: TanStack/router

Length of output: 165


🏁 Script executed:

# Check the context where startNitroServer is calledsed -n'60,90p' ./packages/start-plugin-core/src/prerender.ts

Repository: TanStack/router

Length of output: 953


🏁 Script executed:

# Check if there's any error handling or retry logic for port issues elsewhererg -n"EADDRINUSE|port.*collision|port.*in.*use" ./packages/start-plugin-core/src/ -i

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Let's look at the imports to understand what packages are availablehead -30 ./packages/start-plugin-core/src/prerender.ts

Repository: TanStack/router

Length of output: 1031


🌐 Web query:

Node.js child_process port 0 OS assign available port

💡 Result:

Short answer: bind the child to port 0 (server.listen(0)) so the OS picks an available port, then read the actual port from server.address().port and communicate it back to the parent (e.g. via IPC or stdout).

Why/how (references):

  • The OS assigns an unused port when you pass 0 to listen/bind; Node exposes the chosen port in server.address().port. [1][2][3]

Minimal example (child + parent using fork + IPC):

  • child.js
    const http = require('http');
    const server = http.createServer((req,res)=>res.end('ok'));
    server.listen(0, ()=> {
    const port = server.address().port;
    if (process.send) process.send({port}); // send to parent when forked
    console.log('listening', port); // or stdout
    });

  • parent.js
    const { fork } = require('child_process');
    const child = fork('./child.js');
    child.on('message', msg => console.log('child port', msg.port));

References:

  1. Node.js child_process docs (server handle / IPC examples). [Node.js docs]. [4]
  2. StackOverflow / Node patterns: use port 0 and server.address().port to get assigned port. [3][5]
  3. (--inspect=0 note) Node can auto-assign debugger ports with =0 when launching processes. [0]

(If you want, I can paste a runnable snippet or show using spawn + stdout parsing instead of fork.)


🌐 Web query:

Nitro server startup output port logging console

💡 Result:

Do you mean Nitro (the Nuxt / nitro.build server runtime) or AWS Nitro (EC2 Nitro Enclaves)?


🌐 Web query:

nitro.build server startup console output messages

💡 Result:

Typical Nitro server startup console messages (common outputs you’ll see)

  • "Nitro vX.Y.Z" or framework banner (on some installs). [1]
  • Dev server ready / listening message, e.g. "Nitro is ready athttp://localhost:3000" or "Listening onhttp://localhost:3000". [2]
  • Runtime preset / entry info when running built server (node .output/server/index.mjs). [1][2]
  • Environment / port info (reads NITRO_PORT / PORT, NITRO_HOST / HOST). [1]
  • Plugin/Hook logs: any console.log() from server/plugins run during startup (e.g. "Nitro plugin ..."). [3]
  • Warnings or errors about missing SSL keys, UNIX socket usage, or shutdown handling if relevant environment vars are set (NITRO_SSL_CERT / NITRO_SSL_KEY, NITRO_UNIX_SOCKET, NITRO_SHUTDOWN_*). [1]

Sources:
[1] Nitro — Node.js runtime / deploy docs.
[2] Nitro — Getting Started / dev server messages.
[3] Nitro — Plugins (plugin console output runs at startup).


🌐 Web query:

Node.js child_process stdio pipe buffer size limit blocking

💡 Result:

Short answer

  • Child stdio use OS pipes with limited, platform-specific kernel buffers; if the child writes faster than the parent consumes, the kernel pipe fills and the child will block on write. [1][2]
  • On Linux the default pipe capacity is typically 65,536 bytes (64 KiB) (can be queried/changed with F_GETPIPE_SZ / F_SETPIPE_SZ). [2]
  • In Node, spawned children’s stdout/stderr are exposed as Readable streams (with Node-level buffering controlled by stream highWaterMark, default 16 KiB) and exec()/execSync() also enforce a JS-side maxBuffer. If you don’t read the stream fast enough the kernel pipe still fills and the child will block. [1][3][4]

Practical fixes

  • Always consume the child’s stdout/stderr (attach 'data' handler or pipe it) so the kernel buffer drains. [1]
  • Use stdio: 'inherit' (shares parent stdio) or stdio: 'ignore' (drops output) if you don’t need the data. [1]
  • For very high-throughput cases: (a) stream data incrementally (pipe to a file or process), (b) tune Node stream buffers via highWaterMark where applicable, or (c) on Linux adjust pipe size with fcntl(F_SETPIPE_SZ) from native code (not exposed directly by Node core). [1][2][3]
  • If using child_process.exec(), raise maxBuffer (or switch to spawn()) to avoid JS-side buffering limits. [4]

Sources

  • Node.js child_process docs (spawn/stdio behavior, exec maxBuffer). [1]
  • Linux pipe(7) / man pages (pipe capacity, F_GET/SETPIPE_SZ). [2]
  • Node.js stream docs (default highWaterMark = 16 KiB). [3]
  • child_process.exec/execSync maxBuffer description. [4]

🏁 Script executed:

# Let's verify the exact stdio configuration and check if there are testsgrep -n"stdio" ./packages/start-plugin-core/src/prerender.ts -B 2 -A 2

Repository: TanStack/router

Length of output: 200


🏁 Script executed:

# Check if there are any tests for the prerender functionfind ./packages/start-plugin-core -name"*.test.ts" -o -name"*.spec.ts"| xargs grep -l"startNitroServer\|prerender"2>/dev/null| head -5

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Check tsconfig for strict modefind ./packages/start-plugin-core -name"tsconfig.json"| xargs cat2>/dev/null| head -50

Repository: TanStack/router

Length of output: 283


🏁 Script executed:

# Check root tsconfigcat ./tsconfig.json| grep -A 30"compilerOptions"

Repository: TanStack/router

Length of output: 818


Based on my verification of the review comment, I can now provide the rewritten review:


Address stdout buffer blocking and improve port collision handling.

The code has two legitimate concerns:

  1. stdout buffer blocking: Settingstdio: 'pipe' without consuming stdout can cause the child process to block if Nitro's startup output exceeds the kernel pipe buffer (~64 KiB on Linux). stderr is correctly consumed, but stdout is not. Usingstdio: 'inherit' fixes this by inheriting the parent's stdout.

  2. Port collision risk: The random port selection (3000–12999) may collide with existing services, causing a confusing "startup timed out" error instead of a clear port-in-use failure. However, the suggested fix usingport: 0 requires additional infrastructure to communicate the OS-assigned port back from the Nitro process (e.g., parsing startup output or using IPC), which the diff does not address. Alternatively, consider using a library likeget-port or performing an availability check before spawning.

-    const port = 3000 + Math.floor(Math.random() * 10000)+    // Use port 0 to let OS assign an available ephemeral port,+    // then parse the actual port from Nitro's startup output+    const port = 0     const env = { ...process.env, PORT: String(port) }     const child: ChildProcess = spawn('node', [serverPath], {       env,-      stdio: ['ignore', 'pipe', 'pipe'],+      stdio: ['ignore', 'inherit', 'pipe'],     })

The stdout fix is straightforward and recommended. The port collision fix needs clarification on how to retrieve the actual port Nitro binds to when using port 0.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/start-plugin-core/src/prerender.ts around lines 288-299, change thechild spawn options to avoid stdout buffer blocking by either inheriting stdout(stdio: ['ignore','inherit','pipe']) or actively consuming child.stdout stream;and replace the naive random port selection with a reliable approach such asusing get-port to pick an available port before spawning (or, if you choose port0, implement a mechanism to read the OS-assigned port from Nitro startup outputor via IPC) so you fail fast on port collisions and can determine the actualbound port.

Copy link
Contributor

@coderabbitaicoderabbitaibot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/start-plugin-core/src/prerender.ts (1)

292-350:Address stdout buffer blocking and port collision risks.

The issues from the previous review remain:

  1. stdout buffer blocking:stdio: ['ignore', 'pipe', 'pipe'] pipes stdout but it's never consumed. Only stderr is consumed (lines 343-345). If Nitro outputs significant data to stdout, the kernel pipe buffer (~64 KiB) fills and the process blocks.

  2. Port collision risk: Random port selection (3000–12999) may collide with existing services, resulting in a confusing "startup timed out" error rather than a clear port-in-use failure.

Apply this diff to fix the stdout issue (minimal change):

     const child: ChildProcess = spawn('node', [serverPath], {       env,-      stdio: ['ignore', 'pipe', 'pipe'],+      stdio: ['ignore', 'inherit', 'pipe'],     })

For port collision, consider using theget-port library (already in some Vite tooling) or implement a retry loop that catches EADDRINUSE.

🧹 Nitpick comments (2)
packages/start-plugin-core/src/prerender.ts (1)

352-397:Complex but necessary workaround for Nitro's signal handling.

Theprocess.exit override and SIGHUP emit is a pragmatic solution to trigger Nitro's child process cleanup without exiting the parent process. A few observations:

  1. The 100ms delay (line 392) is somewhat arbitrary – if Nitro's cleanup is slow, this might not be enough.
  2. There's a brief window whereprocess.exit is no-op'd; if other code calls it during this time, it would be silently ignored.

Consider adding a brief comment explaining why this workaround exists (Nitro's configurePreviewServer behavior) for future maintainers.

packages/start-plugin-core/src/plugin.ts (1)

378-409:Consider adding type annotations for the Nitro module interface.

Thenitro: any parameter and untyped access tonitro.options.output.dir,nitro.options.output.publicDir, andnitro.options.preset reduce type safety. Per the coding guidelines requiring TypeScript strict mode, consider defining a minimal interface for the Nitro options you depend on.

+// Minimal type for Nitro module interop (Nitro v3.0.1-alpha.1+)+interface NitroModuleContext {+  options: {+    preset: string+    output: {+      dir: string+      publicDir: string+    }+  }+  hooks: {+    hook: (name: 'compiled', fn: () => Promise<void>) => void+  }+}+ {   name: 'tanstack-start-core:nitro-prerender',   nitro: {     name: 'tanstack-start-prerender',-    setup(nitro: any) {+    setup(nitro: NitroModuleContext) {       nitro.hooks.hook('compiled', async () => {

This provides documentation and catches API drift at compile time. Based on coding guidelines requiring TypeScript strict mode.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between14f3b45 andca09453.

📒 Files selected for processing (3)
  • packages/start-plugin-core/src/plugin.ts (3 hunks)
  • packages/start-plugin-core/src/post-server-build.ts (3 hunks)
  • packages/start-plugin-core/src/prerender.ts (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/start-plugin-core/src/post-server-build.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety throughout the codebase

Files:

  • packages/start-plugin-core/src/plugin.ts
  • packages/start-plugin-core/src/prerender.ts
🧬 Code graph analysis (2)
packages/start-plugin-core/src/plugin.ts (2)
packages/nitro-v2-vite-plugin/src/index.ts (1)
  • config (58-139)
packages/start-plugin-core/src/post-server-build.ts (3)
  • postServerBuild (76-102)
  • writeNitroBuildInfo (16-38)
  • postServerBuildForNitro (104-129)
packages/start-plugin-core/src/prerender.ts (2)
packages/start-plugin-core/src/schema.ts (1)
  • TanStackStartOutputConfig (207-207)
packages/start-plugin-core/src/constants.ts (1)
  • VITE_ENVIRONMENT_NAMES (1-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (6)
packages/start-plugin-core/src/prerender.ts (3)

13-26:LGTM – Function signature update enables flexible invocation.

The updated signature properly supports both Vite builder-based and Nitro-based prerendering flows. The optional parameters with clear JSDoc fornitroServerPath provide good API clarity.


52-66:LGTM – Output directory resolution handles both invocation modes.

The conditional logic correctly prioritizes the explicit override, falls back to builder's client environment config, and throws a clear error when neither is available.


419-445:LGTM – Clean polling implementation for server readiness.

The function uses reasonable defaults (100ms interval, 30s timeout) and correctly treats any non-5xx response as "ready" since a 404 still indicates the server is accepting requests.

packages/start-plugin-core/src/plugin.ts (3)

53-53:LGTM – Config file path tracking for Nitro integration.

AddingviteConfigFile toResolvedStartConfig provides the necessary context for the Nitro prerender flow to invokevite.preview() with the correct configuration.

Also applies to: 80-80


356-358:LGTM – Correct handling of configFile value.

The|| undefined coercion correctly converts Vite'sfalse (no config file) toundefined, aligning with the expected type inResolvedStartConfig.


363-374:LGTM – Nitro detection enables deferred prerendering.

The plugin name prefix check (nitro:) is a reasonable heuristic for detecting Nitro's presence, and passingskipPrerender: true correctly defers prerendering to the Nitro module'scompiled hook.

Comment on lines +88 to +91

// Wait for the server to be ready (handles Nitro's child process startup time)
awaitwaitForServerReady(baseUrl,logger)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue |🟡 Minor

Misleading comment – This is the Vite preview path, not Nitro.

The comment says "handles Nitro's child process startup time" but this code is in theelse branch where we're using Vite preview, not Nitro. The Nitro server path (lines 73-77) already handles readiness internally via polling instartNitroServer.

-    // Wait for the server to be ready (handles Nitro's child process startup time)+    // Wait for the Vite preview server to be ready     await waitForServerReady(baseUrl, logger)
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Wait for the server to be ready (handles Nitro's child process startup time)
awaitwaitForServerReady(baseUrl,logger)
}
// Wait for the Vite preview server to be ready
awaitwaitForServerReady(baseUrl,logger)
}
🤖 Prompt for AI Agents
In packages/start-plugin-core/src/prerender.ts around lines 88 to 91, the inlinecomment incorrectly states it "handles Nitro's child process startup time" whilethis branch uses Vite preview; update the comment to accurately describe thatwaitForServerReady is used for the Vite preview server readiness (or remove themisleading reference to Nitro), e.g. mention Vite preview/startup latency orremove Nitro-specific wording so comments reflect the actual code path.

Copy link
Contributor

@coderabbitaicoderabbitaibot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
packages/start-plugin-core/src/prerender.ts (2)

89-90:Fix comment: this readiness wait is for Vite preview, not Nitro.

This branch is only executed whennitroServerPath is falsy and you’ve started a Vite preview server. The comment still refers to “Nitro’s child process startup time”, which is misleading and was already flagged in a previous review.

Something like:

// Wait for the Vite preview server (and any framework plugins) to be ready

would better reflect what this code is doing.


2-3:Nitro subprocess: stdout piping and port selection are fragile.

This block is doing the right thing conceptually (spawn Nitro, poll until ready, expose a close handle), but there are a few practical issues – some already raised in a previous review:

  1. stdout pipe can block the child process.
    You setstdio: ['ignore', 'pipe', 'pipe'] but never consumechild.stdout. If Nitro writes enough to stdout during startup, the OS pipe buffer can fill and block the child, making the readiness check hang.

    Easiest fix: inherit stdout if you don’t need to capture it:

  • const child: ChildProcess = spawn('node', [serverPath], {
  •  env,
  •  stdio: ['ignore', 'pipe', 'pipe'],
  • })
  • const child: ChildProcess = spawn('node', [serverPath], {
  •  env,
  •  // Inherit stdout so the child can't block on a full pipe buffer
  •  stdio: ['ignore', 'inherit', 'pipe'],
  • })
2. **Random port selection risks collisions and false “readiness”.**  Picking `3000 + Math.random() * 10000` can collide with existing services:- If another HTTP service is already on that port, your polling `fetch("http://localhost:port/")` may see a valid response and treat an unrelated server as “Nitro is ready”.- If Nitro fails with `EADDRINUSE`, you’ll time out after 30 seconds without a clear port‑in‑use signal.Consider a more robust approach, e.g.:- Use a port‑discovery helper (like `get-port`) to pick an available port before spawning Nitro, or- Allow `PORT=0` and have Nitro (or a small wrapper) communicate the actual bound port back (e.g. via stdout structured log or IPC) so you don’t have to guess or risk collisions.3. **Optional: react to early child exit.**  Right now, if the Nitro process exits quickly (e.g. due to a config error), you keep polling until the 30s timeout. Adding a `child.on('exit', ...)` that rejects immediately when `!resolved` would surface those failures faster.Points (1) and (2) are the most important to address; (3) is a nice quality‑of‑life improvement.  Also applies to: 9-9, 292-350</blockquote></details></blockquote></details><details><summary>🧹 Nitpick comments (3)</summary><blockquote><details><summary>packages/start-plugin-core/src/prerender.ts (3)</summary><blockquote>`16-26`: **Server setup vs try/finally – consider pulling startup into the guarded section.**Right now Nitro/Vite server startup (and `waitForServerReady` in the preview path) happens before the `try` block, while `cleanup()` is only called from the `finally`. That means if anything throws between assigning `baseUrl/cleanup` and entering the `try` (notably `waitForServerReady` timing out), the preview server will stay running until the outer caller tears down the process.This isn’t catastrophic for a short‑lived CLI, but for long‑lived processes/tests it’s safer to:- Move the `if (nitroServerPath) { ... } else { ...; await waitForServerReady(...) }` block inside the `try`, or- Add an outer `try/finally` that ensures `cleanup` is awaited whenever the server has been started.This keeps server lifecycle management clearly tied to the guarded region where prerendering actually happens.  Also applies to: 52-66, 70-72, 119-132---`10-10`: **Make `process.exit` override robust when cleaning up Nitro’s preview child.**The Nitro‑aware `startPreviewServer` logic is clever and solves a real problem (Nitro’s `configurePreviewServer` killing the parent process on `SIGHUP`). A couple of robustness tweaks would make it safer:- `server.close` currently does:```tsconst originalExit = process.exitprocess.exit = (() => {}) as typeof process.exitprocess.emit('SIGHUP', 'SIGHUP')process.exit = originalExitawait new Promise((resolve) => setTimeout(resolve, 100))return originalClose()

If any handler attached toSIGHUP throws synchronously,process.exit will never be restored. Wrapping the override in atry/finally keeps the global state consistent:

-      server.close = async () => {-        const originalExit = process.exit-        process.exit = (() => {}) as typeof process.exit--        // Emit SIGHUP to trigger Nitro's child process cleanup-        process.emit('SIGHUP', 'SIGHUP')--        // Restore process.exit-        process.exit = originalExit--        // Give the child process a moment to terminate-        await new Promise((resolve) => setTimeout(resolve, 100))--        // Now close the preview server-        return originalClose()-      }+      server.close = async () => {+        const originalExit = process.exit+        try {+          process.exit = (() => {}) as typeof process.exit++          // Emit SIGHUP to trigger Nitro's child process cleanup+          process.emit('SIGHUP', 'SIGHUP')+        } finally {+          // Always restore process.exit, even if a handler throws+          process.exit = originalExit+        }++        // Give the child process a moment to terminate+        await new Promise((resolve) => setTimeout(resolve, 100))++        // Now close the preview server+        return originalClose()+      }
  • ThehasNitroPlugin detection byp.name.startsWith('nitro:') is reasonable, but if Nitro ever changes its plugin names this branch will silently stop running. Non‑blocking, but you may want a short comment noting that assumption.

Overall, the preview/Nitro interop strategy looks solid; this is mainly about hardening the global process mutation.

Also applies to: 352-403


423-447:waitForServerReady looks good; consider reusing it for Nitro as well.

The readiness helper is straightforward and matches the semantics you use elsewhere (server “ready” once it responds with any status< 500). Implementation is clear and bounded by a timeout.

Given thatstartNitroServer has its own polling logic with similar behavior, you might consider reusingwaitForServerReady there (accepting aURL instead of re‑encodinghttp://localhost:${port}/) to keep the readiness semantics in one place and reduce duplication. Not required, but would simplify future changes (e.g. tweaking timeouts or readiness criteria) to a single helper.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and betweenca09453 andb17e4b6.

📒 Files selected for processing (1)
  • packages/start-plugin-core/src/prerender.ts (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety throughout the codebase

Files:

  • packages/start-plugin-core/src/prerender.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview

Comment on lines 97 to 103
asyncfunctionlocalFetch(
path:string,
fetchPath:string,
options?:RequestInit,
maxRedirects:number=5,
):Promise<Response>{
consturl=newURL(path,baseUrl)
consturl=newURL(fetchPath,baseUrl)
constrequest=newRequest(url,options)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue |🟠 Major

Redirect handling forhttp://localhost:<port>/… is broken.

InlocalFetch, when following redirects you do:

if(location.startsWith('http://localhost')||location.startsWith('/')){constnewUrl=location.replace('http://localhost','')returnlocalFetch(newUrl,options,maxRedirects-1)}

For a URL likehttp://localhost:3000/foo,replace('http://localhost', '') yields':3000/foo', which is not a valid path. The nextnew URL(fetchPath, baseUrl) call then builds a wrong URL.

Suggested fix:

  • Treat absolutehttp://localhost[:port] redirects by parsing and reusing only the path/query/hash, and keep the host/port frombaseUrl.
  • Keep the simple code path for already-relative URLs.

For example:

-    if (isRedirectResponse(response) && maxRedirects > 0) {-      const location = response.headers.get('location')!-      if (location.startsWith('http://localhost') || location.startsWith('/')) {-        const newUrl = location.replace('http://localhost', '')-        return localFetch(newUrl, options, maxRedirects - 1)-      } else {-        logger.warn(`Skipping redirect to external location: ${location}`)-      }-    }+    if (isRedirectResponse(response) && maxRedirects > 0) {+      const location = response.headers.get('location')!++      if (location.startsWith('http://localhost')) {+        const url = new URL(location)+        const newPath = `${url.pathname}${url.search}${url.hash}`+        return localFetch(newPath, options, maxRedirects - 1)+      }++      if (location.startsWith('/')) {+        return localFetch(location, options, maxRedirects - 1)+      }++      logger.warn(`Skipping redirect to external location: ${location}`)+    }

The newheaders: prerenderOptions.headers usage looks good and preserves the ability to customize per‑request headers.

Also applies to: 106-113, 195-201

@birkskyumbirkskyum marked this pull request as draftNovember 28, 2025 01:54
@theoludwig
Copy link

Hey!@birkskyum
Thank you for working on this! What are the next steps, what still need to be fixed before merging this?

@birkskyum
Copy link
MemberAuthor

@theoludwig , we need the nitro 3 to implement a fetch method

theoludwig, RolandoAndrade, and MKS2508 reacted with thumbs up emoji

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@coderabbitaicoderabbitai[bot]coderabbitai[bot] left review comments

Assignees

No one assigned

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

nitro + spa mode breaks since v1.138.0

3 participants

@birkskyum@theoludwig

[8]ページ先頭

©2009-2025 Movatter.jp