Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/router/framework/react/api/router/RouterEventsType.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ type RouterEvents = {
}
onInjectedHtml: {
type: 'onInjectedHtml'
promise: Promise<string>
}
onRendered: {
type: 'onRendered'
Expand Down
19 changes: 19 additions & 0 deletions e2e/react-start/streaming-ssr/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
node_modules
package-lock.json
yarn.lock

.DS_Store
.cache
.env
.vercel
.output
/build/
/api/
/server/build
/public/build
.env.sentry-build-plugin
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/dist/
31 changes: 31 additions & 0 deletions e2e/react-start/streaming-ssr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "tanstack-react-start-e2e-streaming-ssr",
"private": true,
"sideEffects": false,
"type": "module",
"scripts": {
"dev": "vite dev --port 3000",
"dev:e2e": "vite dev",
"build": "vite build && tsc --noEmit",
"start": "pnpx srvx --prod -s ../client dist/server/server.js",
"test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
},
"dependencies": {
"@tanstack/react-query": "^5.80.7",
"@tanstack/react-router": "workspace:^",
"@tanstack/react-router-ssr-query": "workspace:^",
"@tanstack/react-start": "workspace:^",
Comment on lines +15 to +17
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

Use workspace:* protocol for internal dependencies per coding guidelines.

The workspace dependencies are currently using workspace:^, but the coding guidelines specify that internal dependencies should use workspace:*.

As per coding guidelines, the workspace protocol should be workspace:* for all internal TanStack packages.

🔎 Proposed fix
     "@tanstack/react-query": "^5.80.7",
-    "@tanstack/react-router": "workspace:^",
-    "@tanstack/react-router-ssr-query": "workspace:^",
-    "@tanstack/react-start": "workspace:^",
+    "@tanstack/react-router": "workspace:*",
+    "@tanstack/react-router-ssr-query": "workspace:*",
+    "@tanstack/react-start": "workspace:*",
     "react": "^19.0.0",
   "devDependencies": {
     "@playwright/test": "^1.50.1",
-    "@tanstack/router-e2e-utils": "workspace:^",
+    "@tanstack/router-e2e-utils": "workspace:*",
     "@types/node": "^22.10.2",

Also applies to: 24-24

🤖 Prompt for AI Agents
In e2e/react-start/streaming-ssr/package.json around lines 15 to 17 (and also
line 24), the workspace dependency protocol is set to "workspace:^" but must
follow the coding guideline of using "workspace:*" for internal TanStack
packages; update the listed dependencies "@tanstack/react-router",
"@tanstack/react-router-ssr-query", "@tanstack/react-start" (and the package at
line 24) to use the "workspace:*" protocol instead of "workspace:^".

"react": "^19.0.0",
"react-dom": "^19.0.0",
"vite": "^7.1.7"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tanstack/router-e2e-utils": "workspace:^",
"@types/node": "^22.10.2",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"srvx": "^0.9.8",
"typescript": "^5.7.2"
}
}
35 changes: 35 additions & 0 deletions e2e/react-start/streaming-ssr/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { defineConfig, devices } from '@playwright/test'
import { getTestServerPort } from '@tanstack/router-e2e-utils'
import packageJson from './package.json' with { type: 'json' }

const PORT = await getTestServerPort(packageJson.name)
const baseURL = `http://localhost:${PORT}`

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
workers: 1,

reporter: [['line']],

use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL,
},

webServer: {
command: `VITE_SERVER_PORT=${PORT} pnpm build && PORT=${PORT} VITE_SERVER_PORT=${PORT} pnpm start`,
url: baseURL,
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
},
Comment on lines +22 to +27
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check usage of PORT and VITE_SERVER_PORT in the streaming-ssr project

# Search for PORT usage (excluding this config file)
echo "=== PORT usage ==="
rg -n '\bPORT\b' e2e/react-start/streaming-ssr --type ts --type tsx -g '!playwright.config.ts' 2>/dev/null || echo "No PORT matches"

echo ""
echo "=== VITE_SERVER_PORT usage ==="
rg -n 'VITE_SERVER_PORT' e2e/react-start/streaming-ssr --type ts --type tsx -g '!playwright.config.ts' 2>/dev/null || echo "No VITE_SERVER_PORT matches"

