serverFetch causes infinite loop (Maximum call stack size exceeded) when route doesn't exist
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 11.2k
- Forks
- 899
- Avg merge
- 2d 24m
- Merged PRs (30d)
- 40
Description
Description
When using serverFetch() inside an HTML template (e.g., index.html) to fetch a route that doesn't exist, it causes an infinite recursion loop resulting in RangeError: Maximum call stack size exceeded.
Reproduction
- Create a new Nitro project with the default starter template
- The
index.htmlcontains:{{{ serverFetch("/api") }}} - But there's no
/apiroute defined (onlyroutes/index.tswhich maps to/) - Run
bun run build && bun run preview - Visit
http://localhost:3000/api
What happens
/api request
↓
No route found → fallback /** → renderer-template
↓
Template calls serverFetch("/api")
↓
/api request (again)
↓
No route found → fallback /** → renderer-template
↓
... infinite loop → Maximum call stack size exceeded
Error output
[request error] [unhandled] [GET] http://localhost/api
RangeError: Maximum call stack size exceeded
at hasTrailingSlash (file:///.output/server/chunks/nitro/app.mjs:713:26)
at withoutTrailingSlash$1 (file:///.output/server/chunks/nitro/app.mjs:718:40)
at serverFetch (file:///.output/server/chunks/nitro/app.mjs:1574:26)
at template (file:///.output/server/chunks/nitro/renderer-template.mjs:187:7)
...
Root cause
The serverFetch() function in nitro/dist/runtime/internal/app.mjs has no recursion protection:
export function serverFetch(resource, init, context) {
const req = toRequest(resource, init);
req.context = {
...req.context,
...context
};
const appHandler = useNitroApp().fetch;
try {
return Promise.resolve(appHandler(req));
} catch (error) {
return Promise.reject(error);
}
}
Suggested fix
Add recursion depth tracking to prevent infinite loops:
export function serverFetch(resource, init, context) {
const req = toRequest(resource, init);
// Recursion guard
const depth = (context?._serverFetchDepth || 0) + 1;
if (depth > 10) {
throw new Error(`serverFetch: maximum recursion depth exceeded for "${resource}". This usually means the requested route doesn't exist and falls back to a template that calls serverFetch again.`);
}
req.context = {
...req.context,
...context,
_serverFetchDepth: depth
};
const appHandler = useNitroApp().fetch;
try {
return Promise.resolve(appHandler(req));
} catch (error) {
return Promise.reject(error);
}
}
Environment
- Nitro version: v3 (latest)
- Runtime: Bun
- OS: macOS
Additional context
This is a defensive programming issue. While the immediate workaround is to ensure all routes called by serverFetch() exist, the framework should protect against infinite recursion with a clear error message.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the missing-route case with the default Nitro project and inspect nitro/dist/runtime/internal/app.mjs alongside the renderer-template fallback described in the report. Verify that requesting a nonexistent route from a template no longer recurses indefinitely and instead produces a clear bounded error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bun, typescript
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100