All skills
remix-run avatar

/react-router-framework-mode

@220978e
by Remixremix-run/agent-skills136 stars
4

Build full-stack React applications using React Router's framework mode. Use when configuring routes, working with loaders and actions, handling forms, handling navigation, pending/optimistic UI, error boundaries, or working with react-router.config.ts or other react router conventions.

Use this Skill: https://skilld.dev/gh/remix-run/agent-skills/react-router-framework-mode

This session only. Nothing lands on disk.

referencesmiddleware.md

≈1.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Middleware & Context API

⚠️ Version Requirements - Check First!

Before implementing middleware, verify your React Router version:

npm list react-router
Feature Minimum Version Config Flag Required
Middleware 7.9.0+ v8_middleware: true

If your version is below 7.9.0:

  • Middleware is not available
  • Use loaders/actions for request processing instead
  • Or upgrade: npm install react-router@latest

Enabling Middleware

// react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
  future: {
    v8_middleware: true, // Required for middleware
  },
} satisfies Config;

See https://reactrouter.com/how-to/middleware#changes-to-getloadcontextapploadcontext for migration instructions.

Overview

Middleware runs code before and after response generation. Executes in a nested chain: parent → child on the way down, child → parent on the way up.

Root middleware start
  Parent middleware start
    Child middleware start
      → Run loaders, generate Response
    Child middleware end
  Parent middleware end
Root middleware end

Basic Middleware

import type { Route } from "./+types/dashboard";
import { redirect } from "react-router";

async function authMiddleware({ request, context }: Route.MiddlewareArgs) {
  const user = await getUserFromSession(request);
  if (!user) {
    throw redirect("/login");
  }
  context.set(userContext, user);
}

export const middleware: Route.MiddlewareFunction[] = [authMiddleware];

The next Function

Call next() to continue the chain and get the response:

async function loggingMiddleware(
  { request }: Route.MiddlewareArgs,
  next: Route.MiddlewareNext,
) {
  console.log(`→ ${request.method} ${request.url}`);

  const response = await next();

  console.log(`← ${response.status}`);
  return response;
}
  • Call next() only once
  • If you don't need post-processing, skip calling next() (called automatically)
  • next() never throws—errors return as error responses

Context API

Create typed context to share data between middleware and loaders/actions:

// app/context.ts
import { createContext } from "react-router";

export const userContext = createContext<User | null>(null);
export const dbContext = createContext<Database>();

Setting Context in Middleware

import { userContext } from "~/context";

async function authMiddleware({ request, context }: Route.MiddlewareArgs) {
  const user = await getUser(request);
  context.set(userContext, user);
}

export const middleware: Route.MiddlewareFunction[] = [authMiddleware];

Reading Context in Loaders/Actions

import { userContext } from "~/context";

export async function loader({ context }: Route.LoaderArgs) {
  const user = context.get(userContext);
  return { profile: await getProfile(user) };
}

export async function action({ context }: Route.ActionArgs) {
  const user = context.get(userContext);
  // user is available here too
}

Server vs Client Middleware

Server middleware (middleware) runs on document requests and .data fetches:

export const middleware: Route.MiddlewareFunction[] = [
  async ({ request, context }, next) => {
    // Runs on server
    const response = await next();
    return response;
  },
];

Client middleware (clientMiddleware) runs on client-side navigations:

export const clientMiddleware: Route.ClientMiddlewareFunction[] = [
  async ({ context }, next) => {
    // Runs in browser
    const start = performance.now();
    await next();
    console.log(`Navigation: ${performance.now() - start}ms`);
  },
];

When Server Middleware Runs

Request Type Middleware Runs?
Document request (GET /route) Always
Client navigation with loader Yes (.data request)
Client navigation without loader No

To force middleware on routes without loaders, add an empty loader:

export const middleware: Route.MiddlewareFunction[] = [authMiddleware];

export async function loader() {
  return null;
}

Common Patterns

Authentication

async function authMiddleware({ request, context }: Route.MiddlewareArgs) {
  const session = await getSession(request);
  if (!session.get("userId")) {
    throw redirect("/login");
  }
  context.set(userContext, await getUserById(session.get("userId")));
}

Request Logging

async function loggingMiddleware(
  { request }: Route.MiddlewareArgs,
  next: Route.MiddlewareNext,
) {
  const id = crypto.randomUUID();
  const start = performance.now();

  console.log(`[${id}] ${request.method} ${request.url}`);
  const response = await next();
  console.log(`[${id}] ${response.status} (${performance.now() - start}ms)`);

  return response;
}

Response Headers

async function securityHeaders(
  _: Route.MiddlewareArgs,
  next: Route.MiddlewareNext,
) {
  const response = await next();
  response.headers.set("X-Frame-Options", "DENY");
  response.headers.set("X-Content-Type-Options", "nosniff");
  return response;
}

404 Fallback

async function cmsFallback(
  { request }: Route.MiddlewareArgs,
  next: Route.MiddlewareNext,
) {
  const response = await next();

  if (response.status === 404) {
    const redirect = await checkCmsRedirects(request.url);
    if (redirect) throw redirect(redirect, 302);
  }

  return response;
}

Conditional Execution

export const middleware: Route.MiddlewareFunction[] = [
  async ({ request, context }, next) => {
    if (request.method === "POST") {
      await requireAuth(request, context);
    }
    return next();
  },
];

Error Handling

Errors bubble to the nearest ErrorBoundary. The next() function always returns a response (never throws):

export const middleware: Route.MiddlewareFunction[] = [
  async (_, next) => {
    const response = await next();
    // response.status = 500 if child threw
    // Can still set headers, commit sessions, etc.
    return response;
  },
];
  • Error before next(): Bubbles to highest route with a loader
  • Error after next(): Bubbles from the throwing route

See Also

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill is a comprehensive documentation reference for building applications with React Router's framework mode. It provides standard architectural patterns, code examples, and best practices for routing, data loading, actions, and session management. No security risks or malicious patterns were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    2/13 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 220978e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last week.

Steadyupdated 8 months ago

README badge

README badge for remix-run/agent-skills/react-router-framework-mode