Skip to content

Commit d637152

Browse files
fix: write static server function cache to correct output directory with Nitro (#6940)
* fix: write static server function cache to correct output directory with Nitro TSS_CLIENT_OUTPUT_DIR was baked in via Vite's define at config time, before Nitro's configEnvironment hook changed the client build.outDir. This caused staticServerFnCache files to be written to dist/client/ instead of .output/public/. - Remove TSS_CLIENT_OUTPUT_DIR from compile-time define in start-plugin-core - Set it as a runtime env var in prerender.ts using the resolved output dir - Guard server-side cache writes on TSS_CLIENT_OUTPUT_DIR being set - Add e2e test for static server functions with Nitro * fix: remove dynamic Link to nonexistent route to fix tsc --noEmit
1 parent 940151c commit d637152

18 files changed

Lines changed: 1018 additions & 2 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@tanstack/start-plugin-core': patch
3+
'@tanstack/start-static-server-functions': patch
4+
---
5+
6+
fix: write static server function cache to correct output directory when using Nitro
7+
8+
`TSS_CLIENT_OUTPUT_DIR` was baked in via Vite's `define` at config time, before Nitro's `configEnvironment` hook changed the client `build.outDir`. This caused `staticServerFnCache` files to be written to `dist/client/` instead of the Nitro-managed `.output/public/` directory.
9+
10+
Now `TSS_CLIENT_OUTPUT_DIR` is set as a runtime environment variable during prerendering using the resolved client output directory, so it correctly reflects any output directory changes made by deployment adapters like Nitro.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
node_modules
2+
package-lock.json
3+
yarn.lock
4+
5+
.DS_Store
6+
.cache
7+
.env
8+
.vercel
9+
.output
10+
/build/
11+
/api/
12+
/server/build
13+
/public/build
14+
/test-results/
15+
/playwright-report/
16+
/blob-report/
17+
/playwright/.cache/
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
**/build
2+
**/public
3+
pnpm-lock.yaml
4+
routeTree.gen.ts
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "tanstack-react-start-e2e-static-server-functions",
3+
"private": true,
4+
"sideEffects": false,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite dev --port 3000",
8+
"dev:e2e": "vite dev",
9+
"build": "vite build && tsc --noEmit",
10+
"preview": "vite preview",
11+
"start": "node .output/server/index.mjs",
12+
"test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
13+
},
14+
"dependencies": {
15+
"@tanstack/react-router": "workspace:*",
16+
"@tanstack/react-start": "workspace:*",
17+
"@tanstack/start-static-server-functions": "workspace:*",
18+
"nitro": "^3.0.1-alpha.2",
19+
"react": "^19.0.0",
20+
"react-dom": "^19.0.0"
21+
},
22+
"devDependencies": {
23+
"@playwright/test": "^1.50.1",
24+
"@tanstack/router-e2e-utils": "workspace:*",
25+
"@types/node": "^22.10.2",
26+
"@types/react": "^19.0.8",
27+
"@types/react-dom": "^19.0.3",
28+
"@vitejs/plugin-react": "^4.3.4",
29+
"typescript": "^5.7.2",
30+
"vite": "^7.3.1",
31+
"vite-tsconfig-paths": "^5.1.4"
32+
}
33+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { defineConfig, devices } from '@playwright/test'
2+
import { getTestServerPort } from '@tanstack/router-e2e-utils'
3+
import packageJson from './package.json' with { type: 'json' }
4+
5+
const PORT = await getTestServerPort(packageJson.name)
6+
const baseURL = `http://localhost:${PORT}`
7+
8+
export default defineConfig({
9+
testDir: './tests',
10+
workers: 1,
11+
reporter: [['line']],
12+
13+
use: {
14+
baseURL,
15+
},
16+
17+
webServer: {
18+
command: `pnpm build && PORT=${PORT} pnpm start`,
19+
url: baseURL,
20+
reuseExistingServer: !process.env.CI,
21+
stdout: 'pipe',
22+
},
23+
24+
projects: [
25+
{
26+
name: 'chromium',
27+
use: { ...devices['Desktop Chrome'] },
28+
},
29+
],
30+
})
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/* eslint-disable */
2+
3+
// @ts-nocheck
4+
5+
// noinspection JSUnusedGlobalSymbols
6+
7+
// This file was automatically generated by TanStack Router.
8+
// You should NOT make any changes in this file as it will be overwritten.
9+
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10+
11+
import { Route as rootRouteImport } from './routes/__root'
12+
import { Route as PostsRouteImport } from './routes/posts'
13+
import { Route as IndexRouteImport } from './routes/index'
14+
import { Route as PostsIndexRouteImport } from './routes/posts.index'
15+
16+
const PostsRoute = PostsRouteImport.update({
17+
id: '/posts',
18+
path: '/posts',
19+
getParentRoute: () => rootRouteImport,
20+
} as any)
21+
const IndexRoute = IndexRouteImport.update({
22+
id: '/',
23+
path: '/',
24+
getParentRoute: () => rootRouteImport,
25+
} as any)
26+
const PostsIndexRoute = PostsIndexRouteImport.update({
27+
id: '/',
28+
path: '/',
29+
getParentRoute: () => PostsRoute,
30+
} as any)
31+
32+
export interface FileRoutesByFullPath {
33+
'/': typeof IndexRoute
34+
'/posts': typeof PostsRouteWithChildren
35+
'/posts/': typeof PostsIndexRoute
36+
}
37+
export interface FileRoutesByTo {
38+
'/': typeof IndexRoute
39+
'/posts': typeof PostsIndexRoute
40+
}
41+
export interface FileRoutesById {
42+
__root__: typeof rootRouteImport
43+
'/': typeof IndexRoute
44+
'/posts': typeof PostsRouteWithChildren
45+
'/posts/': typeof PostsIndexRoute
46+
}
47+
export interface FileRouteTypes {
48+
fileRoutesByFullPath: FileRoutesByFullPath
49+
fullPaths: '/' | '/posts' | '/posts/'
50+
fileRoutesByTo: FileRoutesByTo
51+
to: '/' | '/posts'
52+
id: '__root__' | '/' | '/posts' | '/posts/'
53+
fileRoutesById: FileRoutesById
54+
}
55+
export interface RootRouteChildren {
56+
IndexRoute: typeof IndexRoute
57+
PostsRoute: typeof PostsRouteWithChildren
58+
}
59+
60+
declare module '@tanstack/react-router' {
61+
interface FileRoutesByPath {
62+
'/posts': {
63+
id: '/posts'
64+
path: '/posts'
65+
fullPath: '/posts'
66+
preLoaderRoute: typeof PostsRouteImport
67+
parentRoute: typeof rootRouteImport
68+
}
69+
'/': {
70+
id: '/'
71+
path: '/'
72+
fullPath: '/'
73+
preLoaderRoute: typeof IndexRouteImport
74+
parentRoute: typeof rootRouteImport
75+
}
76+
'/posts/': {
77+
id: '/posts/'
78+
path: '/'
79+
fullPath: '/posts/'
80+
preLoaderRoute: typeof PostsIndexRouteImport
81+
parentRoute: typeof PostsRoute
82+
}
83+
}
84+
}
85+
86+
interface PostsRouteChildren {
87+
PostsIndexRoute: typeof PostsIndexRoute
88+
}
89+
90+
const PostsRouteChildren: PostsRouteChildren = {
91+
PostsIndexRoute: PostsIndexRoute,
92+
}
93+
94+
const PostsRouteWithChildren = PostsRoute._addFileChildren(PostsRouteChildren)
95+
96+
const rootRouteChildren: RootRouteChildren = {
97+
IndexRoute: IndexRoute,
98+
PostsRoute: PostsRouteWithChildren,
99+
}
100+
export const routeTree = rootRouteImport
101+
._addFileChildren(rootRouteChildren)
102+
._addFileTypes<FileRouteTypes>()
103+
104+
import type { getRouter } from './router.tsx'
105+
import type { createStart } from '@tanstack/react-start'
106+
declare module '@tanstack/react-start' {
107+
interface Register {
108+
ssr: true
109+
router: Awaited<ReturnType<typeof getRouter>>
110+
}
111+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { createRouter } from '@tanstack/react-router'
2+
import { routeTree } from './routeTree.gen'
3+
4+
export function getRouter() {
5+
const router = createRouter({
6+
routeTree,
7+
scrollRestoration: true,
8+
defaultPreloadStaleTime: 0,
9+
})
10+
11+
return router
12+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/// <reference types="vite/client" />
2+
import * as React from 'react'
3+
import {
4+
HeadContent,
5+
Link,
6+
Outlet,
7+
Scripts,
8+
createRootRoute,
9+
} from '@tanstack/react-router'
10+
11+
export const Route = createRootRoute({
12+
head: () => ({
13+
meta: [
14+
{ charSet: 'utf-8' },
15+
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
16+
{ title: 'Static Server Functions E2E Test' },
17+
],
18+
}),
19+
shellComponent: RootDocument,
20+
component: RootComponent,
21+
})
22+
23+
function RootComponent() {
24+
return (
25+
<div>
26+
<div className="p-2 flex gap-2 text-lg">
27+
<Link to="/" data-testid="link-home">
28+
Home
29+
</Link>
30+
<Link to="/posts" data-testid="link-posts">
31+
Posts
32+
</Link>
33+
</div>
34+
<hr />
35+
<Outlet />
36+
</div>
37+
)
38+
}
39+
40+
function RootDocument({ children }: { children: React.ReactNode }) {
41+
return (
42+
<html>
43+
<head>
44+
<HeadContent />
45+
</head>
46+
<body>
47+
{children}
48+
<Scripts />
49+
</body>
50+
</html>
51+
)
52+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Link, createFileRoute } from '@tanstack/react-router'
2+
import { createServerFn } from '@tanstack/react-start'
3+
import { staticFunctionMiddleware } from '@tanstack/start-static-server-functions'
4+
5+
const fetchIndexData = createServerFn({ method: 'GET' })
6+
.middleware([staticFunctionMiddleware])
7+
.handler(async () => {
8+
return { message: 'Hello from static server function!' }
9+
})
10+
11+
export const Route = createFileRoute('/')({
12+
loader: async () => fetchIndexData(),
13+
component: Home,
14+
})
15+
16+
function Home() {
17+
const data = Route.useLoaderData()
18+
return (
19+
<div>
20+
<h1 data-testid="index-heading">Home</h1>
21+
<p data-testid="index-message">{data.message}</p>
22+
<Link to="/posts" data-testid="link-to-posts">
23+
Go to Posts
24+
</Link>
25+
</div>
26+
)
27+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { createFileRoute } from '@tanstack/react-router'
2+
3+
export const Route = createFileRoute('/posts/')({
4+
component: PostsIndexComponent,
5+
})
6+
7+
function PostsIndexComponent() {
8+
return <div data-testid="posts-index">Select a post.</div>
9+
}

0 commit comments

Comments
 (0)