You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Static Server Function runs on every page visit, not only at build time #6787
When I build a route using the staticFunctionMiddleware middleware with a Static Server Function, I encounter the following problem. The Static Server Function is executed every time a page is visited. This means that any static data you want to cache from a CMS, for example, is fetched again each time the page is opened in the browser. This behaviour is different from that described in the documentation:
Static server functions are server functions that are executed at build time and cached as static assets when using prerendering/static-generation
And I activated pre-rendering like mentioned in the docs, see my vite.config.ts:
exportdefaultdefineConfig({plugins: [tanstackStart({prerender: {enabled: true}}),nitro({publicAssets: [// This is a temporary fix to https://github.com/TanStack/router/issues/5368{dir: "dist/client/__tsr",baseURL: "/__tsr",maxAge: 0}]}),react()]});
I expect that the Static Server Function is only executed during the build process. After the build, TanStack Start should serve the cached output of the Static Server Function and should never execute the Static Server Function on page load.
@fullheart Hi, I encounter a similar issue in my project, and I notice that the tanstack prerender data is generated in dist after nitro build finished, and the configuration below only works when dist already have the prerender data.
nitro({publicAssets: [// This is a temporary fix to https://github.com/TanStack/router/issues/5368{dir: "dist/client/__tsr",baseURL: "/__tsr",maxAge: 0}]}),
So we may need to manually copy the prerender data into the nitro output directory : (
I notice that TanstackStart provide a hook that works when prerender success, here is the code. I wish it can be helpful to you !
importpathfrom"node:path/posix";import{nitro}from"nitro/vite";// ... other codetanstackStart({prerender: {enabled: true,crawlLinks: false,onSuccess: async()=>{constfs=awaitimport("node:fs/promises");constsrc=path.resolve("dist/client/__tsr");constdest=path.resolve(".output/public/__tsr");try{awaitfs.cp(src,dest,{recursive: true});console.log("[fix] Copied __tsr after prerender");}catch{console.warn("[fix] __tsr not found");}},},}),
curl -i http://localhost:3000/does-not-exist
# 500 — and the server log prints:
[6787] handler executed (NODE_ENV=production, TSS_CLIENT_OUTPUT_DIR=unset)
Cause: .server() calls ctx.next() unconditionally — TSS_CLIENT_OUTPUT_DIR only gates writing the cache, and only the client half ever reads it. So every server-side request executes the handler; even the prerendered /page answers 500 once the CMS is gone.
Here's the fix we've been running in production, as a patch-package file — save it as patches/@tanstack+start-static-server-functions+1.167.29.patch and run npx patch-package:
diff --git a/node_modules/@tanstack/start-static-server-functions/dist/esm/staticFunctionMiddleware.js b/node_modules/@tanstack/start-static-server-functions/dist/esm/staticFunctionMiddleware.js
index c6fecf9..d65905f 100644
--- a/node_modules/@tanstack/start-static-server-functions/dist/esm/staticFunctionMiddleware.js+++ b/node_modules/@tanstack/start-static-server-functions/dist/esm/staticFunctionMiddleware.js@@ -76,6 +76,16 @@ var staticFunctionMiddleware = createMiddleware({ type: "function" }).client(asy
}
return ctx.next();
}).server(async (ctx) => {
+ // PATCHED — https://github.com/TanStack/router/issues/6787+ // "Static server functions are executed at build time" is the whole contract, but upstream+ // runs the handler on every live request too. Only the build sets TSS_CLIENT_OUTPUT_DIR, so+ // a call arriving without it is a request for an address the build never rendered: answer+ // nothing and let the route decide, rather than execute a handler that was never meant to+ // run at request time.+ if (process.env.NODE_ENV === "production" && !process.env.TSS_CLIENT_OUTPUT_DIR) return {+ result: void 0,+ context: ctx.context+ };
const response = await ctx.next();
if (process.env.NODE_ENV === "production" && process.env.TSS_CLIENT_OUTPUT_DIR) await addItemToCache({
functionId: ctx.serverFnMeta.id,
With that, /does-not-exist answers 404 with zero handler executions. One thing to weigh if this becomes the default: it removes the current silent live fallback — which matches the docs, but is a behaviour change.
For some reason, the package chooses to ignore the existing assets during SSR, which causes the call into the server middleware. This, in turn, causes an uncoditional call into the server function.
I only want to run the server function during the build and prerender step and never at runtime. The patch above helps me get that behaviour.
Activity
floatDreamWithSong commentedon Mar 17, 2026
@fullheart Hi, I encounter a similar issue in my project, and I notice that the tanstack prerender data is generated in
distafter nitro build finished, and the configuration below only works whendistalready have the prerender data.So we may need to manually copy the prerender data into the nitro output directory : (
I notice that TanstackStart provide a hook that works when prerender success, here is the code. I wish it can be helpful to you !
fullheart commentedon Mar 18, 2026
@floatDreamWithSong Wow, thanks for your workaround. I like your solution more than mine. I'll give it a try.
fullheart commentedon Mar 18, 2026
@tannerlinsley Does your PR #6940 also solve the problem I described in this ticket?
fullheart commentedon Apr 22, 2026
This problem seems to have been resolved in the latest version (
"@tanstack/react-start": "^1.167.42").I've removed my workarounds and it's working; see the sample app here: https://github.com/fullheart/tanstack-start-issue-selective-ssg/tree/main/ssg-working
But I have still a problem with SSG. see new issue: #7239
fullheart commentedon Aug 19, 2026
Correcting my "seems resolved" above: only the serving side was fixed. The handler still executes at request time on current versions (1.167.29).
Reproducer (v2 of the original, no Docker): https://github.com/fullheart/tanstack-start-issue-6787-v2
The mock CMS only runs during
npm run build. Afterwards:Cause:
.server()callsctx.next()unconditionally —TSS_CLIENT_OUTPUT_DIRonly gates writing the cache, and only the client half ever reads it. So every server-side request executes the handler; even the prerendered/pageanswers 500 once the CMS is gone.Here's the fix we've been running in production, as a
patch-packagefile — save it aspatches/@tanstack+start-static-server-functions+1.167.29.patchand runnpx patch-package:With that,
/does-not-existanswers 404 with zero handler executions. One thing to weigh if this becomes the default: it removes the current silent live fallback — which matches the docs, but is a behaviour change.Ugzuzg commentedon Oct 4, 2026
I'm using this patch, which isn't necessarily correct, but works for my use case.
For some reason, the package chooses to ignore the existing assets during SSR, which causes the call into the server middleware. This, in turn, causes an uncoditional call into the server function.
I only want to run the server function during the build and prerender step and never at runtime. The patch above helps me get that behaviour.