# Remix & React Router

Serve Courier's proxy from a React Router 7 or Remix v2 resource route, with @noverax/courier-remix.

`@noverax/courier-remix` serves the proxy that `createProxyCaller` and `createProxyQueryCaller` call: one route answers every endpoint, reads over `GET` and writes over `POST`, and streams server-sent events.

## In one command

```bash
npx @noverax/create-courier
```

In a React Router or Remix app it adds what this page sets up by hand — the resource route, its entry in `app/routes.ts`, and Studio's plugin — with a typed example API, its mock, and a page at `/courier`. See [Create Courier](/docs/create-courier).

## Install

```bash
npm install @noverax/courier-remix @noverax/courier
```

Remix continues as React Router 7's framework mode, which is what `npx create-react-router@latest` sets up, and the handler works with both.

## Usage

```ts
// app/routes/api.request.ts
import { registerSources } from "@noverax/courier"
import { courierHandler } from "@noverax/courier-remix"
import { sources } from "~/api"

registerSources(sources)

const handler = courierHandler()
export const loader = handler
export const action = handler
```

React Router 7 serves only the routes `app/routes.ts` lists, so add the file there:

```ts
// app/routes.ts
import { type RouteConfig, index, route } from "@react-router/dev/routes"

export default [
  index("routes/home.tsx"),
  route("api/request", "routes/api.request.ts"),
] satisfies RouteConfig
```

On Remix v2, whose file-system routes serve `app/routes/api.request.ts` at `/api/request` without a config, the route file alone is enough.

`courierHandler()` is zero-arg — sources are read at request time from the core global registry that `registerSources()` fills when the route module loads. The handler uses Web Standard `Request`/`Response` natively — no conversion needed. `loader` answers `GET`, which carries a read that may be cached, and `action` answers `POST`, which carries every other call.

The proxy reads a JSON call up to its `maxBodyBytes`, 1 MB, and an upload (`multipart/form-data`) up to its `maxUploadBytes`, 10 MB; pass others to raise them: `courierHandler({ maxUploadBytes: 50 * 1024 * 1024 })`. In development, a client that declares an oversized upload can see the connection close before it has sent it all, rather than read the 413.

## The client

Pair with `createProxyCaller` on the client, given the routes Courier writes into `courier.routes.ts` from the endpoints you mark `.expose()` — see [Exposing Endpoints](/docs/proxy-mode/expose):

```ts
import { createProxyCaller } from "@noverax/courier"
import { apiRoutes } from "./courier.routes"

// The routes name the key this source has in your `sources` map —
// it is what the proxy addresses, and it travels as `?_s=<name>`.
const caller = createProxyCaller(apiRoutes, {
  proxyPath: { http: "/api/request" },
})
const users = await caller.users.getAll()
```

## The user's session

An API that keeps a `session` gets the user's session with every call a loader, an action or a step it awaits makes, once the request is served with it at hand. List the middleware in the root route:

```tsx title="app/root.tsx"
import { requestContext } from "@noverax/courier-remix/request-context"

export const middleware = [requestContext()]
```

React Router 8 runs route middleware as it is; React Router 7 runs it with `future: { v8_middleware: true }` in `react-router.config.ts`. The session cookies the API sets reach the browser on the response, a document's or a data request's. The request is kept in `AsyncLocalStorage`, which Node provides, and Cloudflare with the `nodejs_compat` flag; the main entry imports none of it.

`react-router-serve` hands React Router a `Request` alone, so `forward`'s client address is not sent. An Express server of your own tells it: serve the requests with `requestContext` from `@noverax/courier-express` instead. A server of your own that calls the handler `createRequestHandler` makes — Bun's, Deno's, a Worker's — wraps it:

```ts title="server.ts"
import { createRequestHandler } from "react-router"
import { withRequestContext } from "@noverax/courier-remix/request-context"

const handle = withRequestContext(createRequestHandler(build, "production"))
```

Remix v2 has no middleware. Serve its handler from an Express server of your own — the one its Express template starts with — and mount `requestContext` from `@noverax/courier-express` before it:

```js title="server.js"
import { requestContext } from "@noverax/courier-express"
import { createRequestHandler } from "@remix-run/express"

app.use(requestContext())
app.all("*", createRequestHandler({ build }))
```

React Router 7 and 8 are checked with `react-router-serve`, and Remix v2 with its Express template's server, `compression` included. See [User Sessions](/docs/server-side/session).

## Cancelled calls

When the browser cancels a call — an aborted upload, a query it no longer needs — the proxy stops its own upstream request, following the request's signal. React Router 7 hands a resource route a rebuilt copy of the request, and the copy's signal does not reliably follow the original: a cancelled upload to a slow upstream was seen to run to completion on both `react-router dev` and `react-router-serve`. The future flag that passes the request through unchanged keeps the cancellation:

```ts
// react-router.config.ts
export default {
  future: { v8_passThroughRequests: true },
}
```
