A zero-config, catch-all api proxy for app router. Every /api/* request is transparently forwarded to your backend. (w/o manual route definitions required)
When your frontend and backend are separate services, the nextjs approach is to create a dedicated route handler for every backend endpoint. That works, but it means duplicating route definitions, manually wiring request/response bodies, and keeping both sides in sync as the api evolves.
Browser -> [endpoint] -> nextjs catch-all route -> [upstream]
Browser <- json response <- nextjs catch-all route <- [upstream]
A single catch-all route intercepts every request. The proxy function strips the /api prefix, rebuilds the url against your backend, and streams the request and response. Your frontend only ever talks to /api/..., the backend url is never exposed to the browser.
The proxy() function accepts an optional second argument:
proxy(req, {
backendUrl: "http://localhost:3001", // override env variable
stripPrefix: 1, // segments to strip (default: 1 -> removes "/api")
timeout: 15_000, // backend timeout in ms (default: 30s)
excludeHeaders: ["cookie"], // headers to NOT forward
extraHeaders: { // headers to attach to every request
"x-api-key": ...,
},
onRequest(outgoing) { // intercept before sending
return outgoing;
},
onResponse(res) { // intercept before relaying
return res;
},
});| Concern | Behavior |
|---|---|
| HTTP methods | GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS |
| Request body | Streamed (half-duplex) supports JSON, form data, file uploads |
| Query parameters | Forwarded as-is |
| Headers | Forwarded, minus hop-by-hop headers (connection, transfer-encoding, etc.) |
| X-Forwarded-For | Maintained/extended automatically |
| Backend down | Returns 502 Bad Gateway with { "error": "..." } |
| Backend slow | Returns 504 Gateway Timeout after the configured timeout |
- Next.js 16+ (App Router)
- TypeScript