Skip to content

Commit b98209d

Browse files
docs: special files (#12340)
* Add a doc for special files * Undo typedoc.json addition
1 parent 845fb13 commit b98209d

File tree

1 file changed

+291
-0
lines changed

1 file changed

+291
-0
lines changed

docs/explanation/special-files.md

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
---
2+
title: Special Files
3+
---
4+
5+
# Special Files
6+
7+
There are a few special files that React Router looks for in your project. Not all of these files are required
8+
9+
## react-router.config.ts
10+
11+
**This file is optional**
12+
13+
The config file is used to configure certain aspects of your app, such as whether you are using server-side rendering, where certain directories are located, and more.
14+
15+
```tsx filename=react-router.config.ts
16+
import type { Config } from "@react-router/dev/config";
17+
18+
export default {
19+
// Config options...
20+
} satisfies Config;
21+
```
22+
23+
See the [config API](https://api.reactrouter.com/v7/types/_react_router_dev.config.Config.html) for more information.
24+
25+
## root.tsx
26+
27+
**This file is required**
28+
29+
The "root" route (`app/root.tsx`) is the only _required_ route in your React Router application because it is the parent to all routes in your `routes/` directory and is in charge of rendering the root `<html>` document.
30+
31+
Because the root route manages your document, it is the proper place to render a handful of "document-level" components React Router provides. These components are to be used once inside your root route and they include everything React Router figured out or built in order for your page to render properly.
32+
33+
```tsx filename=app/root.tsx
34+
import type { LinksFunction } from "react-router";
35+
import {
36+
Links,
37+
Meta,
38+
Outlet,
39+
Scripts,
40+
ScrollRestoration,
41+
} from "react-router";
42+
43+
import "./global-styles.css";
44+
45+
export default function App() {
46+
return (
47+
<html lang="en">
48+
<head>
49+
<meta charSet="utf-8" />
50+
<meta
51+
name="viewport"
52+
content="width=device-width, initial-scale=1"
53+
/>
54+
55+
{/* All `meta` exports on all routes will render here */}
56+
<Meta />
57+
58+
{/* All `link` exports on all routes will render here */}
59+
<Links />
60+
</head>
61+
<body>
62+
{/* Child routes render here */}
63+
<Outlet />
64+
65+
{/* Manages scroll position for client-side transitions */}
66+
{/* If you use a nonce-based content security policy for scripts, you must provide the `nonce` prop. Otherwise, omit the nonce prop as shown here. */}
67+
<ScrollRestoration />
68+
69+
{/* Script tags go here */}
70+
{/* If you use a nonce-based content security policy for scripts, you must provide the `nonce` prop. Otherwise, omit the nonce prop as shown here. */}
71+
<Scripts />
72+
</body>
73+
</html>
74+
);
75+
}
76+
```
77+
78+
### Layout export
79+
80+
The root route supports all [route module exports][route-module].
81+
82+
The root route also supports an additional optional `Layout` export. The `Layout` component serves 2 purposes:
83+
84+
1. Avoid duplicating your document's "app shell" across your root component, `HydrateFallback`, and `ErrorBoundary`
85+
2. Prevent React from re-mounting your app shell elements when switching between the root component/`HydrateFallback`/`ErrorBoundary` which can cause a FOUC if React removes and re-adds `<link rel="stylesheet">` tags from your `<Links>` component.
86+
87+
```tsx filename=app/root.tsx lines=[10-31]
88+
export function Layout({ children }) {
89+
return (
90+
<html lang="en">
91+
<head>
92+
<meta charSet="utf-8" />
93+
<meta
94+
name="viewport"
95+
content="width=device-width, initial-scale=1"
96+
/>
97+
<Meta />
98+
<Links />
99+
</head>
100+
<body>
101+
{/* children will be the root Component, ErrorBoundary, or HydrateFallback */}
102+
{children}
103+
<Scripts />
104+
<ScrollRestoration />
105+
</body>
106+
</html>
107+
);
108+
}
109+
110+
export default function App() {
111+
return <Outlet />;
112+
}
113+
114+
export function ErrorBoundary() {}
115+
```
116+
117+
**A note on `useLoaderData`in the `Layout` Component**
118+
119+
`useLoaderData` is not permitted to be used in `ErrorBoundary` components because it is intended for the happy-path route rendering, and its typings have a built-in assumption that the `loader` ran successfully and returned something. That assumption doesn't hold in an `ErrorBoundary` because it could have been the `loader` that threw and triggered the boundary! In order to access loader data in `ErrorBoundary`'s, you can use `useRouteLoaderData` which accounts for the loader data potentially being `undefined`.
120+
121+
Because your `Layout` component is used in both success and error flows, this same restriction holds. If you need to fork logic in your `Layout` depending on if it was a successful request or not, you can use `useRouteLoaderData("root")` and `useRouteError()`.
122+
123+
<docs-warn>Because your `<Layout>` component is used for rendering the `ErrorBoundary`, you should be _very defensive_ to ensure that you can render your `ErrorBoundary` without encountering any render errors. If your `Layout` throws another error trying to render the boundary, then it can't be used and your UI will fall back to the very minimal built-in default `ErrorBoundary`.</docs-warn>
124+
125+
```tsx filename=app/root.tsx lines=[6-7,19-29,32-34]
126+
export function Layout({
127+
children,
128+
}: {
129+
children: React.ReactNode;
130+
}) {
131+
const data = useRouteLoaderData("root");
132+
const error = useRouteError();
133+
134+
return (
135+
<html lang="en">
136+
<head>
137+
<meta charSet="utf-8" />
138+
<meta
139+
name="viewport"
140+
content="width=device-width, initial-scale=1"
141+
/>
142+
<Meta />
143+
<Links />
144+
<style
145+
dangerouslySetInnerHTML={{
146+
__html: `
147+
:root {
148+
--themeVar: ${
149+
data?.themeVar || defaultThemeVar
150+
}
151+
}
152+
`,
153+
}}
154+
/>
155+
</head>
156+
<body>
157+
{data ? (
158+
<Analytics token={data.analyticsToken} />
159+
) : null}
160+
{children}
161+
<ScrollRestoration />
162+
<Scripts />
163+
</body>
164+
</html>
165+
);
166+
}
167+
```
168+
169+
## routes.ts
170+
171+
**This file is required**
172+
173+
The `routes.ts` file is used to configure which url patterns are matched to which route modules.
174+
175+
```tsx filename=app/routes.ts
176+
import {
177+
type RouteConfig,
178+
route,
179+
} from "@react-router/dev/routes";
180+
181+
export default [
182+
route("some/path", "./some/file.tsx"),
183+
// pattern ^ ^ module file
184+
] satisfies RouteConfig;
185+
```
186+
187+
See the [routing guide][routing] for more information.
188+
189+
## entry.client.tsx
190+
191+
**This file is optional**
192+
193+
By default, React Router will handle hydrating your app on the client for you. You can reveal the default entry client file with the following:
194+
195+
```shellscript nonumber
196+
react-router reveal
197+
```
198+
199+
This file is the entry point for the browser and is responsible for hydrating the markup generated by the server in your [server entry module][server-entry], however you can also initialize any other client-side code here.
200+
201+
```tsx filename=app/entry.client.tsx
202+
import { startTransition, StrictMode } from "react";
203+
import { hydrateRoot } from "react-dom/client";
204+
import { HydratedRouter } from "react-router/dom";
205+
206+
startTransition(() => {
207+
hydrateRoot(
208+
document,
209+
<StrictMode>
210+
<HydratedRouter />
211+
</StrictMode>
212+
);
213+
});
214+
```
215+
216+
This is the first piece of code that runs in the browser. You can initialize client side libraries, add client only providers, etc.
217+
218+
## entry.server.tsx
219+
220+
**This file is optional**
221+
222+
By default, React Router will handle generating the HTTP Response for you. You can reveal the default entry server file with the following:
223+
224+
```shellscript nonumber
225+
react-router reveal
226+
```
227+
228+
The `default` export of this module is a function that lets you create the response, including HTTP status, headers, and HTML, giving you full control over the way the markup is generated and sent to the client.
229+
230+
This module should render the markup for the current page using a `<ServerRouter>` element with the `context` and `url` for the current request. This markup will (optionally) be re-hydrated once JavaScript loads in the browser using the [client entry module][client-entry].
231+
232+
### `handleDataRequest`
233+
234+
You can export an optional `handleDataRequest` function that will allow you to modify the response of a data request. These are the requests that do not render HTML, but rather return the loader and action data to the browser once client-side hydration has occurred.
235+
236+
```tsx
237+
export function handleDataRequest(
238+
response: Response,
239+
{
240+
request,
241+
params,
242+
context,
243+
}: LoaderFunctionArgs | ActionFunctionArgs
244+
) {
245+
response.headers.set("X-Custom-Header", "value");
246+
return response;
247+
}
248+
```
249+
250+
### `handleError`
251+
252+
By default, React Router will log encountered server-side errors to the console. If you'd like more control over the logging, or would like to also report these errors to an external service, then you can export an optional `handleError` function which will give you control (and will disable the built-in error logging).
253+
254+
```tsx
255+
export function handleError(
256+
error: unknown,
257+
{
258+
request,
259+
params,
260+
context,
261+
}: LoaderFunctionArgs | ActionFunctionArgs
262+
) {
263+
if (!request.signal.aborted) {
264+
sendErrorToErrorReportingService(error);
265+
console.error(formatErrorForJsonLogging(error));
266+
}
267+
}
268+
```
269+
270+
_Note that you generally want to avoid logging when the request was aborted, since React Router's cancellation and race-condition handling can cause a lot of requests to be aborted._
271+
272+
### Streaming Rendering Errors
273+
274+
When you are streaming your HTML responses via [`renderToPipeableStream`][rendertopipeablestream] or [`renderToReadableStream`][rendertoreadablestream], your own `handleError` implementation will only handle errors encountered during the initial shell render. If you encounter a rendering error during subsequent streamed rendering you will need to handle these errors manually since the React Router server has already sent the Response by that point.
275+
276+
For `renderToPipeableStream`, you can handle these errors in the `onError` callback function. You will need to toggle a boolean in `onShellReady` so you know if the error was a shell rendering error (and can be ignored) or an async
277+
278+
For an example, please refer to the default [`entry.server.tsx`][node-streaming-entry-server] for Node.
279+
280+
**Thrown Responses**
281+
282+
Note that this does not handle thrown `Response` instances from your `loader`/`action` functions. The intention of this handler is to find bugs in your code which result in unexpected thrown errors. If you are detecting a scenario and throwing a 401/404/etc. `Response` in your `loader`/`action` then it's an expected flow that is handled by your code. If you also wish to log, or send those to an external service, that should be done at the time you throw the response.
283+
284+
[react-router-config]: https://api.reactrouter.com/v7/types/_react_router_dev.config.Config.html
285+
[route-module]: ../start/framework/route-module
286+
[routing]: ../start/framework/routing
287+
[server-entry]: #entryservertsx
288+
[client-entry]: #entryclienttsx
289+
[rendertopipeablestream]: https://react.dev/reference/react-dom/server/renderToPipeableStream
290+
[rendertoreadablestream]: https://react.dev/reference/react-dom/server/renderToReadableStream
291+
[node-streaming-entry-server]: https://github.com/remix-run/react-router/blob/dev/packages/react-router-dev/config/defaults/entry.server.node.tsx

0 commit comments

Comments
 (0)