On this page

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 routes

Proxy 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:

PatternMatch
/aboutThe exact /about path
/blog/:slugOne 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/**:pathOne 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.