Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Copy Markdown
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
Copy Markdown
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