Skip to content

Commit 583fcce

Browse files
committed
Merge branch 'main' into release
2 parents c3cbbf8 + 96b0e83 commit 583fcce

File tree

7 files changed

+132
-64
lines changed

7 files changed

+132
-64
lines changed

contributors.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@
306306
- mtliendo
307307
- namoscato
308308
- Nandann018-ux
309+
- NandkishorJadoun
309310
- nanianlisao
310311
- ned-park
311312
- nenene3

docs/api/utils/redirect.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Respons
2626
Sets the status code and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
2727
header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).
2828

29+
This utility accepts absolute URLs and can navigate to external domains, so
30+
the application should validate any user-supplied inputs to redirects.
31+
2932
```tsx
3033
import { redirect } from "react-router";
3134

docs/api/utils/redirectDocument.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ that will force a document reload to the new location. Sets the status code
2727
and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
2828
header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).
2929

30+
This utility accepts absolute URLs and can navigate to external domains, so
31+
the application should validate any user-supplied inputs to redirects.
32+
3033
```tsx filename=routes/logout.tsx
3134
import { redirectDocument } from "react-router";
3235

docs/how-to/middleware.md

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ title: Middleware
99
<br/>
1010
<br/>
1111

12-
<docs-info>In Framework Mode, you must opt-into middleware via the [`future.v8_middleware`][future-flags] flag because it contains a minor [breaking change][getloadcontext] to the `getLoadContext` function and the loader/action `context` parameter.</docs-info>
13-
1412
Middleware allows you to run code before and after the [`Response`][Response] generation for the matched path. This enables [common patterns][common-patterns] like authentication, logging, error handling, and data preprocessing in a reusable way.
1513

