UNPKG

4.58 kBMarkdownView Raw
1---
2title: Resource Routes
3---
4
5# Resource Routes
6
7[MODES: framework, data]
8
9<br/>
10<br/>
11
12When server rendering, routes can serve "resources" instead of rendering components, like images, PDFs, JSON payloads, webhooks, etc.
13
14## Defining a Resource Route
15
16A route becomes a resource route by convention when its module exports a loader or action but does not export a default component.
17
18Consider a route that serves a PDF instead of UI:
19
20```ts
21route("/reports/pdf/:id", "pdf-report.ts");
22```
23
24```tsx filename=pdf-report.ts
25import type { Route } from "./+types/pdf-report";
26
27export async function loader({ params }: Route.LoaderArgs) {
28 const report = await getReport(params.id);
29 const pdf = await generateReportPDF(report);
30 return new Response(pdf, {
31 status: 200,
32 headers: {
33 "Content-Type": "application/pdf",
34 },
35 });
36}
37```
38
39Note there is no default export. That makes this route a resource route.
40
41## Linking to Resource Routes
42
43When linking to resource routes, use `<a>` or `<Link reloadDocument>`, otherwise React Router will attempt to use client side routing and fetching the payload (you'll get a helpful error message if you make this mistake).
44
45```tsx
46<Link reloadDocument to="/reports/pdf/123">
47 View as PDF
48</Link>
49```
50
51## Handling different request methods
52
53GET requests are handled by the `loader`, while POST, PUT, PATCH, and DELETE are handled by the `action`:
54
55```tsx
56import type { Route } from "./+types/resource";
57
58export function loader(_: Route.LoaderArgs) {
59 return Response.json({ message: "I handle GET" });
60}
61
62export function action(_: Route.ActionArgs) {
63 return Response.json({
64 message: "I handle everything else",
65 });
66}
67```
68
69Calling this `action` through [`<Form>`][form] or [`useFetcher`][fetcher] still revalidates matched UI loaders. A plain `fetch()` to the resource URL does not. See [Revalidation Optimization][optimize-revalidation] for more info.
70
71## Return Types
72
73Resource Routes are flexible when it comes to the return type - you can return [`Response`][Response] instances or [`data()`][data] objects. A good general rule of thumb when deciding which type to use is:
74
75- If you're using resource routes intended for external consumption, return `Response` instances
76 - Keeps the resulting response encoding explicit in your code rather than having to wonder how React Router might convert `data() -> Response` under the hood
77- If you're accessing resource routes from [fetchers][fetcher] or [`<Form>`][form] submissions, return `data()`
78 - Keeps things consistent with the loaders/actions in your UI routes
79 - Allows you to stream promises down to your UI through `data()`/[`Await`][await]
80
81## Error Handling
82
83Throwing an `Error` from Resource route (or anything other than a `Response`/`data()`) will trigger [`handleError`][handleError] and result in a 500 HTTP Response:
84
85```tsx
86export function action() {
87 let db = await getDb();
88 if (!db) {
89 // Fatal error - return a 500 response and trigger `handleError`
90 throw new Error("Could not connect to DB");
91 }
92 // ...
93}
94```
95
96If a resource route generates a `Response` (via `new Response()` or `data()`), it is considered a successful execution and will not trigger `handleError` because the API has successfully produced a Response for the HTTP request. This applies to thrown responses as well as returned responses with a 4xx/5xx status code. This behavior aligns with `fetch()` which does not return a rejected promise on 4xx/5xx Responses.
97
98```tsx
99export function action() {
100 // Non-fatal error - don't trigger `handleError`:
101 throw new Response(
102 { error: "Unauthorized" },
103 { status: 401 },
104 );
105
106 // These 3 are equivalent to the above
107 return new Response(
108 { error: "Unauthorized" },
109 { status: 401 },
110 );
111
112 throw data({ error: "Unauthorized" }, { status: 401 });
113
114 return data({ error: "Unauthorized" }, { status: 401 });
115}
116```
117
118### Error Boundaries
119
120[Error Boundaries][error-boundary] are only applicable when a resource route is accessed from a UI, such as from a [`fetcher`][fetcher] call or a [`<Form>`][form] submission. If you `throw` from your resource route in these cases, it will bubble to the nearest `ErrorBoundary` in the UI.
121
122[handleError]: ../api/framework-conventions/entry.server.tsx#handleerror
123[data]: ../api/utils/data
124[Response]: https://developer.mozilla.org/en-US/docs/Web/API/Response
125[fetcher]: ../api/hooks/useFetcher
126[form]: ../api/components/Form
127[await]: ../api/components/Await
128[error-boundary]: ../start/framework/route-module#errorboundary
129[optimize-revalidation]: ./optimize-revalidation