react-router 7.16.0 → 7.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/dist/development/{browser-nIQ4Nsyi.d.mts → browser-CGcs-0pD.d.mts} +1 -1
  3. package/dist/development/{chunk-QUQL4437.mjs → chunk-6CSD65Y2.mjs} +2 -2
  4. package/dist/{production/chunk-NALGHHKE.mjs → development/chunk-ASILSGTR.mjs} +2 -2
  5. package/dist/development/{chunk-SRID2YZ2.js → chunk-KFNXW4AL.js} +1 -1
  6. package/dist/development/{chunk-XEJDWL2B.js → chunk-PBLBZ3QU.js} +7 -7
  7. package/dist/{production/chunk-SKEDDLRM.js → development/chunk-PULC7NLK.js} +99 -99
  8. package/dist/development/{context-m8rizgnE.d.mts → context-CmHpk1Ws.d.mts} +1 -1
  9. package/dist/development/dom-export.d.mts +3 -3
  10. package/dist/development/dom-export.d.ts +1 -1
  11. package/dist/development/dom-export.js +28 -28
  12. package/dist/development/dom-export.mjs +3 -3
  13. package/dist/development/{index-react-server-client-BLiUx67a.d.ts → index-react-server-client-CwU9bE5R.d.ts} +1 -1
  14. package/dist/development/{index-react-server-client-CdKROblb.d.mts → index-react-server-client-DPrDrCew.d.mts} +1 -1
  15. package/dist/development/index-react-server-client.d.mts +2 -2
  16. package/dist/development/index-react-server-client.d.ts +1 -1
  17. package/dist/development/index-react-server-client.js +4 -4
  18. package/dist/development/index-react-server-client.mjs +2 -2
  19. package/dist/development/index-react-server.js +1 -1
  20. package/dist/development/index-react-server.mjs +1 -1
  21. package/dist/development/index.d.mts +6 -6
  22. package/dist/development/index.d.ts +2 -2
  23. package/dist/development/index.js +85 -85
  24. package/dist/development/index.mjs +3 -3
  25. package/dist/development/lib/types/internal.js +1 -1
  26. package/dist/development/lib/types/internal.mjs +1 -1
  27. package/dist/production/{browser-nIQ4Nsyi.d.mts → browser-CGcs-0pD.d.mts} +1 -1
  28. package/dist/{development/chunk-S54KXAEJ.mjs → production/chunk-5TQZEVD5.mjs} +2 -2
  29. package/dist/production/{chunk-EAQNHM3N.js → chunk-CTIXC7EV.js} +7 -7
  30. package/dist/{development/chunk-IBI7OMNB.js → production/chunk-EN242BO4.js} +99 -99
  31. package/dist/production/{chunk-Q65P7S7Y.mjs → chunk-OSYEOCBT.mjs} +2 -2
  32. package/dist/production/{chunk-Y7DNFQZP.js → chunk-RTRY3JFT.js} +1 -1
  33. package/dist/production/{context-m8rizgnE.d.mts → context-CmHpk1Ws.d.mts} +1 -1
  34. package/dist/production/dom-export.d.mts +3 -3
  35. package/dist/production/dom-export.d.ts +1 -1
  36. package/dist/production/dom-export.js +28 -28
  37. package/dist/production/dom-export.mjs +3 -3
  38. package/dist/production/{index-react-server-client-BLiUx67a.d.ts → index-react-server-client-CwU9bE5R.d.ts} +1 -1
  39. package/dist/production/{index-react-server-client-CdKROblb.d.mts → index-react-server-client-DPrDrCew.d.mts} +1 -1
  40. package/dist/production/index-react-server-client.d.mts +2 -2
  41. package/dist/production/index-react-server-client.d.ts +1 -1
  42. package/dist/production/index-react-server-client.js +4 -4
  43. package/dist/production/index-react-server-client.mjs +2 -2
  44. package/dist/production/index-react-server.js +1 -1
  45. package/dist/production/index-react-server.mjs +1 -1
  46. package/dist/production/index.d.mts +6 -6
  47. package/dist/production/index.d.ts +2 -2
  48. package/dist/production/index.js +85 -85
  49. package/dist/production/index.mjs +3 -3
  50. package/dist/production/lib/types/internal.js +1 -1
  51. package/dist/production/lib/types/internal.mjs +1 -1
  52. package/docs/explanation/backend-for-frontend.md +50 -0
  53. package/docs/explanation/code-splitting.md +61 -0
  54. package/docs/explanation/concurrency.md +135 -0
  55. package/docs/explanation/form-vs-fetcher.md +292 -0
  56. package/docs/explanation/hot-module-replacement.md +137 -0
  57. package/docs/explanation/hydration.md +14 -0
  58. package/docs/explanation/index-query-param.md +86 -0
  59. package/docs/explanation/index.md +4 -0
  60. package/docs/explanation/lazy-route-discovery.md +78 -0
  61. package/docs/explanation/location.md +6 -0
  62. package/docs/explanation/progressive-enhancement.md +150 -0
  63. package/docs/explanation/race-conditions.md +88 -0
  64. package/docs/explanation/react-transitions.md +160 -0
  65. package/docs/explanation/route-matching.md +7 -0
  66. package/docs/explanation/server-client-execution.md +4 -0
  67. package/docs/explanation/sessions-and-cookies.md +465 -0
  68. package/docs/explanation/special-files.md +16 -0
  69. package/docs/explanation/state-management.md +524 -0
  70. package/docs/explanation/styling.md +87 -0
  71. package/docs/explanation/type-safety.md +82 -0
  72. package/docs/how-to/accessibility.md +44 -0
  73. package/docs/how-to/client-data.md +199 -0
  74. package/docs/how-to/data-strategy.md +317 -0
  75. package/docs/how-to/error-boundary.md +231 -0
  76. package/docs/how-to/error-reporting.md +142 -0
  77. package/docs/how-to/fetchers.md +307 -0
  78. package/docs/how-to/file-route-conventions.md +410 -0
  79. package/docs/how-to/file-uploads.md +217 -0
  80. package/docs/how-to/form-validation.md +120 -0
  81. package/docs/how-to/headers.md +164 -0
  82. package/docs/how-to/index.md +4 -0
  83. package/docs/how-to/instrumentation.md +556 -0
  84. package/docs/how-to/meta.md +40 -0
  85. package/docs/how-to/middleware.md +763 -0
  86. package/docs/how-to/navigation-blocking.md +233 -0
  87. package/docs/how-to/optimize-revalidation.md +12 -0
  88. package/docs/how-to/pre-rendering.md +225 -0
  89. package/docs/how-to/presets.md +103 -0
  90. package/docs/how-to/react-server-components.md +899 -0
  91. package/docs/how-to/resource-routes.md +126 -0
  92. package/docs/how-to/route-module-type-safety.md +100 -0
  93. package/docs/how-to/search-params.md +4 -0
  94. package/docs/how-to/security.md +30 -0
  95. package/docs/how-to/server-bundles.md +66 -0
  96. package/docs/how-to/spa.md +120 -0
  97. package/docs/how-to/status.md +63 -0
  98. package/docs/how-to/suspense.md +132 -0
  99. package/docs/how-to/using-handle.md +117 -0
  100. package/docs/how-to/view-transitions.md +237 -0
  101. package/docs/how-to/webhook.md +50 -0
  102. package/docs/index.md +39 -0
  103. package/docs/start/data/actions.md +138 -0
  104. package/docs/start/data/custom.md +198 -0
  105. package/docs/start/data/data-loading.md +44 -0
  106. package/docs/start/data/index.md +4 -0
  107. package/docs/start/data/installation.md +52 -0
  108. package/docs/start/data/navigating.md +12 -0
  109. package/docs/start/data/pending-ui.md +12 -0
  110. package/docs/start/data/route-object.md +268 -0
  111. package/docs/start/data/routing.md +281 -0
  112. package/docs/start/data/testing.md +8 -0
  113. package/docs/start/declarative/index.md +4 -0
  114. package/docs/start/declarative/installation.md +43 -0
  115. package/docs/start/declarative/navigating.md +133 -0
  116. package/docs/start/declarative/routing.md +237 -0
  117. package/docs/start/declarative/url-values.md +65 -0
  118. package/docs/start/framework/actions.md +174 -0
  119. package/docs/start/framework/data-loading.md +201 -0
  120. package/docs/start/framework/deploying.md +96 -0
  121. package/docs/start/framework/index.md +4 -0
  122. package/docs/start/framework/installation.md +41 -0
  123. package/docs/start/framework/navigating.md +182 -0
  124. package/docs/start/framework/pending-ui.md +142 -0
  125. package/docs/start/framework/rendering.md +59 -0
  126. package/docs/start/framework/route-module.md +527 -0
  127. package/docs/start/framework/routing.md +362 -0
  128. package/docs/start/framework/testing.md +133 -0
  129. package/docs/start/index.md +4 -0
  130. package/docs/start/modes.md +201 -0
  131. package/docs/upgrading/component-routes.md +363 -0
  132. package/docs/upgrading/future.md +280 -0
  133. package/docs/upgrading/index.md +4 -0
  134. package/docs/upgrading/remix.md +403 -0
  135. package/docs/upgrading/router-provider.md +442 -0
  136. package/docs/upgrading/v6.md +382 -0
  137. package/package.json +2 -1
