Proxy
Define proxy.ts at the project root to run logic before a request reaches any route in app/.
The proxy can inspect the request, then redirect, rewrite, reject, respond, or continue to the app
router:
import type { ProxyRoutes } from "@rebask/core/proxy"
import { rewrite } from "@rebask/core/proxy"
const routes: ProxyRoutes = {
"/admin/*": (request) => {
if (!request.headers.get("cookie")?.includes("session=")) {
return Response.redirect(new URL("/login", request.url))
}
},
"/old-blog/:slug": (_request, params) => {
return rewrite(`/blog/${params.slug}`)
},
"/dashboard": (request) => {
const headers = new Headers(request.headers)
headers.set("x-user-role", "admin")
return rewrite("/admin/dashboard", { headers })
},
"/api/health": () => Response.json({ ok: true }),
}
export default routesProxy signature
Export a ProxyRoutes object as the default export from proxy.ts. Each key is a URL pattern and each value is a handler
that receives the standard Web Request
and the parameters extracted from the pattern:
import type { ProxyRoutes } from "@rebask/core/proxy"
const routes: ProxyRoutes = {
"/api/:version/**:path": (request, params) => {
const url = new URL(request.url)
return Response.json({
version: params.version,
path: params.path,
method: request.method,
})
},
}Returning a Response ends proxy processing. Use it for redirects, access errors, JSON responses, and other responses that should not reach the application router. Return nothing to continue to the app router.
Proxy routes match every supported HTTP method. Use request.method to branch when needed.
Route patterns
Proxy patterns are rou3 route patterns . Every pattern supported by rou3's compiled router is supported here:
| Pattern | Match |
|---|---|
/about | The exact /about path |
/blog/:slug | One segment, available as params.slug |
/users/:id(\\d+) | One segment matching the regex |
/users/:id? | Zero or one segment |
/files/:path+ | One or more segments, available as params.path |
/files/:path* | Zero or more segments |
/files/* | The rest of the path, including / |
/files/** | Zero or more segments |
/files/**:path | One or more segments, available as params.path |
/blog/:id(\\d+){-:title}? | Optional group |
A trailing * can match zero segments. Use :path+ or **:path when at least one segment is
required.
Proxy handlers receive rou3 params directly. Named captures use their name, unnamed captures use
"0", "1", …. Values are the percent-encoded strings rou3 returns; catch-all values are a single
string with / separators.
For example, /api/users/123 matches /api/**:path with params.path set to "users/123". Use
decodeURIComponent(params.path) when you need a decoded value. App routes decode params because
they follow the filesystem conventions.
Static files
The proxy runs before files in public/ are served, so a pattern that matches a static file answers
for it too. Return nothing to let the file through.
When using an adapter, a broad pattern like /* can also match build assets, so keep patterns
narrow or return early for paths you don't want to handle.
Rewrites
Use rewrite() to send the request to another route without changing the browser URL:
import type { ProxyRoutes } from "@rebask/core/proxy"
import { rewrite } from "@rebask/core/proxy"
const routes: ProxyRoutes = {
"/old-blog/:slug": (_request, params) => rewrite(`/blog/${params.slug}`),
}The destination is matched by the application router using the original HTTP method and request body. The browser continues to display the original URL.
Forwarding headers
Pass headers to rewrite() when the destination needs modified request headers. The supplied
Headers object replaces the original request headers, so clone request.headers when existing
headers should be preserved:
import type { ProxyRoutes } from "@rebask/core/proxy"
import { rewrite } from "@rebask/core/proxy"
const routes: ProxyRoutes = {
"/dashboard": (request) => {
const headers = new Headers(request.headers)
headers.set("x-user-role", "admin")
headers.delete("x-legacy-role")
return rewrite("/admin/dashboard", { headers })
},
}Rewrites preserve the original Accept header and Rebask's Flight headers. Proxy code cannot modify
these headers with rewrite().
External rewrites
Pass an absolute URL to rewrite() to proxy the request to another origin:
import type { ProxyRoutes } from "@rebask/core/proxy"
import { rewrite } from "@rebask/core/proxy"
const routes: ProxyRoutes = {
"/api/upstream": (request) => {
const url = new URL(request.url)
return rewrite(`https://api.example.com/data${url.search}`)
},
}External rewrites proxy the original request method, body, and headers to the upstream URL, with
Host and X-Forwarded-Host set for the target and original host. Rebask does not follow redirects
returned by the upstream. If the upstream cannot be reached or does not respond before the timeout,
Rebask returns 502 Bad Gateway.
The default timeout is 30 seconds. Configure it with
proxyTimeout, or set it to null to disable it.
Cookies in Proxy
Use cookies(request) to read request cookies and cookies(response) to set or delete cookies on
a response or rewrite:
import { cookies } from "@rebask/core/cookies"
const routes: ProxyRoutes = {
"/login-required": (request) => {
if (cookies(request).has("session")) return
const response = Response.redirect(new URL("/login", request.url))
cookies(response).set("login-attempt", "true", { httpOnly: true, path: "/" })
return response
},
}See cookies in the Server Runtime APIs docs for the available options.
Flight headers
Rebask hides its Flight protocol headers from the proxy because they control how a request is
handled, such as RSC navigations, actions, or resumes. They are restored after the proxy runs, so
those request types continue to work as expected. The proxy cannot read, remove, or replace them
with rewrite().
These headers are framework state, not application input.