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
staticFunctionMiddlewareUnexpected token '<', "<!DOCTYPE "... is not valid JSON #7630
bun install
bun run build
cd ./apps/website-tanstack
uncomment middleware in`apps/website-tanstack/src/serverFunctions/docs.ts`
bun run build
bun run start
navigate to /doc
I also included a demo for #7516
comment code in packages/@intlayer/design-system/src/providers/ReactQueryProvider.tsx
cd ../../
bun run build ## rebuild design systemcd ./apps/website-tanstack
bun run build
Ran into the same error and dug into it using the staticFunctionMiddleware_repro. Everything below is on the pinned versions (@tanstack/react-start@1.168.25, which resolves start-plugin-core@1.171.17 and start-static-server-functions@1.167.17), so this is not the #6940 output-directory bug - that fix is already present.
The payload file is missing, and the client middleware has no fallback when it is.
Static payloads are only written for server-function calls that actually execute during prerender (staticFunctionMiddleware.server writes only when TSS_CLIENT_OUTPUT_DIR is set, which prerenderWithVite does). In the repro build the prerender set (crawlLinks: false + explicit pages) rendered 3 pages, none of which invoke the doc server functions - so __tsr/staticServerFnCache/ is never created anywhere on disk. Any route that uses the middleware but is not covered by the prerender page set has no payload, by construction.
At runtime, the client half of the middleware fetches the payload URL and calls r.json() directly - no response.ok check, no content-type check, and no fallback to ctx.next() (staticFunctionMiddleware.js, the fetchItem call in 1.167.17). A missing payload is therefore a hard crash of the navigation instead of a degradation to a live server-function call.
The failed fetch does not even return a 404. The app's own catch-all route serves the SPA shell as 200 text/html for the missing payload path:
(the locale catch-all parsed __tsr as a locale). r.json() on that body is exactly Unexpected token '<', "<!DOCTYPE "... is not valid JSON. Because the shell comes back as 200, checking response.ok alone would not be enough - the response needs a content-type check.
Suggested fix in start-static-server-functions: in the client middleware, validate the response (ok + application/json) and fall back to ctx.next() when the payload is missing, so uncovered routes degrade to a live server-function call instead of crashing. A build-time warning when a decorated function produced zero payloads during prerender would also make the coverage gap visible.
Workaround meanwhile: ensure prerender.pages includes every route that calls a static-middleware function (including every locale variant, since the payload hash includes the serialized args), or keep the middleware off until the fallback lands.
Ran into the same error and dug into it using the staticFunctionMiddleware_repro. Everything below is on the pinned versions (@tanstack/react-start@1.168.25, which resolves start-plugin-core@1.171.17 and start-static-server-functions@1.167.17), so this is not the #6940 output-directory bug - that fix is already present.
The payload file is missing, and the client middleware has no fallback when it is.
Static payloads are only written for server-function calls that actually execute during prerender (staticFunctionMiddleware.server writes only when TSS_CLIENT_OUTPUT_DIR is set, which prerenderWithVite does). In the repro build the prerender set (crawlLinks: false + explicit pages) rendered 3 pages, none of which invoke the doc server functions - so __tsr/staticServerFnCache/ is never created anywhere on disk. Any route that uses the middleware but is not covered by the prerender page set has no payload, by construction.
At runtime, the client half of the middleware fetches the payload URL and calls r.json() directly - no response.ok check, no content-type check, and no fallback to ctx.next() (staticFunctionMiddleware.js, the fetchItem call in 1.167.17). A missing payload is therefore a hard crash of the navigation instead of a degradation to a live server-function call.
The failed fetch does not even return a 404. The app's own catch-all route serves the SPA shell as 200 text/html for the missing payload path:
(the locale catch-all parsed __tsr as a locale). r.json() on that body is exactly Unexpected token '<', "<!DOCTYPE "... is not valid JSON. Because the shell comes back as 200, checking response.ok alone would not be enough - the response needs a content-type check.
Suggested fix in start-static-server-functions: in the client middleware, validate the response (ok + application/json) and fall back to ctx.next() when the payload is missing, so uncovered routes degrade to a live server-function call instead of crashing. A build-time warning when a decorated function produced zero payloads during prerender would also make the coverage gap visible.
Workaround meanwhile: ensure prerender.pages includes every route that calls a static-middleware function (including every locale variant, since the payload hash includes the serialized args), or keep the middleware off until the fallback lands.
Payloads aren't missing here, they're just not served.
Nitro bakes its public asset manifest before TanStack's prerender runs, so everything prerender writes lands on disk after the manifest is sealed and falls through to the SSR catch-all. Hence the HTML shell with lang="__tsr".
You can confirm it by dropping any file into .output/public after a build, same result
I ran into the same crash. What happens: when the cache file for a call doesn't exist, the server has nothing to send back except the app's own HTML page — and fetchItem then tries to parse that HTML as JSON. That's the Unexpected token '<'.
In our case some calls simply never ran during prerender, so their files were never written, and every navigation that needed one of them died. As @IgnazioDS said: the client just has no fallback for a missing file.
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..50fd024 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@@ -57,7 +57,14 @@ var fetchItem = async ({ data, functionId }) => {
hash: jsonToFilenameSafeString(data)
});
let result = staticClientCache?.get(url);
- result = await fetch(url, { method: "GET" }).then((r) => r.json()).then((d) => fromJSON(d, { plugins: getDefaultSerovalPlugins() }));+ // PATCHED — https://github.com/TanStack/router/issues/7630+ // Upstream calls `r.json()` on whatever comes back. A payload the build never wrote is+ // answered by the app's own catch-all as HTML, so this threw `Unexpected token '<'` and+ // took the navigation with it. Both checks are needed: the shell can arrive as a 200.+ const response = await fetch(url, { method: "GET" });+ if (!response.ok) return void 0;+ if (!response.headers.get("content-type")?.includes("application/json")) return void 0;+ result = fromJSON(await response.json(), { plugins: getDefaultSerovalPlugins() });
return result;
};
var staticFunctionMiddleware = createMiddleware({ type: "function" }).client(async (ctx) => {
With this, a missing file no longer kills the navigation — it falls through to ctx.next(), and what happens then stays your setup's decision: live execution as today, or (for fully prerendered sites, see #6787) a clean "no data" from the server. The content-type check matters too, because the HTML page can come back as a 200. Reproducer: https://github.com/fullheart/tanstack-start-issue-6787-v2
Activity
IgnazioDS commentedon Jul 12, 2026
Ran into the same error and dug into it using the staticFunctionMiddleware_repro. Everything below is on the pinned versions (
@tanstack/react-start@1.168.25, which resolvesstart-plugin-core@1.171.17andstart-static-server-functions@1.167.17), so this is not the #6940 output-directory bug - that fix is already present.The payload file is missing, and the client middleware has no fallback when it is.
Static payloads are only written for server-function calls that actually execute during prerender (
staticFunctionMiddleware.serverwrites only whenTSS_CLIENT_OUTPUT_DIRis set, whichprerenderWithVitedoes). In the repro build the prerender set (crawlLinks: false+ explicitpages) rendered 3 pages, none of which invoke the doc server functions - so__tsr/staticServerFnCache/is never created anywhere on disk. Any route that uses the middleware but is not covered by the prerender page set has no payload, by construction.At runtime, the client half of the middleware fetches the payload URL and calls
r.json()directly - noresponse.okcheck, no content-type check, and no fallback toctx.next()(staticFunctionMiddleware.js, thefetchItemcall in 1.167.17). A missing payload is therefore a hard crash of the navigation instead of a degradation to a live server-function call.The failed fetch does not even return a 404. The app's own catch-all route serves the SPA shell as 200 text/html for the missing payload path:
(the locale catch-all parsed
__tsras a locale).r.json()on that body is exactlyUnexpected token '<', "<!DOCTYPE "... is not valid JSON. Because the shell comes back as 200, checkingresponse.okalone would not be enough - the response needs a content-type check.Suggested fix in
start-static-server-functions: in the client middleware, validate the response (ok +application/json) and fall back toctx.next()when the payload is missing, so uncovered routes degrade to a live server-function call instead of crashing. A build-time warning when a decorated function produced zero payloads during prerender would also make the coverage gap visible.Workaround meanwhile: ensure
prerender.pagesincludes every route that calls a static-middleware function (including every locale variant, since the payload hash includes the serialized args), or keep the middleware off until the fallback lands.IgnazioDS commentedon Jul 12, 2026
Ran into the same error and dug into it using the staticFunctionMiddleware_repro. Everything below is on the pinned versions (
@tanstack/react-start@1.168.25, which resolvesstart-plugin-core@1.171.17andstart-static-server-functions@1.167.17), so this is not the #6940 output-directory bug - that fix is already present.The payload file is missing, and the client middleware has no fallback when it is.
Static payloads are only written for server-function calls that actually execute during prerender (
staticFunctionMiddleware.serverwrites only whenTSS_CLIENT_OUTPUT_DIRis set, whichprerenderWithVitedoes). In the repro build the prerender set (crawlLinks: false+ explicitpages) rendered 3 pages, none of which invoke the doc server functions - so__tsr/staticServerFnCache/is never created anywhere on disk. Any route that uses the middleware but is not covered by the prerender page set has no payload, by construction.At runtime, the client half of the middleware fetches the payload URL and calls
r.json()directly - noresponse.okcheck, no content-type check, and no fallback toctx.next()(staticFunctionMiddleware.js, thefetchItemcall in 1.167.17). A missing payload is therefore a hard crash of the navigation instead of a degradation to a live server-function call.The failed fetch does not even return a 404. The app's own catch-all route serves the SPA shell as 200 text/html for the missing payload path:
(the locale catch-all parsed
__tsras a locale).r.json()on that body is exactlyUnexpected token '<', "<!DOCTYPE "... is not valid JSON. Because the shell comes back as 200, checkingresponse.okalone would not be enough - the response needs a content-type check.Suggested fix in
start-static-server-functions: in the client middleware, validate the response (ok +application/json) and fall back toctx.next()when the payload is missing, so uncovered routes degrade to a live server-function call instead of crashing. A build-time warning when a decorated function produced zero payloads during prerender would also make the coverage gap visible.Workaround meanwhile: ensure
prerender.pagesincludes every route that calls a static-middleware function (including every locale variant, since the payload hash includes the serialized args), or keep the middleware off until the fallback lands.aymericzip commentedon Aug 6, 2026
Found a fix using nitro middleware
Payloads aren't missing here, they're just not served.
Nitro bakes its public asset manifest before TanStack's prerender runs, so everything prerender writes lands on disk after the manifest is sealed and falls through to the SSR catch-all. Hence the HTML shell with lang="__tsr".
You can confirm it by dropping any file into .output/public after a build, same result
Nitro middleware fix: aymericzip/intlayer@b96d2b2
fullheart commentedon Aug 19, 2026
I ran into the same crash. What happens: when the cache file for a call doesn't exist, the server has nothing to send back except the app's own HTML page — and
fetchItemthen tries to parse that HTML as JSON. That's theUnexpected token '<'.In our case some calls simply never ran during prerender, so their files were never written, and every navigation that needed one of them died. As @IgnazioDS said: the client just has no fallback for a missing file.
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 this, a missing file no longer kills the navigation — it falls through to
ctx.next(), and what happens then stays your setup's decision: live execution as today, or (for fully prerendered sites, see #6787) a clean "no data" from the server. The content-type check matters too, because the HTML page can come back as a 200. Reproducer: https://github.com/fullheart/tanstack-start-issue-6787-v2