1614
Middleware runs in a nested chain, executing from parent routes to child routes on the way "down" to your route handlers, then from child routes back to parent routes on the way "up" after a [`Response`][Response] is generated.
@@ -132,12 +130,27 @@ function getLoadContext(req, res) {
132130

133131
## Quick Start (Data Mode)
134132

135-
<docs-info>Note there is no future flag in Data Mode because you can opt-into middleware by adding it to your routes, no breaking changes exist that require a future flag.</docs-info>
133+
### 1. TypeScript: augment `Future` for loader/action `context`
136134

137-
### 1. Create a context
135+
In order to properly type your `context` param in your `loader`/`action`/`middleware` functions, you will need a small module augmentation to override the default context type of `any`:
138136

139-
Middleware uses a `context` provider instance to provide data down the middleware chain.
140-
You can create type-safe context objects using [`createContext`][createContext]:
137+
```ts
138+
// src/react-router.d.ts
139+
140+
import "react-router";
141+
142+
declare module "react-router" {
143+
interface Future {
144+
v8_middleware: true;
145+
}
146+
}
147+
```
148+
149+
Without this, `context` stays loosely typed even when middleware is enabled at runtime.
150+
151+
### 2. Create a context
152+
153+
Middleware uses a `context` provider to pass data through the middleware chain into loaders and actions. Create typed context with [`createContext`][createContext]:
141154

142155
```ts
143156
import { createContext } from "react-router";
@@ -146,10 +159,16 @@ import type { User } from "~/types";
146159
export const userContext = createContext<User | null>(null);
147160
```
148161

149-
### 2. Add middleware to your routes
162+
### 3. Add `middleware` to route objects
163+
164+
Attach `middleware` arrays to your route objects:
150165

151166
```tsx
152-
import { redirect } from "react-router";
167+
import {
168+
redirect,
169+
useLoaderData,
170+
type LoaderFunctionArgs,
171+
} from "react-router";
153172
import { userContext } from "~/context";
154173

155174
const routes = [
@@ -159,10 +178,10 @@ const routes = [
159178
Component: Root,
160179
children: [
161180
{
162-
path: "profile",
181+
path: "dashboard",
163182
middleware: [authMiddleware], // 👈
164-
loader: profileLoader,
165-
Component: Profile,
183+
loader: dashboardLoader,
184+
Component: Dashboard,
166185
},
167186
{
168187
path: "login",
@@ -187,15 +206,15 @@ async function authMiddleware({ context }) {
187206
context.set(userContext, user);
188207
}
189208

190-
export async function profileLoader({
209+
export async function dashboardLoader({
191210
context,
192-
}: Route.LoaderArgs) {
211+
}: LoaderFunctionArgs) {
193212
const user = context.get(userContext);
194213
const profile = await getProfile(user);
195214
return { profile };
196215
}
197216

198-
export default function Profile() {
217+
export default function Dashboard() {
199218
let loaderData = useLoaderData();
200219
return (
201220
<div>
@@ -206,9 +225,9 @@ export default function Profile() {
206225
}
207226
```
208227

209-
### 3. Add a `getContext` function (optional)
228+
### 4. Add a `getContext` function (optional)
210229

211-
If you wish to include a base context on all navigations/fetches, you can add an [`getContext`][getContext] function to your router. This will be called to populate a fresh context on every navigation/fetch.
230+
To seed every navigation or fetcher call with shared values, pass [`getContext`][getContext] when creating the router:
212231

213232
```tsx
214233
let sessionContext = createContext();
@@ -222,7 +241,7 @@ const router = createBrowserRouter(routes, {
222241
});
223242
```
224243

225-
<docs-info>This API exists to mirror the `getLoadContext` API on the server in Framework Mode, which exists as a way to hand off values from your HTTP server to the React Router handler. This [`getContext`][getContext] API can be used to hand off global values from the [`window`][window]/[`document`][document] to React Router, but because they're all running in the same context (the browser), you can achieve effectively the same behavior with root route middleware. Therefore, you may not need this API the same way you would on the server - but it's provided for consistency.</docs-warning>
244+
<docs-info>This mirrors Framework mode’s server-side [`getLoadContext`][getloadcontext]. In the browser, root `middleware` can often do the same job, but `getContext` is available when you want to seed every request up front.</docs-info>
226245

227246
## Core Concepts
228247

@@ -250,7 +269,7 @@ Client middleware runs in the browser in framework and data mode for client-side
250269
async function clientMiddleware({ request }, next) {
251270
console.log(request.method, request.url);
252271
await next();
253-
console.log(response.status, request.method, request.url);
272+
console.log(`Finished ${request.method} ${request.url}`);
254273
}
255274

256275
// Framework mode
@@ -720,6 +739,10 @@ export async function loader({
720739
[common-patterns]: #common-patterns
721740
[server-client]: #server-vs-client-middleware
722741
[rr-config]: ../api/framework-conventions/react-router.config.ts
742+
[create-browser-router]: ../api/data-routers/createBrowserRouter
743+
[create-hash-router]: ../api/data-routers/createHashRouter
744+
[create-memory-router]: ../api/data-routers/createMemoryRouter
745+
[create-static-handler]: ../api/data-routers/createStaticHandler
723746
[framework-action]: ../start/framework/route-module#action
724747
[framework-loader]: ../start/framework/route-module#loader
725748
[getloadcontext]: #changes-to-getloadcontextapploadcontext

docs/how-to/react-server-components.md

Lines changed: 60 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default defineConfig({
7777

7878
### Build Output
7979

80-
The RSC Framework Mode server build file (`build/server/index.js`) now exports a `default` request handler function (`(request: Request) => Promise<Response>`) for document/data requests.
80+
The RSC Framework Mode server build file (`build/server/index.js`) exports a `default` request handler function (`(request: Request) => Promise<Response>`) for document/data requests.
8181

8282
If needed, you can convert this into a [standard Node.js request listener][node-request-listener] for use with Node's built-in `http.createServer` function (or anything that supports it, e.g. [Express][express]) by using the `createRequestListener` function from [@remix-run/node-fetch-server][node-fetch-server].
8383

@@ -104,7 +104,7 @@ app.listen(3000);
104104

105105
### React Elements From Loaders/Actions
106106

107-
In RSC Framework Mode, loaders and actions can now return React elements along with other data. These elements will only ever be rendered on the server.
107+
In RSC Framework Mode, loaders and actions can return React elements along with other data. These elements will only ever be rendered on the server.
108108

109109
```tsx
110110
import type { Route } from "./+types/route";
@@ -133,6 +133,8 @@ If you need to use client-only features (e.g. [Hooks][hooks], event handlers) wi
133133
```tsx filename=src/routes/counter/counter.tsx
134134
"use client";
135135

136+
import { useState } from "react";
137+
136138
export function Counter() {
137139
const [count, setCount] = useState(0);
138140
return (
@@ -173,7 +175,9 @@ export default function Route({
173175

174176
### Route Server Components
175177

176-
If a route exports a `ServerComponent` instead of the typical `default` component export, this will be a server component rather than the usual client component. A default export and `ServerComponent` can not both be exported from the same route module, but you can still export client-only annotations like `clientLoader` and `clientAction` alongside a `ServerComponent`, along with any other component exports such as `ErrorBoundary` or `Layout`.
178+
If a route exports a `ServerComponent` instead of the typical `default` component export, the route renders on the server instead of the client. A route module cannot export both `default` and `ServerComponent`.
179+
180+
You can still export client-only annotations like `clientLoader` and `clientAction` alongside a `ServerComponent`. The other route module component exports follow the same client/server split: `ErrorBoundary`, `Layout`, and `HydrateFallback` are client components, while `ServerErrorBoundary`, `ServerLayout`, and `ServerHydrateFallback` render on the server.
177181

178182
The following route module components have their own mutually exclusive server component counterparts:
179183

@@ -213,6 +217,8 @@ If you need to use client-only features (e.g. [Hooks][hooks], event handlers) wi
213217
```tsx filename=src/routes/counter/counter.tsx
214218
"use client";
215219

220+
import { useState } from "react";
221+
216222
export function Counter() {
217223
const [count, setCount] = useState(0);
218224
return (
@@ -287,6 +293,8 @@ The plugin will automatically detect custom entry files in your `app` directory:
287293

288294
If these files are not found, React Router will use the default entries provided by the framework.
289295

296+
If you want to inspect the generated defaults before overriding them, you can also use `react-router reveal entry.client`, `react-router reveal entry.rsc`, and `react-router reveal entry.ssr`.
297+
290298
#### Basic Override Pattern
291299

292300
You can create a custom entry file that wraps or extends the default behavior. For example, to add custom logging to the RSC entry:
@@ -371,14 +379,11 @@ When copying default entries, make sure to maintain the required exports:
371379

372380
### Unsupported Config Options
373381

374-
For the initial unstable release, the following options from `react-router.config.ts` are not yet supported in RSC Framework Mode:
382+
The following options from `react-router.config.ts` are not currently supported in RSC Framework Mode:
375383

376384
- `buildEnd`
377-
- `prerender`
378385
- `presets`
379-
- `routeDiscovery`
380386
- `serverBundles`
381-
- `ssr: false` (SPA Mode)
382387
- `future.v8_splitRouteModules`
383388
- `future.unstable_subResourceIntegrity`
384389

@@ -403,11 +408,13 @@ matchRSCServerRequest({
403408
});
404409
```
405410

406-
While you can define components inline, we recommend using the `lazy()` option and defining [Route Modules][route-module] for both startup performance and code organization
411+
While you can define components inline, we recommend using the `lazy()` option and defining [Route Modules][route-module] for both startup performance and code organization.
407412

408413
<docs-info>
409414

410-
The [Route Module API][route-module] up until now has been a [Framework Mode][framework-mode] only feature. However, the `lazy` field of the RSC route config expects the same exports as the Route Module exports, unifying the APIs even further.
415+
The `lazy` field of the RSC route config expects the same exports as the [Route Module API][route-module], which keeps the route-module shape consistent across [Framework Mode][framework-mode] and RSC Data Mode.
416+
417+
That includes exports like `loader`, `action`, `meta`, `links`, `headers`, `ErrorBoundary`, `HydrateFallback`, and the client annotations.
411418

412419
</docs-info>
413420

@@ -542,6 +549,10 @@ export function clientAction() {}
542549
export function clientLoader() {}
543550

544551
export function shouldRevalidate() {}
552+
553+
export default function ClientRoot() {
554+
return <p>Client route</p>;
555+
}
545556
```
546557

547558
We can then re-export these from our lazy loaded route module:
@@ -551,7 +562,7 @@ export {
551562
clientAction,
552563
clientLoader,
553564
shouldRevalidate,
554-
} from "./route.client";
565+
} from "./client";
555566

556567
export default function Root() {
557568
// ...
@@ -566,7 +577,7 @@ export {
566577
clientAction,
567578
clientLoader,
568579
shouldRevalidate,
569-
} from "./route.client";
580+
} from "./client";
570581

571582
export default function Root() {
572583
// Adding a Server Component at the root is required by bundlers
@@ -721,8 +732,12 @@ export async function generateHTML(
721732
// Provide the React Server touchpoints.
722733
createFromReadableStream,
723734
// Render the router to HTML.
724-
async renderHTML(getPayload) {
725-
const payload = getPayload();
735+
async renderHTML(getPayload, options) {
736+
const payload = await getPayload();
737+
const formState =
738+
payload.type === "render"
739+
? await payload.formState
740+
: undefined;
726741

727742
const bootstrapScriptContent =
728743
await import.meta.viteRsc.loadBootstrapScriptContent(
@@ -732,8 +747,10 @@ export async function generateHTML(
732747
return await renderHTMLToReadableStream(
733748
<RSCStaticRouter getPayload={getPayload} />,
734749
{
750+
...options,
735751
bootstrapScriptContent,
736-
formState: payload.formState,
752+
formState,
753+
signal: request.signal,
737754
},
738755
);
739756
},
@@ -771,9 +788,9 @@ function fetchServer(request: Request) {
771788
// The app routes.
772789
routes: routes(),
773790
// Encode the match with the React Server implementation.
774-
generateResponse(match) {
791+
generateResponse(match, options) {
775792
return new Response(
776-
renderToReadableStream(match.payload),
793+
renderToReadableStream(match.payload, options),
777794
{
778795
status: match.statusCode,
779796
headers: match.headers,
@@ -811,8 +828,8 @@ import {
811828
unstable_createCallServer as createCallServer,
812829
unstable_getRSCStream as getRSCStream,
813830
unstable_RSCHydratedRouter as RSCHydratedRouter,
814-
type unstable_RSCPayload as RSCServerPayload,
815-
} from "react-router";
831+
type unstable_RSCPayload as RSCPayload,
832+
} from "react-router/dom";
816833

817834
// Create and set the callServer function to support post-hydration server actions.
818835
setServerCallback(
@@ -824,31 +841,31 @@ setServerCallback(
824841
);
825842

826843
// Get and decode the initial server payload.
827-
createFromReadableStream<RSCServerPayload>(
828-
getRSCStream(),
829-
).then((payload) => {
830-
startTransition(async () => {
831-
const formState =
832-
payload.type === "render"
833-
? await payload.formState
834-
: undefined;
835-
836-
hydrateRoot(
837-
document,
838-
<StrictMode>
839-
<RSCHydratedRouter
840-
createFromReadableStream={
841-
createFromReadableStream
842-
}
843-
payload={payload}
844-
/>
845-
</StrictMode>,
846-
{
847-
formState,
848-
},
849-
);
850-
});
851-
});
844+
createFromReadableStream<RSCPayload>(getRSCStream()).then(
845+
(payload) => {
846+
startTransition(async () => {
847+
const formState =
848+
payload.type === "render"
849+
? await payload.formState
850+
: undefined;
851+
852+
hydrateRoot(
853+
document,
854+
<StrictMode>
855+
<RSCHydratedRouter
856+
createFromReadableStream={
857+
createFromReadableStream
858+
}
859+
payload={payload}
860+
/>
861+
</StrictMode>,
862+
{
863+
formState,
864+
},
865+
);
866+
});
867+
},
868+
);
852869
```
853870

854871
[picking-a-mode]: ../start/modes

0 commit comments

Comments
 (0)