Skip to content

Conversation

@birkskyum
Copy link
Member

@birkskyum birkskyum commented Nov 27, 2025

Nitro Prerendering (both v2 and v3)

Likely resolves

Also, adds e2e tests for Nitro integration (each testing both nitro v2 and v3):

  • 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

Summary by CodeRabbit

Release Notes

  • New Features

    • Added enhanced Nitro integration for improved full-stack development workflows and better server-side function support.
    • Introduced improved prerendering capabilities for both traditional server-side and SPA-based applications.
  • Infrastructure

    • Expanded end-to-end test infrastructure with comprehensive testing coverage for various project configurations.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 27, 2025

📝 Walkthrough

Walkthrough

This PR introduces comprehensive end-to-end test infrastructure for four project variants: React Start with Nitro (SPA and full-stack) and Solid Start with Nitro (SPA and full-stack). It scaffolds complete projects with routing, components, configuration, and Playwright tests, and modifies the start-plugin-core to integrate Nitro prerender workflows alongside existing Vite-based prerendering.

Changes

Cohort / File(s) Summary
React Start Nitro SPA - Configuration & Setup
e2e/react-start/basic-nitro-spa/.gitignore, package.json, tsconfig.json, playwright.config.ts, postcss.config.mjs, vite.config.v2.ts, vite.config.v3.ts, public/site.webmanifest
Project manifest, build tooling (Vite, Playwright, Tailwind, Nitro v2/v3), and test configuration for SPA prerendering with deterministic port-based server startup.
React Start Nitro SPA - Routing & Components
src/components/DefaultCatchBoundary.tsx, src/components/NotFound.tsx, src/router.tsx, src/routeTree.gen.ts, src/routes/__root.tsx, src/routes/index.tsx, src/routes/static.tsx, src/utils/seo.ts, src/styles/app.css
TanStack Router setup with generated route tree, error/404 boundaries, root shell component with navigation, home and static routes with server functions, and SEO utilities.
React Start Nitro SPA - E2E Tests
tests/app.spec.ts
Playwright tests validating SPA prerender output, server function execution, and client-side navigation flow.
React Start Nitro (Full-Stack) - Configuration & Setup
e2e/react-start/basic-nitro/.gitignore, .prettierignore, package.json, tsconfig.json, playwright.config.ts, postcss.config.mjs, public/site.webmanifest
Full-stack variant config with Nitro server startup and prerender file detection in .output/public/static directory.
React Start Nitro (Full-Stack) - Routing & Components
src/components/DefaultCatchBoundary.tsx, src/components/NotFound.tsx, src/router.tsx, src/routeTree.gen.ts, src/routes/__root.tsx, src/routes/index.tsx, src/routes/static.tsx, src/utils/seo.ts, src/styles/app.css
Same routing and component structure as SPA variant; server functions return Node.js version info and Nitro runtime identifier.
React Start Nitro (Full-Stack) - E2E Tests & Setup
tests/app.spec.ts, tests/setup/global.setup.ts, tests/setup/global.teardown.ts
Playwright tests for runtime detection, prerendered static page verification, and navigation; empty setup/teardown hooks.
Solid Start Nitro SPA - Configuration & Setup
e2e/solid-start/basic-nitro-spa/.gitignore, package.json, tsconfig.json, playwright.config.ts, postcss.config.mjs, public/site.webmanifest
Solid.js project manifest and tooling; mirrors React SPA setup with Solid-specific dependencies.
Solid Start Nitro SPA - Routing & Components
src/components/DefaultCatchBoundary.tsx, src/components/NotFound.tsx, src/router.tsx, src/routeTree.gen.ts, src/routes/__root.tsx, src/routes/index.tsx, src/routes/static.tsx, src/utils/seo.ts, src/styles/app.css
Solid.js-adapted TanStack Router setup with Solid-specific JSX semantics and hydration/HydrationScript integration.
Solid Start Nitro SPA - E2E Tests
tests/app.spec.ts
SPA prerender, server function, and client-side navigation tests for Solid variant.
Solid Start Nitro (Full-Stack) - Configuration & Setup
e2e/solid-start/basic-nitro/.gitignore, .prettierignore, package.json, tsconfig.json, playwright.config.ts, postcss.config.mjs, public/site.webmanifest
Full-stack Solid variant configuration with Nitro prerender detection.
Solid Start Nitro (Full-Stack) - Routing & Components
src/components/DefaultCatchBoundary.tsx, src/components/NotFound.tsx, src/router.tsx, src/routeTree.gen.ts, src/routes/__root.tsx, src/routes/index.tsx, src/routes/static.tsx, src/utils/seo.ts, src/styles/app.css
Solid.js full-stack routing; server functions return user agent and Nitro runtime identifier.
Solid Start Nitro (Full-Stack) - E2E Tests
tests/app.spec.ts
Runtime detection, prerendered page verification, and navigation tests for Solid full-stack variant.
Core Plugin - Nitro Integration
packages/start-plugin-core/src/plugin.ts
Adds viteConfigFile field to ResolvedStartConfig, detects Nitro plugin presence, conditionally skips Vite prerender when Nitro is active, registers new tanstack-start-core:nitro-prerender plugin entry that delegates prerender to postServerBuildForNitro.
Core Plugin - Post-Build & Prerender Setup
packages/start-plugin-core/src/post-server-build.ts
Adds writeNitroBuildInfo() to emit nitro.json metadata, introduces setupPrerenderConfig() to initialize prerender settings, updates postServerBuild() signature with skipPrerender parameter, and adds new postServerBuildForNitro() function to handle Nitro-specific prerender orchestration.
Core Plugin - Prerender Backend Orchestration
packages/start-plugin-core/src/prerender.ts
Extends prerender() signature to accept optional outputDir, configFile, and nitroServerPath; adds conditional backend startup: if nitroServerPath is provided, spawns Nitro server; otherwise starts Vite preview; implements startNitroServer() helper and updates startPreviewServer() to accept configFile parameter and detect Nitro plugins for cleanup.

