Repository navigation
staticFunctionMiddleware Unexpected token '<', "<!DOCTYPE "... is not valid JSON #7630
Description
Activity
Ran into the same error and dug into it using the staticFunctionMiddleware_repro. Everything below is on the pinned versions (
@tanstack/[email protected], which resolves[email protected]and[email protected]), 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:
$ curl -i http://localhost:4174/__tsr/staticServerFnCache/deadbeef.json HTTP/1.1 200 OK content-type: text/html; charset=utf-8 <!DOCTYPE html><html dir="ltr" lang="__tsr">...(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.-
Ran into the same error and dug into it using the staticFunctionMiddleware_repro. Everything below is on the pinned versions (
@tanstack/[email protected], which resolves[email protected]and[email protected]), 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:
$ curl -i http://localhost:4174/__tsr/staticServerFnCache/deadbeef.json HTTP/1.1 200 OK content-type: text/html; charset=utf-8 <!DOCTYPE html><html dir="ltr" lang="__tsr">...(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.-
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 resultNitro middleware fix: aymericzip/intlayer@b96d2b2
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: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
Which project does this relate to?
Router
Describe the bug
When I add
staticFunctionMiddlewareto my server function, it throws an error after the build:Unexpected token
'<', "<!DOCTYPE "... is not valid JSONThe returned object is serializable as JSON.
I also tried
JSON.parse(JSON.stringify(result)), but I get the same error.Complete minimal reproducer
https://github.com/aymericzip/staticFunctionMiddleware_repro
Steps to Reproduce the Bug
navigate to
/docI also included a demo for #7516
comment code in packages/@intlayer/design-system/src/providers/ReactQueryProvider.tsx
Expected behavior
Screenshots or Videos
Demo: https://drive.google.com/open?id=1gBbIZ2okGl099IfONT5lGjToc2TeMtBf&usp=drive_fs
Platform
mac
Additional context
No response