Skip to content

Add unstable to public API #12917

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions .changeset/client-context.md

This file was deleted.

61 changes: 44 additions & 17 deletions .changeset/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@

Support `middleware` on routes (unstable)

Routes can now define a `middleware` property accepting an array of functions that will run sequentially before route loader run in parallel. These functions accept the same arguments as `loader`/`action` and an additional `next` function to run the remaining data pipeline. This allows middlewares to perform logic before and after loaders/actions execute.
Routes can now define an array of middleware functions that will run sequentially before route handlers run. These functions accept the same arguments as `loader`/`action` plus an additional `next` function to run the remaining data pipeline. This allows middlewares to perform logic before and after handlers execute.

```tsx
// Framework mode
export const middleware = [logger, auth];
export const unstable_middleware = [serverLogger, serverAuth];
export const unstable_clientMiddleware = [clientLogger];

// Library mode
const routes = [
{
path: "/",
middleware: [logger, auth],
unstable_middleware: [clientLogger, clientAuth],
loader: rootLoader,
Component: Root,
},
Expand All @@ -24,49 +25,75 @@ const routes = [
Here's a simple example of a client-side logging middleware that can be placed on the root route:

```tsx
const logger: MiddlewareFunction = ({ request, params, context }, next) => {
async function clientLogger({
request,
params,
context,
next,
}: Route.ClientMiddlewareArgs) {
let start = performance.now();

// Run the remaining middlewares and all route loaders
await next();

let duration = performance.now() - start;
console.log(request.status, request.method, request.url, `(${duration}ms)`);
};
console.log(`Navigated to ${request.url} (${duration}ms)`);
}
```

Note that in the above example, the `next`/`middleware` functions don't return anything. This is by design as on the client there is no "response" to send over the network like there would be for middlewares running on the server. The data is all handled behind the scenes by the stateful `router`.

For a server-side middleware, the `next` function will return the HTTP `Response` that React Router will be sending across the wire, thus giving you a chance to make changes as needed. You may throw a new response to short circuit and respond immediately, or you may return a new or altered response to override the default returned by `next()`.

```tsx
async function serverLogger({
request,
params,
context,
next,
}: Route.MiddlewareArgs) {
let start = performance.now();

// 👇 Grab the response here
let res = await next();
let duration = performance.now() - start;
console.log(`Navigated to ${request.url} (${duration}ms)`);

// 👇 And return it here
return res;
}
```

You can throw a redirect from a middleware to short circuit any remaining processing:

```tsx
const auth: MiddlewareFunction = ({ request, params, context }, next) => {
let user = session.get("user");
function serverAuth({ request, params, context, next }: Route.MiddlewareArgs) {
let user = context.session.get("user");
if (!user) {
session.set("returnTo", request.url);
context.session.set("returnTo", request.url);
throw redirect("/login", 302);
}
context.user = user;
// No need to call next() if you don't need to do any post processing
};
}
```

Note that in the above example, the `next`/`middleware` functions don't return anything. This is by design as on the client there is no "response" to send over the network like there would be for middlewares running on the server. The data is all handled behind the scenes by the stateful `router`.

For a server-side middleware, the `next` function will return the HTTP `Response` that React Router will be sending across the wire, thus giving you a chance to make changes as needed. You may throw a new response to short circuit and respond immediately, or you may return a new or altered response to override the default returned by `next()`.
Here's another example of using a server middleware to detect 404s and check the CMS for a redirect:

```tsx
const redirects: MiddlewareFunction({ request }, next) {
async function redirects({ request, next }: Route.MiddlewareArgs) {
// attempt to handle the request
let response = await next();
let res = await next();

// if it's a 404, check the CMS for a redirect, do it last
// because it's expensive
if (response.status === 404) {
if (res.status === 404) {
let cmsRedirect = await checkCMSRedirects(request.url);
if (cmsRedirect) {
throw redirect(cmsRedirect, 302);
}
}

return response;
return res;
}
```
9 changes: 7 additions & 2 deletions .changeset/spa-context.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
---
"react-router": minor
"react-router": patch
---

Add `context` support to client side data routers (`createBrowsertRouter`, etc.)
Add `context` support to client side data routers (unstable)

- Library mode
- `createBrowserRouter(routes, { unstable_context })`
- Framework mode
- `<HydratedRouter unstable_context>`
2 changes: 1 addition & 1 deletion integration/browser-entry-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ test("allows users to pass a client side context to HydratedRouter", async ({
hydrateRoot(
document,
<StrictMode>
<HydratedRouter context={{ foo: 'bar' }} />
<HydratedRouter unstable_context={{ foo: 'bar' }} />
</StrictMode>
);
});
Expand Down
Loading