-
-
Notifications
You must be signed in to change notification settings - Fork 772
Open
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.
Metadata
Metadata
Assignees
Labels
No labels