@@ -0,0 +1,556 @@
1
+ ---
2
+ title: Instrumentation
3
+ ---
4
+
5
+ # Instrumentation
6
+
7
+ [MODES: framework, data]
8
+
9
+ <br/>
10
+ <br/>
11
+
12
+ Instrumentation allows you to add logging, error reporting, and performance tracing to your React Router application without modifying your actual route handlers. This enables comprehensive observability solutions for production applications on both the server and client.
13
+
14
+ ## Overview
15
+
16
+ With the React Router Instrumentation APIs, you provide "wrapper" functions that execute around your request handlers, router operations, route middlewares, and/or route handlers. This allows you to:
17
+
18
+ - Monitor application performance
19
+ - Add logging
20
+ - Integrate with observability platforms (Sentry, DataDog, New Relic, etc.)
21
+ - Implement OpenTelemetry tracing
22
+ - Track user behavior and navigation patterns
23
+
24
+ A key design principle is that instrumentation is **read-only** - you can observe what's happening but cannot modify runtime application behavior by modifying the arguments passed to, or data returned from your route handlers.
25
+
26
+ <docs-info>
27
+ As with any instrumentation approach, adding additional code execution at runtime may alter the performance characteristics compared to an uninstrumented application. Keep this in mind and perform appropriate testing and/or leverage conditional instrumentation to avoid a negative UX impact in production.
28
+ </docs-info>
29
+
30
+ ## Quick Start (Framework Mode)
31
+
32
+ [modes: framework]
33
+
34
+ ### 1. Server-side Instrumentation
35
+
36
+ Add instrumentations to your `entry.server.tsx`:
37
+
38
+ ```tsx filename=app/entry.server.tsx
39
+ export const instrumentations = [
40
+ {
41
+ // Instrument the server handler
42
+ handler(handler) {
43
+ handler.instrument({
44
+ async request(handleRequest, { request }) {
45
+ let url = `${request.method} ${request.url}`;
46
+ console.log(`Request start: ${url}`);
47
+ await handleRequest();
48
+ console.log(`Request end: ${url}`);
49
+ },
50
+ });
51
+ },
52
+
53
+ // Instrument individual routes
54
+ route(route) {
55
+ // Skip instrumentation for specific routes if needed
56
+ if (route.id === "root") return;
57
+
58
+ route.instrument({
59
+ async loader(callLoader, { request }) {
60
+ let url = `${request.method} ${request.url}`;
61
+ console.log(`Loader start: ${url} - ${route.id}`);
62
+ await callLoader();
63
+ console.log(`Loader end: ${url} - ${route.id}`);
64
+ },
65
+ // Other available instrumentations:
66
+ // async action() { /* ... */ },
67
+ // async middleware() { /* ... */ },
68
+ // async lazy() { /* ... */ },
69
+ });
70
+ },
71
+ },
72
+ ];
73
+
74
+ export default function handleRequest(/* ... */) {
75
+ // Your existing handleRequest implementation
76
+ }
77
+ ```
78
+
79
+ ### 2. Client-side Instrumentation
80
+
81
+ Add instrumentations to your `entry.client.tsx`:
82
+
83
+ ```tsx filename=app/entry.client.tsx
84
+ import { startTransition, StrictMode } from "react";
85
+ import { hydrateRoot } from "react-dom/client";
86
+ import { HydratedRouter } from "react-router/dom";
87
+
88
+ const instrumentations = [
89
+ {
90
+ // Instrument router operations
91
+ router(router) {
92
+ router.instrument({
93
+ // Instrument navigations
94
+ async navigate(callNavigate, { currentUrl, to }) {
95
+ let nav = `${currentUrl} → ${to}`;
96
+ console.log(`Navigation start: ${nav}`);
97
+ await callNavigate();
98
+ console.log(`Navigation end: ${nav}`);
99
+ },
100
+ // Instrument fetcher calls
101
+ async fetch(
102
+ callFetch,
103
+ { href, currentUrl, fetcherKey },
104
+ ) {
105
+ let fetch = `${fetcherKey} → ${href}`;
106
+ console.log(`Fetcher start: ${fetch}`);
107
+ await callFetch();
108
+ console.log(`Fetcher end: ${fetch}`);
109
+ },
110
+ });
111
+ },
112
+
113
+ // Instrument individual routes (same as server-side)
114
+ route(route) {
115
+ // Skip instrumentation for specific routes if needed
116
+ if (route.id === "root") return;
117
+
118
+ route.instrument({
119
+ async loader(callLoader, { request }) {
120
+ let url = `${request.method} ${request.url}`;
121
+ console.log(`Loader start: ${url} - ${route.id}`);
122
+ await callLoader();
123
+ console.log(`Loader end: ${url} - ${route.id}`);
124
+ },
125
+ // Other available instrumentations:
126
+ // async action() { /* ... */ },
127
+ // async middleware() { /* ... */ },
128
+ // async lazy() { /* ... */ },
129
+ });
130
+ },
131
+ },
132
+ ];
133
+
134
+ startTransition(() => {
135
+ hydrateRoot(
136
+ document,
137
+ <StrictMode>
138
+ <HydratedRouter instrumentations={instrumentations} />
139
+ </StrictMode>,
140
+ );
141
+ });
142
+ ```
143
+
144
+ ## Quick Start (Data Mode)
145
+
146
+ [modes: data]
147
+
148
+ In Data Mode, you add instrumentations when creating your router:
149
+
150
+ ```tsx
151
+ import {
152
+ createBrowserRouter,
153
+ RouterProvider,
154
+ } from "react-router";
155
+
156
+ const instrumentations = [
157
+ {
158
+ // Instrument router operations
159
+ router(router) {
160
+ router.instrument({
161
+ // Instrument navigations
162
+ async navigate(callNavigate, { currentUrl, to }) {
163
+ let nav = `${currentUrl} → ${to}`;
164
+ console.log(`Navigation start: ${nav}`);
165
+ await callNavigate();
166
+ console.log(`Navigation end: ${nav}`);
167
+ },
168
+ // Instrument fetcher calls
169
+ async fetch(
170
+ callFetch,
171
+ { href, currentUrl, fetcherKey },
172
+ ) {
173
+ let fetch = `${fetcherKey} → ${href}`;
174
+ console.log(`Fetcher start: ${fetch}`);
175
+ await callFetch();
176
+ console.log(`Fetcher end: ${fetch}`);
177
+ },
178
+ });
179
+ },
180
+
181
+ // Instrument individual routes (same as server-side)
182
+ route(route) {
183
+ // Skip instrumentation for specific routes if needed
184
+ if (route.id === "root") return;
185
+
186
+ route.instrument({
187
+ async loader(callLoader, { request }) {
188
+ let url = `${request.method} ${request.url}`;
189
+ console.log(`Loader start: ${url} - ${route.id}`);
190
+ await callLoader();
191
+ console.log(`Loader end: ${url} - ${route.id}`);
192
+ },
193
+ // Other available instrumentations:
194
+ // async action() { /* ... */ },
195
+ // async middleware() { /* ... */ },
196
+ // async lazy() { /* ... */ },
197
+ });
198
+ },
199
+ },
200
+ ];
201
+
202
+ const router = createBrowserRouter(routes, {
203
+ instrumentations,
204
+ });
205
+
206
+ function App() {
207
+ return <RouterProvider router={router} />;
208
+ }
209
+ ```
210
+
211
+ ## Core Concepts
212
+
213
+ ### Instrumentation Levels
214
+
215
+ There are different levels at which you can instrument your application. Each instrumentation function receives a second "info" parameter containing relevant contextual information for the specific aspect being instrumented.
216
+
217
+ #### 1. Handler Level (Server)
218
+
219
+ [modes: framework]
220
+
221
+ Instruments the top-level request handler that processes all requests to your server:
222
+
223
+ ```tsx filename=entry.server.tsx
224
+ export const instrumentations = [
225
+ {
226
+ handler(handler) {
227
+ handler.instrument({
228
+ async request(handleRequest, { request, context }) {
229
+ // Runs around ALL requests to your app
230
+ await handleRequest();
231
+ },
232
+ });
233
+ },
234
+ },
235
+ ];
236
+ ```
237
+
238
+ #### 2. Router Level (Client)
239
+
240
+ [modes: framework,data]
241
+
242
+ Instruments client-side router operations like navigations and fetcher calls:
243
+
244
+ ```tsx
245
+ export const instrumentations = [
246
+ {
247
+ router(router) {
248
+ router.instrument({
249
+ async navigate(callNavigate, { to, currentUrl }) {
250
+ // Runs around navigation operations
251
+ await callNavigate();
252
+ },
253
+ async fetch(
254
+ callFetch,
255
+ { href, currentUrl, fetcherKey },
256
+ ) {
257
+ // Runs around fetcher operations
258
+ await callFetch();
259
+ },
260
+ });
261
+ },
262
+ },
263
+ ];
264
+
265
+ // Framework Mode (entry.client.tsx)
266
+ <HydratedRouter instrumentations={instrumentations} />;
267
+
268
+ // Data Mode
269
+ const router = createBrowserRouter(routes, {
270
+ instrumentations,
271
+ });
272
+ ```
273
+
274
+ #### 3. Route Level (Server + Client)
275
+
276
+ [modes: framework,data]
277
+
278
+ Instruments individual route handlers:
279
+
280
+ ```tsx
281
+ const instrumentations = [
282
+ {
283
+ route(route) {
284
+ route.instrument({
285
+ async loader(
286
+ callLoader,
287
+ { params, request, context, pattern },
288
+ ) {
289
+ // Runs around loader execution
290
+ await callLoader();
291
+ },
292
+ async action(
293
+ callAction,
294
+ { params, request, context, pattern },
295
+ ) {
296
+ // Runs around action execution
297
+ await callAction();
298
+ },
299
+ async middleware(
300
+ callMiddleware,
301
+ { params, request, context, pattern },
302
+ ) {
303
+ // Runs around middleware execution
304
+ await callMiddleware();
305
+ },
306
+ async lazy(callLazy) {
307
+ // Runs around lazy route loading
308
+ await callLazy();
309
+ },
310
+ });
311
+ },
312
+ },
313
+ ];
314
+ ```
315
+
316
+ ### Read-only Design
317
+
318
+ Instrumentations are designed to be **observational only**. You cannot:
319
+
320
+ - Modify arguments passed to handlers
321
+ - Change return values from handlers
322
+ - Alter application behavior
323
+
324
+ This ensures that instrumentation is safe to add to production applications and cannot introduce bugs in your route logic.
325
+
326
+ ### Error Handling
327
+
328
+ To ensure that instrumentation code doesn't impact the runtime application, errors are caught internally and prevented from propagating outward. This design choice shows up in 2 aspects.
329
+
330
+ First, if a "handler" function (loader, action, request handler, navigation, etc.) throws an error, that error will not bubble out of the `callHandler` function invoked from your instrumentation. Instead, the `callHandler` function returns a discriminated union result of type `{ type: "success", error: undefined } | { type: "error", error: unknown }`. This ensures your entire instrumentation function runs without needing any try/catch/finally logic to handle application errors.
331
+
332
+ ```tsx
333
+ export const instrumentations = [
334
+ {
335
+ route(route) {
336
+ route.instrument({
337
+ async loader(callLoader) {
338
+ let { status, error } = await callLoader();
339
+
340
+ if (status === "error") {
341
+ // error case - `error` is defined
342
+ } else {
343
+ // success case - `error` is undefined
344
+ }
345
+ },
346
+ });
347
+ },
348
+ },
349
+ ];
350
+ ```
351
+
352
+ Second, if your instrumentation function throws an error, React Router will gracefully swallow that so that it does not bubble outward and impact other instrumentations or application behavior. In both of these examples, the handlers and all other instrumentation functions will still run:
353
+
354
+ ```tsx
355
+ export const instrumentations = [
356
+ {
357
+ route(route) {
358
+ route.instrument({
359
+ // Throwing before calling the handler - RR will
360
+ // catch the error and still call the loader
361
+ async loader(callLoader) {
362
+ somethingThatThrows();
363
+ await callLoader();
364
+ },
365
+ // Throwing after calling the handler - RR will
366
+ // catch the error internally
367
+ async action(callAction) {
368
+ await callAction();
369
+ somethingThatThrows();
370
+ },
371
+ });
372
+ },
373
+ },
374
+ ];
375
+ ```
376
+
377
+ ### Composition
378
+
379
+ You can compose multiple instrumentations by providing an array:
380
+
381
+ ```tsx
382
+ export const instrumentations = [
383
+ loggingInstrumentation,
384
+ performanceInstrumentation,
385
+ errorReportingInstrumentation,
386
+ ];
387
+ ```
388
+
389
+ Each instrumentation wraps the previous one, creating a nested execution chain.
390
+
391
+ ### Conditional Instrumentation
392
+
393
+ You can enable instrumentation conditionally based on environment or other factors:
394
+
395
+ ```tsx
396
+ export const instrumentations =
397
+ process.env.NODE_ENV === "production"
398
+ ? [productionInstrumentation]
399
+ : [developmentInstrumentation];
400
+ ```
401
+
402
+ ```tsx
403
+ // Or conditionally within an instrumentation
404
+ export const instrumentations = [
405
+ {
406
+ route(route) {
407
+ // Only instrument specific routes
408
+ if (!route.id?.startsWith("routes/admin")) return;
409
+
410
+ // Or, only instrument if a query parameter is present
411
+ let sp = new URL(request.url).searchParams;
412
+ if (!sp.has("DEBUG")) return;
413
+
414
+ route.instrument({
415
+ async loader() {
416
+ /* ... */
417
+ },
418
+ });
419
+ },
420
+ },
421
+ ];
422
+ ```
423
+
424
+ ## Common Patterns
425
+
426
+ ### Request logging (server)
427
+
428
+ ```tsx
429
+ const logging: ServerInstrumentation = {
430
+ handler({ instrument }) {
431
+ instrument({
432
+ request: (fn, { request }) =>
433
+ log(`request ${request.url}`, fn),
434
+ });
435
+ },
436
+ route({ instrument, id }) {
437
+ instrument({
438
+ middleware: (fn) => log(` middleware (${id})`, fn),
439
+ loader: (fn) => log(` loader (${id})`, fn),
440
+ action: (fn) => log(` action (${id})`, fn),
441
+ });
442
+ },
443
+ };
444
+
445
+ async function log(
446
+ label: string,
447
+ cb: () => Promise<InstrumentationHandlerResult>,
448
+ ) {
449
+ let start = Date.now();
450
+ console.log(`➡️ ${label}`);
451
+ await cb();
452
+ console.log(`⬅️ ${label} (${Date.now() - start}ms)`);
453
+ }
454
+
455
+ export const instrumentations = [logging];
456
+ ```
457
+
458
+ ### OpenTelemetry Integration
459
+
460
+ ```tsx
461
+ import { trace, SpanStatusCode } from "@opentelemetry/api";
462
+
463
+ const tracer = trace.getTracer("my-app");
464
+
465
+ const otel: ServerInstrumentation = {
466
+ handler({ instrument }) {
467
+ instrument({
468
+ request: (fn, { request }) =>
469
+ otelSpan(`request`, { url: request.url }, fn),
470
+ });
471
+ },
472
+ route({ instrument, id }) {
473
+ instrument({
474
+ middleware: (fn, { pattern }) =>
475
+ otelSpan(
476
+ "middleware",
477
+ { routeId: id, pattern: pattern },
478
+ fn,
479
+ ),
480
+ loader: (fn, { pattern }) =>
481
+ otelSpan(
482
+ "loader",
483
+ { routeId: id, pattern: pattern },
484
+ fn,
485
+ ),
486
+ action: (fn, { pattern }) =>
487
+ otelSpan(
488
+ "action",
489
+ { routeId: id, pattern: pattern },
490
+ fn,
491
+ ),
492
+ });
493
+ },
494
+ };
495
+
496
+ async function otelSpan(
497
+ label: string,
498
+ attributes: Record<string, string>,
499
+ cb: () => Promise<InstrumentationHandlerResult>,
500
+ ) {
501
+ return tracer.startActiveSpan(
502
+ label,
503
+ { attributes },
504
+ async (span) => {
505
+ let { error } = await cb();
506
+ if (error) {
507
+ span.recordException(error);
508
+ span.setStatus({
509
+ code: SpanStatusCode.ERROR,
510
+ });
511
+ }
512
+ span.end();
513
+ },
514
+ );
515
+ }
516
+
517
+ export const instrumentations = [otel];
518
+ ```
519
+
520
+ ### Client-side Performance Tracking
521
+
522
+ ```tsx
523
+ const windowPerf: ClientInstrumentation = {
524
+ router({ instrument }) {
525
+ instrument({
526
+ navigate: (fn, { to, currentUrl }) =>
527
+ measure(`navigation:${currentUrl}->${to}`, fn),
528
+ fetch: (fn, { href }) =>
529
+ measure(`fetcher:${href}`, fn),
530
+ });
531
+ },
532
+ route({ instrument, id }) {
533
+ instrument({
534
+ middleware: (fn) => measure(`middleware:${id}`, fn),
535
+ loader: (fn) => measure(`loader:${id}`, fn),
536
+ action: (fn) => measure(`action:${id}`, fn),
537
+ });
538
+ },
539
+ };
540
+
541
+ async function measure(
542
+ label: string,
543
+ cb: () => Promise<InstrumentationHandlerResult>,
544
+ ) {
545
+ performance.mark(`start:${label}`);
546
+ await cb();
547
+ performance.mark(`end:${label}`);
548
+ performance.measure(
549
+ label,
550
+ `start:${label}`,
551
+ `end:${label}`,
552
+ );
553
+ }
554
+
555
+ <HydratedRouter instrumentations={[windowPerf]} />;
556
+ ```
@@ -0,0 +1,40 @@
1
+ ---
2
+ title: Meta Tags and SEO
3
+ hidden: true
4
+ ---
5
+
6
+ [copy pasted from route module doc]
7
+
8
+ By default, meta descriptors will render a [`<meta>` tag][meta-element] in most cases. The two exceptions are:
9
+
10
+ - `{ title }` renders a `<title>` tag
11
+ - `{ "script:ld+json" }` renders a `<script type="application/ld+json">` tag, and its value should be a serializable object that is stringified and injected into the tag.
12
+
13
+ ```tsx
14
+ export function meta() {
15
+ return [
16
+ {
17
+ "script:ld+json": {
18
+ "@context": "https://schema.org",
19
+ "@type": "Organization",
20
+ name: "React Router",
21
+ url: "https://reactrouter.com",
22
+ },
23
+ },
24
+ ];
25
+ }
26
+ ```
27
+
28
+ A meta descriptor can also render a [`<link>` tag][link-element] by setting the `tagName` property to `"link"`. This is useful for `<link>` tags associated with SEO like `canonical` URLs. For asset links like stylesheets and favicons, you should use the [`links` export][links] instead.
29
+
30
+ ```tsx
31
+ export function meta() {
32
+ return [
33
+ {
34
+ tagName: "link",
35
+ rel: "canonical",
36
+ href: "https://reactrouter.com",
37
+ },
38
+ ];
39
+ }
40
+ ```