Skip to content
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: 10 additions & 0 deletions .changeset/shy-lemons-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@react-router/cloudflare": minor
"@react-router/architect": minor
"@react-router/express": minor
"@react-router/node": minor
"@react-router/dev": minor
"react-router": minor
---

Stabilize middleware and context APIs
9 changes: 4 additions & 5 deletions docs/community/api-development-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ title: API Development Strategy

React Router is foundational to your application. We want to make sure that upgrading to new major versions is as smooth as possible while still allowing us to adjust and enhance the behavior and API as the React ecosystem advances.

Our strategy and motivations are discussed in more detail in our [Future Flags][future-flags-blog-post] blog post.
Our strategy and motivations are discussed in more detail in our [Future Flags][future-flags-blog-post] blog post and our [Open Governance Model][governance].

## Future Flags

Expand Down Expand Up @@ -36,10 +36,9 @@ To learn about current unstable flags, keep an eye on the [CHANGELOG](../start/c

### Example New Feature Flow

The decision flow for a new feature looks something like this (note this diagram is in relation to Remix v1/v2 but applies to React Router v6/v7 as well):
The decision flow for a new feature looks something like this:

![Flowchart of the decision process for how to introduce a new feature][feature-flowchart]
<img width="400" src="https://reactrouter.com/_docs/feature-flowchart.png" alt="Flowchart of the decision process for how to introduce a new feature" />

[future-flags-blog-post]: https://remix.run/blog/future-flags
[feature-flowchart]: https://remix.run/docs-images/feature-flowchart.png
[picking-a-router]: ../routers/picking-a-router
[governance]: https://github.com/remix-run/react-router/blob/main/GOVERNANCE.md#new-feature-process
206 changes: 102 additions & 104 deletions docs/how-to/middleware.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions docs/start/data/route-object.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,20 @@ function MyRouteComponent() {
}
```

## `unstable_middleware`
## `middleware`

Route [middleware][middleware] runs sequentially before and after navigations. This gives you a singular place to do things like logging and authentication. The `next` function continues down the chain, and on the leaf route the `next` function executes the loaders/actions for the navigation.

```tsx
createBrowserRouter([
{
path: "/",
unstable_middleware: [loggingMiddleware],
middleware: [loggingMiddleware],
loader: rootLoader,
Component: Root,
children: [{
path: 'auth',
unstable_middleware: [authMiddleware],
middleware: [authMiddleware],
loader: authLoader,
Component: Auth,
children: [...]
Expand All @@ -84,7 +84,7 @@ async function loggingMiddleware({ request }, next) {
console.log(`Navigation completed in ${duration}ms`);
}

const userContext = unstable_createContext<User>();
const userContext = createContext<User>();

async function authMiddleware ({ context }) {
const userId = getUserId();
Expand Down
18 changes: 8 additions & 10 deletions docs/start/framework/route-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default function MyRouteComponent({
}
```

## `unstable_middleware`
## `middleware`

Route [middleware][middleware] runs sequentially on the server before and after document and
data requests. This gives you a singular place to do things like logging,
Expand All @@ -103,7 +103,7 @@ async function loggingMiddleware(
return response;
}

export const unstable_middleware = [loggingMiddleware];
export const middleware = [loggingMiddleware];
```

Here's an example middleware to check for logged in users and set the user in
Expand All @@ -125,19 +125,19 @@ async function authMiddleware ({
context.set(userContext, user);
};

export const unstable_middleware = [authMiddleware];
export const middleware = [authMiddleware];
```

<docs-warning>Please make sure you understand [when middleware runs][when-middleware-runs] to make sure your application will behave the way you intend when adding middleware to your routes.</docs-warning>

See also:

- [`unstable_middleware` params][middleware-params]
- [`middleware` params][middleware-params]
- [Middleware][middleware]

## `unstable_clientMiddleware`
## `clientMiddleware`

This is the client-side equivalent of `unstable_middleware` and runs in the browser during client navigations. The only difference from server middleware is that client middleware doesn't return Responses because they're not wrapping an HTTP request on the server.
This is the client-side equivalent of `middleware` and runs in the browser during client navigations. The only difference from server middleware is that client middleware doesn't return Responses because they're not wrapping an HTTP request on the server.

Here's an example middleware to log requests on the client:

Expand All @@ -158,9 +158,7 @@ async function loggingMiddleware(
// ✅ No need to return anything
}

export const unstable_clientMiddleware = [
loggingMiddleware,
];
export const clientMiddleware = [loggingMiddleware];
```

See also:
Expand Down Expand Up @@ -502,7 +500,7 @@ export function shouldRevalidate(

Next: [Rendering Strategies](./rendering)

[middleware-params]: https://api.reactrouter.com/v7/types/react_router.unstable_MiddlewareFunction.html
[middleware-params]: https://api.reactrouter.com/v7/types/react_router.MiddlewareFunction.html
[middleware]: ../../how-to/middleware
[when-middleware-runs]: ../../how-to/middleware#when-middleware-runs
[loader-params]: https://api.reactrouter.com/v7/interfaces/react_router.LoaderFunctionArgs
Expand Down
41 changes: 40 additions & 1 deletion docs/upgrading/future.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,43 @@ This guide walks you through the process of adopting future flags in your React

We highly recommend you make a commit after each step and ship it instead of doing everything all at once. Most flags can be adopted in any order, with exceptions noted below.

<docs-warning>**There are no current future flags in React Router v7**</docs-warning>
## Update to latest v7.x

First update to the latest minor version of v7.x to have the latest future flags. You may see a number of deprecation warnings as you upgrade, which we'll cover below.

👉 Update to latest v7

```sh
npm install react-router@7 @react-router/{dev,node,etc.}@7
```

## `future.v8_middleware`

[MODES: framework]

<br/>
<br/>

**Background**

Middleware allows you to run code before and after the [`Response`][Response] generation for the matched path. This enables common patterns like authentication, logging, error handling, and data preprocessing in a reusable way. Please see the [docs](../how-to/middleware) for more information.

👉 **Enable the Flag**

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

export default {
future: {
v8_middleware: true,
},
} satisfies Config;
```

**Update your Code**

If you're using `react-router-serve`, then you should not need to make any updates to your code.

You should only need to update your code if you are using the `context` parameter in `loader` and `action` functions. This only applies if you have a custom server with a `getLoadContext` function. Please see the docs on the middleware [`getLoadContext` changes](../how-to/middleware#changes-to-getloadcontextapploadcontext) and the instructions to [migrate to the new API](../how-to/middleware#migration-from-apploadcontext).

[Response]: https://developer.mozilla.org/en-US/docs/Web/API/Response
8 changes: 4 additions & 4 deletions integration/browser-entry-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,20 +87,20 @@ test("allows users to pass a client side context to HydratedRouter", async ({
let fixture = await createFixture({
files: {
"app/entry.client.tsx": js`
import { unstable_createContext, unstable_RouterContextProvider } from "react-router";
import { createContext, RouterContextProvider } from "react-router";
import { HydratedRouter } from "react-router/dom";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

export const myContext = new unstable_createContext('foo');
export const myContext = new createContext('foo');

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<HydratedRouter
unstable_getContext={() => {
return new unstable_RouterContextProvider([
getContext={() => {
return new RouterContextProvider([
[myContext, 'bar']
]);
}}
Expand Down
4 changes: 2 additions & 2 deletions integration/helpers/rsc-parcel/src/browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
setServerCallback,
// @ts-expect-error - no types for this yet
} from "react-server-dom-parcel/client";
import { unstable_getContext } from "./config/unstable-get-context";
import { getContext } from "./config/get-context";

// Create and set the callServer function to support post-hydration server actions.
setServerCallback(
Expand All @@ -39,7 +39,7 @@ createFromReadableStream(getRSCStream()).then((payload: RSCPayload) => {
<RSCHydratedRouter
payload={payload}
createFromReadableStream={createFromReadableStream}
unstable_getContext={unstable_getContext}
getContext={getContext}
/>
</StrictMode>,
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// THIS FILE IS DESIGNED TO BE OVERRIDDEN IN TESTS IF NEEDED
export const unstable_getContext = undefined;
export const getContext = undefined;
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// THIS FILE IS DESIGNED TO BE OVERRIDDEN IN TESTS IF NEEDED
export const unstable_getContext = undefined;
export const getContext = undefined;
4 changes: 2 additions & 2 deletions integration/helpers/rsc-vite/src/entry.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
unstable_RSCHydratedRouter as RSCHydratedRouter,
} from "react-router";
import type { unstable_RSCPayload as RSCPayload } from "react-router";
import { unstable_getContext } from "./config/unstable-get-context";
import { getContext } from "./config/get-context";

setServerCallback(
createCallServer({
Expand All @@ -30,7 +30,7 @@ createFromReadableStream<RSCPayload>(getRSCStream()).then((payload) => {
<RSCHydratedRouter
payload={payload}
createFromReadableStream={createFromReadableStream}
unstable_getContext={unstable_getContext}
getContext={getContext}
/>
</StrictMode>,
);
Expand Down
6 changes: 3 additions & 3 deletions integration/helpers/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const reactRouterConfig = ({
appDirectory,
splitRouteModules,
viteEnvironmentApi,
middleware,
v8_middleware,
routeDiscovery,
}: {
ssr?: boolean;
Expand All @@ -41,7 +41,7 @@ export const reactRouterConfig = ({
Config["future"]
>["unstable_splitRouteModules"];
viteEnvironmentApi?: boolean;
middleware?: boolean;
v8_middleware?: boolean;
routeDiscovery?: Config["routeDiscovery"];
}) => {
let config: Config = {
Expand All @@ -53,7 +53,7 @@ export const reactRouterConfig = ({
future: {
unstable_splitRouteModules: splitRouteModules,
unstable_viteEnvironmentApi: viteEnvironmentApi,
unstable_middleware: middleware,
v8_middleware,
},
};

Expand Down
Loading
Loading