Sequence Diagram(s)

sequenceDiagram
    participant Orchestrator as Build/Prerender<br/>Orchestrator
    participant PluginCore as start-plugin-core
    participant ServerSelector as Server Selection
    participant NitroServer as Nitro Server
    participant ViteServer as Vite Preview
    participant PrerenderEngine as Prerender<br/>Engine
    participant Cleanup as Cleanup

    Orchestrator->>PluginCore: postServerBuild() or postServerBuildForNitro()
    PluginCore->>PluginCore: setupPrerenderConfig(startConfig)
    alt Nitro Path Provided
        PluginCore->>ServerSelector: nitroServerPath exists
        ServerSelector->>NitroServer: startNitroServer(nitroServerPath)
        NitroServer->>NitroServer: spawn child process
        NitroServer->>NitroServer: discover free port, poll ready
        NitroServer-->>ServerSelector: {url, close()}
    else Vite Path
        PluginCore->>ServerSelector: no nitroServerPath
        ServerSelector->>ViteServer: startPreviewServer(configFile)
        ViteServer->>ViteServer: build & start preview
        ViteServer-->>ServerSelector: preview server instance
    end
    
    ServerSelector-->>PrerenderEngine: server URL
    PrerenderEngine->>PrerenderEngine: waitForServerReady()
    PrerenderEngine->>PrerenderEngine: prerender pages via fetch
    PrerenderEngine-->>PluginCore: prerender complete
    
    alt Error During Prerender
        PrerenderEngine->>Cleanup: error occurred
        Cleanup->>Cleanup: log error
    else Success
        PrerenderEngine->>Cleanup: prerender done
    end
    
    Cleanup->>Cleanup: call cleanup/close handler
    alt Nitro Active
        Cleanup->>NitroServer: close Nitro child
    else Vite Active
        Cleanup->>ViteServer: close preview server
    end
    Cleanup-->>Orchestrator: done
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 Four frameworks danced in e2e clothes,
React and Solid, both in rows—
Nitro prerender takes the stage,
Backend orchestration's coming of age!
Routes, components, tests all shine,
SPA and full-stack, both divine! 🚀

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.94% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'nitro with vite preview' is vague and generic, using non-descriptive terms that don't clearly convey the changeset's primary purpose or scope. Consider a more descriptive title such as 'Add Nitro v3 support for prerendering with vite.preview()' or 'Support Nitro prerendering in TanStack Start' to clearly communicate the main change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