# Also check for package.json scripts
echo ""
echo "=== Check package.json for scripts ==="
find e2e/react-start/streaming-ssr -name 'package.json' -exec head -50 {} \; 2>/dev/null | grep -A 10 '"scripts"' || echo "No package.json found"

Repository: TanStack/router

Length of output: 617


Remove the unnecessary VITE_SERVER_PORT environment variable.

The VITE_SERVER_PORT variable is not used anywhere in the project and is not a standard Vite configuration variable. The PORT variable is necessary and used by srvx, which defaults to the PORT environment variable for the server's listening port. Simplify the command to:

command: `PORT=${PORT} pnpm build && pnpm start`,
🤖 Prompt for AI Agents
In e2e/react-start/streaming-ssr/playwright.config.ts around lines 22 to 27, the
webServer command sets an unused VITE_SERVER_PORT environment variable; remove
VITE_SERVER_PORT and simplify the command to only set PORT for both build and
start steps so the process uses the standard PORT env var (i.e., set
PORT=${PORT} for build and start and drop VITE_SERVER_PORT).


projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
})
261 changes: 261 additions & 0 deletions e2e/react-start/streaming-ssr/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as SyncOnlyRouteImport } from './routes/sync-only'
import { Route as StreamRouteImport } from './routes/stream'
import { Route as SlowRenderRouteImport } from './routes/slow-render'
import { Route as QueryHeavyRouteImport } from './routes/query-heavy'
import { Route as NestedDeferredRouteImport } from './routes/nested-deferred'
import { Route as ManyPromisesRouteImport } from './routes/many-promises'
import { Route as FastSerialRouteImport } from './routes/fast-serial'
import { Route as DeferredRouteImport } from './routes/deferred'
import { Route as ConcurrentRouteImport } from './routes/concurrent'
import { Route as IndexRouteImport } from './routes/index'

