Middlewares
Every middleware shipped by remix, in canonical stack order (top runs first). The table is the answer to "do I need to install X?" — almost always no, Remix already has it.
The canonical stack
| Order |
Middleware |
Import |
What it does |
Deep doc |
| 5 |
cors() |
remix/cors-middleware |
Adds CORS response headers; short-circuits preflight OPTIONS. First so preflights skip everything else. |
security |
| 10 |
logger() |
remix/logger-middleware |
Request/response logging (method, path, status, duration). |
this skill |
| 15 |
cop() |
remix/cop-middleware |
Tokenless cross-origin protection via Sec-Fetch-Site/Origin. Cheap reject before session work. |
security |
| 20 |
compression() |
remix/compression-middleware |
gzip/br encoding for text-like responses. |
this skill |
| 30 |
staticFiles(...) |
remix/static-middleware |
Serves ./public and exits chain on hit. |
this skill |
| 40 |
formData({...}) |
remix/form-data-middleware |
Parses multipart/form-data and application/x-www-form-urlencoded; streams file uploads to a handler. |
forms |
| 50 |
methodOverride() |
remix/method-override-middleware |
Promotes _method=PUT/PATCH/DELETE form field to the HTTP method. After formData. |
this skill |
| 60 |
session(...) |
remix/session-middleware |
Loads + saves the session cookie. Before auth and csrf. |
sessions |
| 65 |
csrf() |
remix/csrf-middleware |
Synchronizer-token CSRF validation. After session (token lives in session). |
security |
| 70 |
asyncContext() |
remix/async-context-middleware |
Stores RequestContext in AsyncLocalStorage so helpers can call getContext() without threading. |
this skill |
| 80 |
auth({...}) |
remix/auth-middleware |
Resolves identity from session/bearer schemes; populates Auth on the context. |
auth |
| — |
requireAuth(...) |
remix/auth-middleware |
Per-route guard. Returns 401/redirect when unauthenticated. |
auth |
You'll write your own middlewares too — loadDatabase(), loadCurrentTenant(), request-id, error boundary, etc. Those go at the end (after asyncContext, before auth if they enrich the context for auth schemes). See references/custom-middleware.md.
Production stack — copy/paste
import { asyncContext } from 'remix/async-context-middleware'
import { compression } from 'remix/compression-middleware'
import { createRouter } from 'remix/fetch-router'
import { formData } from 'remix/form-data-middleware'
import { logger } from 'remix/logger-middleware'
import { methodOverride } from 'remix/method-override-middleware'
import { staticFiles } from 'remix/static-middleware'
import { session } from 'remix/session-middleware'
import { csrf } from 'remix/csrf-middleware'
export function createAppRouter() {
const middleware = []
if (process.env.NODE_ENV !== 'production') middleware.push(logger())
middleware.push(compression())
middleware.push(staticFiles('./public', { cacheControl: 'public, max-age=3600' }))
middleware.push(formData({}))
middleware.push(methodOverride())
middleware.push(session(sessionCookie, sessionStorage))
middleware.push(csrf())
middleware.push(asyncContext())
middleware.push(loadDatabase()) // your custom middleware
middleware.push(loadAuth()) // wraps auth({...schemes})
return createRouter({ middleware })
}
Add cors() first if your API is consumed by browsers at other origins. Add cop() after logger if you want fast tokenless cross-origin rejection in front of csrf().
When NOT to use the canonical stack
- Pure JSON API. Drop
staticFiles, formData, methodOverride. Maybe drop csrf (use bearer tokens + cors).
- Serverless edge. Drop
compression (your platform's CDN handles it). Drop staticFiles (CDN handles it).
- Webhook endpoint. Skip
session, csrf, auth. Validate the sender's signature manually inside the handler.
logger first — so you log everything including static hits and rejects.
compression early — once headers are written, can't add Content-Encoding.
staticFiles before business logic — let /assets/* exit cheap.
methodOverride after formData — needs the parsed _method field.
session before auth and csrf — both read from the session.
asyncContext high enough that downstream middleware can use getContext().
Built-in middlewares that don't belong in the canonical stack
Some middlewares are situational, not always-on:
| Middleware |
Use it when |
requireAuth(...) |
Per-protected-route or per-controller, not global |
cors(...) |
API needs cross-origin access from browser JS |
cop(...) |
Want tokenless cross-origin defense |
Writing your own
A middleware is (ctx, next) => Promise<Response>. Set values on the context with ctx.set(Key, value); downstream handlers read with ctx.get(Key). See references/custom-middleware.md for patterns (inject context, short-circuit, post-process headers, error boundary, request-id, etc.).
Further reading
1---2name: remix-middlewares3description: Reference card for every middleware shipped by Remix v3 — `logger`, `compression`, `staticFiles`, `formData`, `methodOverride`, `session`, `csrf`, `cop`, `cors`, `asyncContext`, `auth`, `requireAuth` — with canonical stack ordering and pointers to deep docs. Plus the contract for writing your own. Load whenever the user is composing or editing the middleware stack in `app/router.ts`, debugging request flow, choosing between similar middlewares (`cop` vs `csrf`, custom vs built-in), or about to install Express middlewares (helmet, morgan, body-parser, cookie-parser, multer) — Remix already ships equivalents. Also load if the user asks "what middleware comes with Remix?" or "where does X go in the stack?".4---56# Middlewares78Every middleware shipped by `remix`, in canonical stack order (top runs first). The table is the answer to "do I need to install X?" — almost always no, Remix already has it.910## The canonical stack1112| Order | Middleware | Import | What it does | Deep doc |13|------:|---|---|---|---|14| 5 | `cors()` | `remix/cors-middleware` | Adds CORS response headers; short-circuits preflight `OPTIONS`. **First** so preflights skip everything else. | [security](../security/SKILL.md) |15| 10 | `logger()` | `remix/logger-middleware` | Request/response logging (method, path, status, duration). | this skill |16| 15 | `cop()` | `remix/cop-middleware` | Tokenless cross-origin protection via `Sec-Fetch-Site`/`Origin`. Cheap reject before session work. | [security](../security/SKILL.md) |17| 20 | `compression()` | `remix/compression-middleware` | gzip/br encoding for text-like responses. | this skill |18| 30 | `staticFiles(...)` | `remix/static-middleware` | Serves `./public` and exits chain on hit. | this skill |19| 40 | `formData({...})` | `remix/form-data-middleware` | Parses `multipart/form-data` and `application/x-www-form-urlencoded`; streams file uploads to a handler. | [forms](../forms/SKILL.md) |20| 50 | `methodOverride()` | `remix/method-override-middleware` | Promotes `_method=PUT/PATCH/DELETE` form field to the HTTP method. **After `formData`.** | this skill |21| 60 | `session(...)` | `remix/session-middleware` | Loads + saves the session cookie. **Before auth and csrf.** | [sessions](../sessions/SKILL.md) |22| 65 | `csrf()` | `remix/csrf-middleware` | Synchronizer-token CSRF validation. **After `session`** (token lives in session). | [security](../security/SKILL.md) |23| 70 | `asyncContext()` | `remix/async-context-middleware` | Stores `RequestContext` in `AsyncLocalStorage` so helpers can call `getContext()` without threading. | this skill |24| 80 | `auth({...})` | `remix/auth-middleware` | Resolves identity from session/bearer schemes; populates `Auth` on the context. | [auth](../auth/SKILL.md) |25| — | `requireAuth(...)` | `remix/auth-middleware` | Per-route guard. Returns 401/redirect when unauthenticated. | [auth](../auth/SKILL.md) |2627You'll write your own middlewares too — `loadDatabase()`, `loadCurrentTenant()`, request-id, error boundary, etc. Those go at the end (after `asyncContext`, before `auth` if they enrich the context for auth schemes). See [references/custom-middleware.md](./references/custom-middleware.md).2829## Production stack — copy/paste3031```ts32import { asyncContext } from 'remix/async-context-middleware'33import { compression } from 'remix/compression-middleware'34import { createRouter } from 'remix/fetch-router'35import { formData } from 'remix/form-data-middleware'36import { logger } from 'remix/logger-middleware'37import { methodOverride } from 'remix/method-override-middleware'38import { staticFiles } from 'remix/static-middleware'39import { session } from 'remix/session-middleware'40import { csrf } from 'remix/csrf-middleware'4142export function createAppRouter() {43 const middleware = []4445 if (process.env.NODE_ENV !== 'production') middleware.push(logger())46 middleware.push(compression())47 middleware.push(staticFiles('./public', { cacheControl: 'public, max-age=3600' }))48 middleware.push(formData({}))49 middleware.push(methodOverride())50 middleware.push(session(sessionCookie, sessionStorage))51 middleware.push(csrf())52 middleware.push(asyncContext())53 middleware.push(loadDatabase()) // your custom middleware54 middleware.push(loadAuth()) // wraps auth({...schemes})5556 return createRouter({ middleware })57}58```5960Add `cors()` first if your API is consumed by browsers at other origins. Add `cop()` after `logger` if you want fast tokenless cross-origin rejection in front of `csrf()`.6162## When NOT to use the canonical stack6364- **Pure JSON API.** Drop `staticFiles`, `formData`, `methodOverride`. Maybe drop `csrf` (use bearer tokens + `cors`).65- **Serverless edge.** Drop `compression` (your platform's CDN handles it). Drop `staticFiles` (CDN handles it).66- **Webhook endpoint.** Skip `session`, `csrf`, `auth`. Validate the sender's signature manually inside the handler.6768## Cross-cutting tips (deep doc: [references/ordering.md](./references/ordering.md))6970- **`logger` first** — so you log everything including static hits and rejects.71- **`compression` early** — once headers are written, can't add `Content-Encoding`.72- **`staticFiles` before business logic** — let `/assets/*` exit cheap.73- **`methodOverride` after `formData`** — needs the parsed `_method` field.74- **`session` before `auth` and `csrf`** — both read from the session.75- **`asyncContext` high enough** that downstream middleware can use `getContext()`.7677## Built-in middlewares that don't belong in the canonical stack7879Some middlewares are situational, not always-on:8081| Middleware | Use it when |82|---|---|83| `requireAuth(...)` | Per-protected-route or per-controller, not global |84| `cors(...)` | API needs cross-origin access from browser JS |85| `cop(...)` | Want tokenless cross-origin defense |8687## Writing your own8889A middleware is `(ctx, next) => Promise<Response>`. Set values on the context with `ctx.set(Key, value)`; downstream handlers read with `ctx.get(Key)`. See [references/custom-middleware.md](./references/custom-middleware.md) for patterns (inject context, short-circuit, post-process headers, error boundary, request-id, etc.).9091## Further reading9293- [references/built-ins.md](./references/built-ins.md) — full options for every shipped middleware94- [references/custom-middleware.md](./references/custom-middleware.md) — patterns for writing your own95- [references/ordering.md](./references/ordering.md) — common ordering bugs, what comes before/after what96- Sub-skills with their own deep doc: [security](../security/SKILL.md), [sessions](../sessions/SKILL.md), [auth](../auth/SKILL.md), [forms](../forms/SKILL.md)