@nx-cloud
Copy link

nx-cloud bot commented Nov 27, 2025

View your CI Pipeline Execution ↗ for commit 99e6322

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 15m 34s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 21s View ↗

☁️ Nx Cloud last updated this comment at 2026-01-02 12:55:13 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 27, 2025

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@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/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-fn-stubs

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-fn-stubs@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/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 99e6322

@birkskyum birkskyum marked this pull request as ready for review November 28, 2025 00:08
Copy link
Contributor

@coderabbitai coderabbitai bot 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 from post-server-build.ts. Consider extracting a shared helper to reduce maintenance burden:

// In a shared utility file:
export function setupPrerenderConfig(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.

The versions: { 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 via startConfig.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: Tighten children typing instead of using any

To keep TS strictness and avoid any, you can type children 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: Use ReactNode instead of any for children

For stricter typing, you can avoid any by typing children 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 and keywords 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 over tags (e.g. dropping entries with content == 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: Move HeadContent into <head> and consider adding lang on <html>

Right now <HeadContent /> is rendered inside <body>, while it’s intended to manage <head> tags. Moving it into the <head> block (alongside HydrationScript) will produce more correct HTML and better reflect recommended usage. You may also want to add a lang 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 with content: undefined. Depending on how the meta tags are rendered, this could result in attributes like content="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 and basic-nitro-spa). While this keeps each test self-contained, it creates maintenance overhead if changes are needed.

The same undefined content value issue noted in e2e/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 a site.webmanifest file, but the root route's links 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 the site.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, and Route.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:

export const Route = 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 via data-testid for Playwright. As with the index route, you could inline loader: 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, and RootDocument shell) looks correct and consistent with other React Start examples. One tiny nit: if that color 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 between af02265 and 8162cc6.

⛔ 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: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T00:18:21.282Z
Learning: 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: nlynzaad
Repo: TanStack/router PR: 5732
File: packages/start-client-core/src/client/hydrateStart.ts:6-9
Timestamp: 2025-11-02T16:16:24.898Z
Learning: 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: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T00:18:21.282Z
Learning: 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-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: 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: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: 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: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T00:18:21.282Z
Learning: 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: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: 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: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/src/styles/app.css:19-21
Timestamp: 2025-10-09T12:59:02.129Z
Learning: 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: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/public/site.webmanifest:2-3
Timestamp: 2025-10-09T12:59:14.842Z
Learning: 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: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T00:18:21.282Z
Learning: 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: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T00:18:21.282Z
Learning: 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
async function localFetch(
urlPath: string,
options?: RequestInit,
maxRedirects: number = 5,
): Promise<Response> {
const url = new URL(urlPath, baseUrl)
const request = new Request(url, options)
const response = await fetch(request)

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}`)
}
}

return response
}
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 next localFetch 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' prefix
and 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. Change
the branch that handles local redirects to detect http(s)://localhost (with
optional port), construct a URL object from location (or relative to baseUrl if
needed), build a new path using url.pathname + url.search + url.hash, and call
localFetch with that new path; keep the existing maxRedirects decrement and
warning 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`)
await new Promise((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 uses prerenderOptions.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 uses
prerenderOptions.retryDelay; update the logger to reflect the actual value (e.g.
use the retryDelay variable or prerenderOptions.retryDelay) so the message
matches the real delay, and ensure retryDelay has a sensible default when
undefined before logging.

Copy link
Contributor

@coderabbitai coderabbitai bot 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 that ChildProcess should be sorted before spawn 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 between 8162cc6 and 6c19468.

📒 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 neither builder nor outputDir 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 the finally block.


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

The check res.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 for res.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 the finally block regardless of success or failure.


348-369: LGTM!

Clean update to accept an optional configFile parameter with proper error wrapping.

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

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

The setupPrerenderConfig helper cleanly consolidates the prerender configuration logic that was previously duplicated, enabling reuse between postServerBuild and postServerBuildForNitro.


44-70: LGTM!

Clean refactoring with the new skipPrerender 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 of postServerBuild while using Nitro-specific parameters (outputDir, nitroServerPath) instead of the Vite builder. The reuse of setupPrerenderConfig ensures consistent behavior across both paths.

Comment on lines +288 to +303
async function startNitroServer(
serverPath: string,
): Promise<{ url: URL; close: () => Promise<void> }> {
return new Promise((resolve, reject) => {
// Find a random port
const port = 3000 + Math.floor(Math.random() * 10000)
const env = { ...process.env, PORT: String(port) }

const child: 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 file
find . -type f -name "prerender.ts" | head -20

Repository: TanStack/router

Length of output: 226


🏁 Script executed:

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

Repository: TanStack/router

Length of output: 108


🏁 Script executed:

# Read the complete function starting from line 280
sed -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 dependency
cat ./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 function
rg -n "startNitroServer" ./packages/start-plugin-core/src/prerender.ts

Repository: TanStack/router

Length of output: 165


🏁 Script executed:

# Check the context where startNitroServer is called
sed -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 elsewhere
rg -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 available
head -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 at http://localhost:3000" or "Listening on http://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 tests
grep -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 function
find ./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 mode
find ./packages/start-plugin-core -name "tsconfig.json" | xargs cat 2>/dev/null | head -50

Repository: TanStack/router

Length of output: 283


🏁 Script executed:

# Check root tsconfig
cat ./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: Setting stdio: '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. Using stdio: '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 using port: 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 like get-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 the
child 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 as
using get-port to pick an available port before spawning (or, if you choose port
0, implement a mechanism to read the OS-assigned port from Nitro startup output
or via IPC) so you fail fast on port collisions and can determine the actual
bound port.

Copy link
Contributor

@coderabbitai coderabbitai bot 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 the get-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.

The process.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 where process.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.

The nitro: any parameter and untyped access to nitro.options.output.dir, nitro.options.output.publicDir, and nitro.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 between 14f3b45 and ca09453.

📒 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 for nitroServerPath 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.

Adding viteConfigFile to ResolvedStartConfig provides the necessary context for the Nitro prerender flow to invoke vite.preview() with the correct configuration.

Also applies to: 80-80


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

The || undefined coercion correctly converts Vite's false (no config file) to undefined, aligning with the expected type in ResolvedStartConfig.


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

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

Comment on lines 88 to 91

// Wait for the server to be ready (handles Nitro's child process startup time)
await waitForServerReady(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 the else branch where we're using Vite preview, not Nitro. The Nitro server path (lines 73-77) already handles readiness internally via polling in startNitroServer.

-    // 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)
await waitForServerReady(baseUrl, logger)
}
// Wait for the Vite preview server to be ready
await waitForServerReady(baseUrl, logger)
}
🤖 Prompt for AI Agents
In packages/start-plugin-core/src/prerender.ts around lines 88 to 91, the inline
comment incorrectly states it "handles Nitro's child process startup time" while
this branch uses Vite preview; update the comment to accurately describe that
waitForServerReady is used for the Vite preview server readiness (or remove the
misleading reference to Nitro), e.g. mention Vite preview/startup latency or
remove Nitro-specific wording so comments reflect the actual code path.

Copy link
Contributor

@coderabbitai coderabbitai bot 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 when nitroServerPath 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 set stdio: ['ignore', 'pipe', 'pipe'] but never consume child.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:

```ts
const originalExit = process.exit
process.exit = (() => {}) as typeof process.exit

process.emit('SIGHUP', 'SIGHUP')

process.exit = originalExit
await new Promise((resolve) => setTimeout(resolve, 100))
return originalClose()

If any handler attached to SIGHUP throws synchronously, process.exit will never be restored. Wrapping the override in a try/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()
+      }
  • The hasNitroPlugin detection by p.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 that startNitroServer has its own polling logic with similar behavior, you might consider reusing waitForServerReady there (accepting a URL instead of re‑encoding http://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 between ca09453 and b17e4b6.

📒 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
async function localFetch(
path: string,
fetchPath: string,
options?: RequestInit,
maxRedirects: number = 5,
): Promise<Response> {
const url = new URL(path, baseUrl)
const url = new URL(fetchPath, baseUrl)
const request = new Request(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 for http://localhost:<port>/… is broken.

In localFetch, when following redirects you do:

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

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

Suggested fix:

  • Treat absolute http://localhost[:port] redirects by parsing and reusing only the path/query/hash, and keep the host/port from baseUrl.
  • 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 new headers: prerenderOptions.headers usage looks good and preserves the ability to customize per‑request headers.

Also applies to: 106-113, 195-201

@birkskyum birkskyum marked this pull request as draft November 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
Member Author

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

@xentobias
Copy link

@birkskyum thanks for your work on this. Is there an existing issue we can use to track the status of implementing the fetch method in Nitro 3?

@birkskyum birkskyum force-pushed the nitro-with-vite-preview branch from b17e4b6 to e6f7f47 Compare December 26, 2025 17:00
@birkskyum
Copy link
Member Author

birkskyum commented Dec 26, 2025

@theoludwig @xentobias this feature is tracked in the Nitro repo as the "Support prerendering" check box in this ticket:

@birkskyum birkskyum force-pushed the nitro-with-vite-preview branch from 00b4d6f to 9bf435d Compare December 27, 2025 20:26
@birkskyum birkskyum force-pushed the nitro-with-vite-preview branch from 54420d4 to 7b76b61 Compare January 2, 2026 11:47
@birkskyum birkskyum marked this pull request as ready for review January 2, 2026 13:06
Copy link
Contributor

@coderabbitai coderabbitai bot 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 (4)
e2e/react-start/basic-nitro-spa/src/routes/index.tsx (1)

9-14: Server function implementation looks good.

The createServerFn().handler pattern correctly returns data for the loader. For stricter type safety, you could optionally add an explicit return type annotation, but type inference works fine here.

🔎 Optional: Add explicit return type for stricter typing
-const getData = createServerFn().handler(() => {
+const getData = createServerFn().handler((): { message: string; timestamp: string } => {
   return {
     message: 'Hello from Nitro server!',
     timestamp: new Date().toISOString(),
   }
 })
e2e/react-start/basic-nitro-spa/src/components/DefaultCatchBoundary.tsx (1)

27-27: Consider extracting the repeated button className.

The same className string appears three times (lines 27, 34, 41). Extracting it to a constant would improve maintainability and reduce duplication.

🔎 Suggested refactor
+const buttonClassName = 'px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold'
+
 export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
   const router = useRouter()
   const isRoot = useMatch({
     strict: false,
     select: (state) => state.id === rootRouteId,
   })
 
   console.error(error)
 
   return (
     <div className="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6">
       <ErrorComponent error={error} />
       <div className="flex gap-2 items-center flex-wrap">
         <button
           onClick={() => {
             router.invalidate()
           }}
-          className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
+          className={buttonClassName}
         >
           Try Again
         </button>
         {isRoot ? (
           <Link
             to="/"
-            className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
+            className={buttonClassName}
           >
             Home
           </Link>
         ) : (
           <Link
             to="/"
-            className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
+            className={buttonClassName}
             onClick={(e) => {
               e.preventDefault()
               window.history.back()
             }}
           >
             Go Back
           </Link>
         )}
       </div>
     </div>
   )
 }

Also applies to: 34-34, 41-41

e2e/react-start/basic-nitro/playwright.config.ts (1)

17-44: Well-structured Playwright configuration.

The configuration follows best practices:

  • Good explanatory comment about using Node directly instead of vite preview
  • Correct reuseExistingServer pattern for CI environments (based on learnings)
  • Proper baseURL and port setup
  • Appropriate stdout piping for visibility

Consider adding a brief comment explaining why workers: 1 is used (e.g., to avoid port conflicts or shared state issues in e2e tests):

🔎 Optional: Add clarifying comment for workers setting
 export default defineConfig({
   testDir: './tests',
+  // Single worker to avoid port conflicts during e2e testing
   workers: 1,
e2e/react-start/basic-nitro-spa/vite.config.v2.ts (1)

6-21: Configuration structure is correct; consider adding documentation.

The plugin configuration correctly mirrors the v3 setup while using nitroV2Plugin(). The structure and ordering are appropriate for SPA prerendering with Nitro v2 integration.

📝 Consider adding a file header comment to document the v2/v3 split

Adding a brief comment at the top of both configuration files would improve maintainability by explaining why parallel v2 and v3 configs exist:

+/**
+ * Vite configuration for Nitro v2 e2e tests.
+ * See vite.config.v3.ts for the Nitro v3 equivalent.
+ */
 import { defineConfig } from 'vite'

This helps future maintainers understand the intentional duplication.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b17e4b6 and 99e6322.

⛔ Files ignored due to path filters (14)
  • 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
📒 Files selected for processing (30)
  • 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/src/components/DefaultCatchBoundary.tsx
  • e2e/react-start/basic-nitro-spa/src/components/NotFound.tsx
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/router.tsx
  • e2e/react-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro-spa/src/routes/index.tsx
  • e2e/react-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/react-start/basic-nitro-spa/src/styles/app.css
  • e2e/react-start/basic-nitro-spa/src/utils/seo.ts
  • e2e/react-start/basic-nitro-spa/tests/app.spec.ts
  • e2e/react-start/basic-nitro-spa/tsconfig.json
  • e2e/react-start/basic-nitro-spa/vite.config.v2.ts
  • e2e/react-start/basic-nitro-spa/vite.config.v3.ts
  • 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/src/components/DefaultCatchBoundary.tsx
  • e2e/react-start/basic-nitro/src/components/NotFound.tsx
  • e2e/react-start/basic-nitro/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro/src/router.tsx
  • e2e/react-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/src/routes/index.tsx
✅ Files skipped from review due to trivial changes (2)
  • e2e/react-start/basic-nitro/.prettierignore
  • e2e/react-start/basic-nitro-spa/.gitignore
🚧 Files skipped from review as they are similar to previous changes (18)
  • e2e/react-start/basic-nitro-spa/src/router.tsx
  • e2e/react-start/basic-nitro-spa/tsconfig.json
  • e2e/react-start/basic-nitro/src/router.tsx
  • e2e/react-start/basic-nitro-spa/playwright.config.ts
  • e2e/react-start/basic-nitro-spa/package.json
  • e2e/react-start/basic-nitro-spa/src/routes/static.tsx
  • e2e/react-start/basic-nitro/public/site.webmanifest
  • e2e/react-start/basic-nitro/src/components/DefaultCatchBoundary.tsx
  • e2e/react-start/basic-nitro/src/routes/index.tsx
  • e2e/react-start/basic-nitro-spa/tests/app.spec.ts
  • e2e/react-start/basic-nitro-spa/src/components/NotFound.tsx
  • e2e/react-start/basic-nitro/package.json
  • e2e/react-start/basic-nitro/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/styles/app.css
  • e2e/react-start/basic-nitro-spa/src/utils/seo.ts
  • e2e/react-start/basic-nitro-spa/public/site.webmanifest
  • e2e/react-start/basic-nitro/src/routes/__root.tsx
  • e2e/react-start/basic-nitro/.gitignore
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety for all code

Files:

  • e2e/react-start/basic-nitro/src/components/NotFound.tsx
  • e2e/react-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro-spa/vite.config.v3.ts
  • e2e/react-start/basic-nitro-spa/vite.config.v2.ts
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/routes/index.tsx
  • e2e/react-start/basic-nitro/playwright.config.ts
  • e2e/react-start/basic-nitro-spa/src/components/DefaultCatchBoundary.tsx
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Implement ESLint rules for router best practices using the ESLint plugin router

Files:

  • e2e/react-start/basic-nitro/src/components/NotFound.tsx
  • e2e/react-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro-spa/vite.config.v3.ts
  • e2e/react-start/basic-nitro-spa/vite.config.v2.ts
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/routes/index.tsx
  • e2e/react-start/basic-nitro/playwright.config.ts
  • e2e/react-start/basic-nitro-spa/src/components/DefaultCatchBoundary.tsx
🧠 Learnings (10)
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: 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-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: 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-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/routes/index.tsx
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.

Applied to files:

  • e2e/react-start/basic-nitro-spa/src/routes/__root.tsx
  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{js,ts,tsx} : Implement ESLint rules for router best practices using the ESLint plugin router

Applied to files:

  • e2e/react-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/index.tsx
📚 Learning: 2025-10-14T18:59:33.990Z
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: 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-11-02T16:16:24.898Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5732
File: packages/start-client-core/src/client/hydrateStart.ts:6-9
Timestamp: 2025-11-02T16:16:24.898Z
Learning: 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-spa/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/routes/index.tsx
📚 Learning: 2025-12-21T12:52:35.231Z
Learnt from: Sheraff
Repo: TanStack/router PR: 6171
File: packages/router-core/src/new-process-route-tree.ts:898-898
Timestamp: 2025-12-21T12:52:35.231Z
Learning: In `packages/router-core/src/new-process-route-tree.ts`, the matching logic intentionally allows paths without trailing slashes to match index routes with trailing slashes (e.g., `/a` can match `/a/` route), but not vice-versa (e.g., `/a/` cannot match `/a` layout route). This is implemented via the condition `!pathIsIndex || node.kind === SEGMENT_TYPE_INDEX` and is a deliberate design decision to provide better UX by being permissive with missing trailing slashes.

Applied to files:

  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Use file-based routing in `src/routes/` directories or code-based routing with route definitions

Applied to files:

  • e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts
  • e2e/react-start/basic-nitro-spa/src/routes/index.tsx
📚 Learning: 2025-12-25T13:04:55.492Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 6215
File: e2e/react-start/custom-basepath/package.json:13-17
Timestamp: 2025-12-25T13:04:55.492Z
Learning: In the TanStack Router repository, e2e test scripts are specifically designed to run in CI (which uses a Unix environment), so Unix-specific commands (like `rm -rf`, `&` for backgrounding, and direct environment variable assignments without `cross-env`) are acceptable in e2e test npm scripts.

Applied to files:

  • e2e/react-start/basic-nitro/playwright.config.ts
📚 Learning: 2025-10-09T12:59:14.842Z
Learnt from: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/public/site.webmanifest:2-3
Timestamp: 2025-10-09T12:59:14.842Z
Learning: 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/playwright.config.ts
🧬 Code graph analysis (1)
e2e/react-start/basic-nitro-spa/vite.config.v2.ts (1)
packages/nitro-v2-vite-plugin/src/index.ts (1)
  • nitroV2Plugin (19-142)
🔇 Additional comments (13)
e2e/react-start/basic-nitro-spa/src/routeTree.gen.ts (1)

1-86: Skipping review of autogenerated file.

This file is autogenerated by TanStack Router and should not be manually modified or reviewed. Based on learnings, files named routeTree.gen.ts are generated artifacts that will be overwritten by the router generator.

e2e/react-start/basic-nitro/postcss.config.mjs (1)

1-5: LGTM! Standard PostCSS configuration for Tailwind.

This configuration follows the modern approach for integrating Tailwind CSS with PostCSS. The empty options object appropriately uses Tailwind's default configuration.

e2e/react-start/basic-nitro-spa/postcss.config.mjs (1)

1-5: LGTM! Consistent PostCSS configuration.

This configuration is identical to the one in e2e/react-start/basic-nitro/postcss.config.mjs, which provides consistency across the Nitro e2e test variants. The setup correctly enables Tailwind CSS processing for the SPA variant.

e2e/react-start/basic-nitro-spa/src/routes/index.tsx (2)

1-7: LGTM! Clean route setup following TanStack Router patterns.

The file-based route definition correctly uses createFileRoute('/') with a loader that fetches server data and a component to render it.


16-26: LGTM! Well-structured component with proper e2e test IDs.

The Home component correctly consumes loader data via Route.useLoaderData() and includes appropriate data-testid attributes for end-to-end testing.

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

1-13: LGTM! All necessary imports are present and correctly structured.

The Vite client types reference and TanStack Router primitives are properly imported. The tilde alias for local imports is consistent with project conventions.


15-38: LGTM! Root route configuration is complete and well-structured.

The root route properly configures:

  • Document head with SEO meta tags and stylesheet links
  • Error and not-found boundaries
  • Custom shell component for HTML structure

All settings follow TanStack Router best practices.


40-73: LGTM! RootDocument shell component is properly implemented.

The HTML shell correctly:

  • Injects head content and scripts via TanStack Router primitives
  • Implements navigation with active link styling
  • Uses exact: true on the Home link to prevent it from matching all paths
  • Positions devtools for development convenience
  • Properly types children as React.ReactNode

The structure is compatible with React 19.2 and follows TanStack Router conventions.

e2e/react-start/basic-nitro/playwright.config.ts (2)

3-3: No action needed. Import attributes syntax (with { type: 'json' }) is already widely used throughout the codebase (in multiple playwright.config.ts and test files), and the project's TypeScript configuration (5.9.0 with module: "ESNext", target: "ES2022", and resolveJsonModule: true) fully supports this syntax. This code follows an established project pattern and presents no compatibility concerns.

Likely an incorrect or invalid review comment.


7-12: NITRO_VARIANT setup and build scripts are correctly configured.

Verification confirms:

  • NITRO_VARIANT is properly set via test:e2e:v2 and test:e2e:v3 npm scripts in package.json
  • Both build:v2 and build:v3 scripts exist and are implemented
  • TypeScript strict mode is enabled in tsconfig.json

The validation pattern ensures tests fail fast with a clear error if the environment variable is misconfigured.

e2e/react-start/basic-nitro-spa/vite.config.v3.ts (2)

1-4: Imports look clean.

The import structure is straightforward and follows standard Vite configuration patterns.


6-21: Code configuration is correct for SPA prerendering with Nitro v3.

The vite configuration properly sets up Nitro for SPA prerendering. Nitro v3 is available (v3.0.1-alpha.1+) with fetch method support already implemented, so no version or API compatibility concerns remain.

e2e/react-start/basic-nitro-spa/vite.config.v2.ts (1)

1-4: Correct imports for Nitro v2 integration.

The v2 configuration properly uses the TanStack-provided nitroV2Plugin wrapper instead of the native Nitro plugin. This is appropriate for maintaining backward compatibility during the transition to Nitro v3.

Comment on lines +39 to +48
<Link
to="/"
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
onClick={(e) => {
e.preventDefault()
window.history.back()
}}
>
Go Back
</Link>
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

Use a button element instead of Link for the "Go Back" action.

The Link component with to="/" generates an href="/" attribute, but the onClick handler prevents default navigation and uses window.history.back() instead. This creates several issues:

  • Accessibility: Screen readers will announce this as a link to the home page ("/"), but it actually navigates backward in history.
  • User experience: Right-click "Open in new tab", middle-click, or keyboard navigation could navigate to "/" instead of going back.
  • Semantics: A non-navigation action (going back in history) should use a button element, not a Link.
🔎 Recommended fix
-        <Link
-          to="/"
+        <button
+          onClick={() => {
+            window.history.back()
+          }}
           className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
-          onClick={(e) => {
-            e.preventDefault()
-            window.history.back()
-          }}
         >
           Go Back
-        </Link>
+        </button>
🤖 Prompt for AI Agents
In e2e/react-start/basic-nitro-spa/src/components/DefaultCatchBoundary.tsx
around lines 39–48, replace the Link element used for the "Go Back" action with
a semantic button: remove the to="/" href, render a <button type="button"> that
keeps the same className/styling, attach the onClick handler to call
window.history.back(), and include an accessible label (e.g. aria-label="Go
back") if the visible text is not sufficient; ensure you no longer call
e.preventDefault() since there is no link navigation to prevent.

@@ -0,0 +1,25 @@
import { Link } from '@tanstack/react-router'

export function NotFound({ children }: { children?: any }) {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace any type with React.ReactNode for strict type safety.

The children prop is typed as any, which violates the coding guideline to use TypeScript strict mode with extensive type safety. Use React.ReactNode instead.

🔎 Proposed fix
-export function NotFound({ children }: { children?: any }) {
+export function NotFound({ children }: { children?: React.ReactNode }) {

As per coding guidelines, TypeScript files must use strict mode with extensive type safety.

📝 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
export function NotFound({ children }: { children?: any }) {
export function NotFound({ children }: { children?: React.ReactNode }) {
🤖 Prompt for AI Agents
In e2e/react-start/basic-nitro/src/components/NotFound.tsx around line 3, the
children prop is typed as any; replace that with React.ReactNode to satisfy
strict TypeScript typing. Import React if not already imported (or use the React
namespace available in JSX settings), update the function signature to use
children?: React.ReactNode, and ensure no other uses rely on any; run typecheck
to confirm no further errors.

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

Projects

None yet

4 participants