const SyncOnlyRoute = SyncOnlyRouteImport.update({
id: '/sync-only',
path: '/sync-only',
getParentRoute: () => rootRouteImport,
} as any)
const StreamRoute = StreamRouteImport.update({
id: '/stream',
path: '/stream',
getParentRoute: () => rootRouteImport,
} as any)
const SlowRenderRoute = SlowRenderRouteImport.update({
id: '/slow-render',
path: '/slow-render',
getParentRoute: () => rootRouteImport,
} as any)
const QueryHeavyRoute = QueryHeavyRouteImport.update({
id: '/query-heavy',
path: '/query-heavy',
getParentRoute: () => rootRouteImport,
} as any)
const NestedDeferredRoute = NestedDeferredRouteImport.update({
id: '/nested-deferred',
path: '/nested-deferred',
getParentRoute: () => rootRouteImport,
} as any)
const ManyPromisesRoute = ManyPromisesRouteImport.update({
id: '/many-promises',
path: '/many-promises',
getParentRoute: () => rootRouteImport,
} as any)
const FastSerialRoute = FastSerialRouteImport.update({
id: '/fast-serial',
path: '/fast-serial',
getParentRoute: () => rootRouteImport,
} as any)
const DeferredRoute = DeferredRouteImport.update({
id: '/deferred',
path: '/deferred',
getParentRoute: () => rootRouteImport,
} as any)
const ConcurrentRoute = ConcurrentRouteImport.update({
id: '/concurrent',
path: '/concurrent',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/concurrent': typeof ConcurrentRoute
'/deferred': typeof DeferredRoute
'/fast-serial': typeof FastSerialRoute
'/many-promises': typeof ManyPromisesRoute
'/nested-deferred': typeof NestedDeferredRoute
'/query-heavy': typeof QueryHeavyRoute
'/slow-render': typeof SlowRenderRoute
'/stream': typeof StreamRoute
'/sync-only': typeof SyncOnlyRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/concurrent': typeof ConcurrentRoute
'/deferred': typeof DeferredRoute
'/fast-serial': typeof FastSerialRoute
'/many-promises': typeof ManyPromisesRoute
'/nested-deferred': typeof NestedDeferredRoute
'/query-heavy': typeof QueryHeavyRoute
'/slow-render': typeof SlowRenderRoute
'/stream': typeof StreamRoute
'/sync-only': typeof SyncOnlyRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/concurrent': typeof ConcurrentRoute
'/deferred': typeof DeferredRoute
'/fast-serial': typeof FastSerialRoute
'/many-promises': typeof ManyPromisesRoute
'/nested-deferred': typeof NestedDeferredRoute
'/query-heavy': typeof QueryHeavyRoute
'/slow-render': typeof SlowRenderRoute
'/stream': typeof StreamRoute
'/sync-only': typeof SyncOnlyRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/concurrent'
| '/deferred'
| '/fast-serial'
| '/many-promises'
| '/nested-deferred'
| '/query-heavy'
| '/slow-render'
| '/stream'
| '/sync-only'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/concurrent'
| '/deferred'
| '/fast-serial'
| '/many-promises'
| '/nested-deferred'
| '/query-heavy'
| '/slow-render'
| '/stream'
| '/sync-only'
id:
| '__root__'
| '/'
| '/concurrent'
| '/deferred'
| '/fast-serial'
| '/many-promises'
| '/nested-deferred'
| '/query-heavy'
| '/slow-render'
| '/stream'
| '/sync-only'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ConcurrentRoute: typeof ConcurrentRoute
DeferredRoute: typeof DeferredRoute
FastSerialRoute: typeof FastSerialRoute
ManyPromisesRoute: typeof ManyPromisesRoute
NestedDeferredRoute: typeof NestedDeferredRoute
QueryHeavyRoute: typeof QueryHeavyRoute
SlowRenderRoute: typeof SlowRenderRoute
StreamRoute: typeof StreamRoute
SyncOnlyRoute: typeof SyncOnlyRoute
}

declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/sync-only': {
id: '/sync-only'
path: '/sync-only'
fullPath: '/sync-only'
preLoaderRoute: typeof SyncOnlyRouteImport
parentRoute: typeof rootRouteImport
}
'/stream': {
id: '/stream'
path: '/stream'
fullPath: '/stream'
preLoaderRoute: typeof StreamRouteImport
parentRoute: typeof rootRouteImport
}
'/slow-render': {
id: '/slow-render'
path: '/slow-render'
fullPath: '/slow-render'
preLoaderRoute: typeof SlowRenderRouteImport
parentRoute: typeof rootRouteImport
}
'/query-heavy': {
id: '/query-heavy'
path: '/query-heavy'
fullPath: '/query-heavy'
preLoaderRoute: typeof QueryHeavyRouteImport
parentRoute: typeof rootRouteImport
}
'/nested-deferred': {
id: '/nested-deferred'
path: '/nested-deferred'
fullPath: '/nested-deferred'
preLoaderRoute: typeof NestedDeferredRouteImport
parentRoute: typeof rootRouteImport
}
'/many-promises': {
id: '/many-promises'
path: '/many-promises'
fullPath: '/many-promises'
preLoaderRoute: typeof ManyPromisesRouteImport
parentRoute: typeof rootRouteImport
}
'/fast-serial': {
id: '/fast-serial'
path: '/fast-serial'
fullPath: '/fast-serial'
preLoaderRoute: typeof FastSerialRouteImport
parentRoute: typeof rootRouteImport
}
'/deferred': {
id: '/deferred'
path: '/deferred'
fullPath: '/deferred'
preLoaderRoute: typeof DeferredRouteImport
parentRoute: typeof rootRouteImport
}
'/concurrent': {
id: '/concurrent'
path: '/concurrent'
fullPath: '/concurrent'
preLoaderRoute: typeof ConcurrentRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
}
}

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ConcurrentRoute: ConcurrentRoute,
DeferredRoute: DeferredRoute,
FastSerialRoute: FastSerialRoute,
ManyPromisesRoute: ManyPromisesRoute,
NestedDeferredRoute: NestedDeferredRoute,
QueryHeavyRoute: QueryHeavyRoute,
SlowRenderRoute: SlowRenderRoute,
StreamRoute: StreamRoute,
SyncOnlyRoute: SyncOnlyRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()

import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}
18 changes: 18 additions & 0 deletions e2e/react-start/streaming-ssr/src/router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { QueryClient } from '@tanstack/react-query'
import { createRouter } from '@tanstack/react-router'
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
import { routeTree } from './routeTree.gen'

export function getRouter() {
const queryClient = new QueryClient()
const router = createRouter({
routeTree,
context: { queryClient },
scrollRestoration: true,
})
setupRouterSsrQueryIntegration({
router,
queryClient,
})
return router
}
Loading
Loading