Skip to content

Commit 6cae6d8

Browse files
authored
docs: polyfills usage in app router (#80447)
Closes: https://linear.app/vercel/issue/DOC-4752/docs-polyfills-in-app-router Fixes: #74730
1 parent 0a42be9 commit 6cae6d8

File tree

2 files changed

+230
-2
lines changed

2 files changed

+230
-2
lines changed

docs/01-app/03-api-reference/03-file-conventions/instrumentation-client.mdx

Lines changed: 180 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: instrumentation-client.js
33
description: Learn how to add client-side instrumentation to track and monitor your Next.js application's frontend performance.
44
---
55

6-
The `instrumentation-client.js|ts` file allows you to add monitoring and analytics code that runs before your application's frontend code starts executing. This is useful for setting up performance tracking, error monitoring, or any other client-side observability tools.
6+
The `instrumentation-client.js|ts` file allows you to add monitoring, analytics code, and other side-effects that run before your application becomes interactive. This is useful for setting up performance tracking, error monitoring, polyfills, or any other client-side observability tools.
77

88
To use it, place the file in the **root** of your application or inside a `src` folder.
99

@@ -39,7 +39,185 @@ window.addEventListener('error', (event) => {
3939
})
4040
```
4141

42-
## Version History
42+
**Error handling:** Implement try-catch blocks around your instrumentation code to ensure robust monitoring. This prevents individual tracking failures from affecting other instrumentation features.
43+
44+
## Router navigation tracking
45+
46+
You can export an `onRouterTransitionStart` function to receive notifications when navigation begins:
47+
48+
```ts filename="instrumentation-client.ts" switcher
49+
performance.mark('app-init')
50+
51+
export function onRouterTransitionStart(
52+
url: string,
53+
navigationType: 'push' | 'replace' | 'traverse'
54+
) {
55+
console.log(`Navigation started: ${navigationType} to ${url}`)
56+
performance.mark(`nav-start-${Date.now()}`)
57+
}
58+
```
59+
60+
```js filename="instrumentation-client.js" switcher
61+
performance.mark('app-init')
62+
63+
export function onRouterTransitionStart(url, navigationType) {
64+
console.log(`Navigation started: ${navigationType} to ${url}`)
65+
performance.mark(`nav-start-${Date.now()}`)
66+
}
67+
```
68+
69+
The `onRouterTransitionStart` function receives two parameters:
70+
71+
- `url: string` - The URL being navigated to
72+
- `navigationType: 'push' | 'replace' | 'traverse'` - The type of navigation
73+
74+
## Performance considerations
75+
76+
Keep instrumentation code lightweight.
77+
78+
Next.js monitors initialization time in development and will log warnings if it takes longer than 16ms, which could impact smooth page loading.
79+
80+
## Execution timing
81+
82+
The `instrumentation-client.js` file executes at a specific point in the application lifecycle:
83+
84+
1. **After** the HTML document is loaded
85+
2. **Before** React hydration begins
86+
3. **Before** user interactions are possible
87+
88+
This timing makes it ideal for setting up error tracking, analytics, and performance monitoring that needs to capture early application lifecycle events.
89+
90+
## Examples
91+
92+
### Error tracking
93+
94+
Initialize error tracking before React starts and add navigation breadcrumbs for better debugging context.
95+
96+
```ts filename="instrumentation-client.ts" switcher
97+
import Monitor from './lib/monitoring'
98+
99+
Monitor.initialize()
100+
101+
export function onRouterTransitionStart(url: string) {
102+
Monitor.pushEvent({
103+
message: `Navigation to ${url}`,
104+
category: 'navigation',
105+
})
106+
}
107+
```
108+
109+
```js filename="instrumentation-client.js" switcher
110+
import Monitor from './lib/monitoring'
111+
112+
Monitor.initialize()
113+
114+
export function onRouterTransitionStart(url) {
115+
Monitor.pushEvent({
116+
message: `Navigation to ${url}`,
117+
category: 'navigation',
118+
})
119+
}
120+
```
121+
122+
### Analytics tracking
123+
124+
Initialize analytics and track navigation events with detailed metadata for user behavior analysis.
125+
126+
```ts filename="instrumentation-client.ts" switcher
127+
import { analytics } from './lib/analytics'
128+
129+
analytics.init()
130+
131+
export function onRouterTransitionStart(url: string, navigationType: string) {
132+
analytics.track('page_navigation', {
133+
url,
134+
type: navigationType,
135+
timestamp: Date.now(),
136+
})
137+
}
138+
```
139+
140+
```js filename="instrumentation-client.js" switcher
141+
import { analytics } from './lib/analytics'
142+
143+
analytics.init()
144+
145+
export function onRouterTransitionStart(url, navigationType) {
146+
analytics.track('page_navigation', {
147+
url,
148+
type: navigationType,
149+
timestamp: Date.now(),
150+
})
151+
}
152+
```
153+
154+
### Performance monitoring
155+
156+
Track Time to Interactive and navigation performance using the Performance Observer API and performance marks.
157+
158+
```ts filename="instrumentation-client.ts" switcher
159+
const startTime = performance.now()
160+
161+
const observer = new PerformanceObserver(
162+
(list: PerformanceObserverEntryList) => {
163+
for (const entry of list.getEntries()) {
164+
if (entry instanceof PerformanceNavigationTiming) {
165+
console.log('Time to Interactive:', entry.loadEventEnd - startTime)
166+
}
167+
}
168+
}
169+
)
170+
171+
observer.observe({ entryTypes: ['navigation'] })
172+
173+
export function onRouterTransitionStart(url: string) {
174+
performance.mark(`nav-start-${url}`)
175+
}
176+
```
177+
178+
```js filename="instrumentation-client.js" switcher
179+
const startTime = performance.now()
180+
181+
const observer = new PerformanceObserver((list) => {
182+
for (const entry of list.getEntries()) {
183+
if (entry instanceof PerformanceNavigationTiming) {
184+
console.log('Time to Interactive:', entry.loadEventEnd - startTime)
185+
}
186+
}
187+
})
188+
189+
observer.observe({ entryTypes: ['navigation'] })
190+
191+
export function onRouterTransitionStart(url) {
192+
performance.mark(`nav-start-${url}`)
193+
}
194+
```
195+
196+
### Polyfills
197+
198+
Load polyfills before application code runs. Use static imports for immediate loading and dynamic imports for conditional loading based on feature detection.
199+
200+
```ts filename="instrumentation-client.ts" switcher
201+
import './lib/polyfills'
202+
203+
if (!window.ResizeObserver) {
204+
import('./lib/polyfills/resize-observer').then((mod) => {
205+
window.ResizeObserver = mod.default
206+
})
207+
}
208+
```
209+
210+
```js filename="instrumentation-client.js" switcher
211+
import './lib/polyfills'
212+
213+
if (!window.ResizeObserver) {
214+
import('./lib/polyfills/resize-observer').then((mod) => {
215+
window.ResizeObserver = mod.default
216+
})
217+
}
218+
```
219+
220+
## Version history
43221

44222
| Version | Changes |
45223
| ------- | ----------------------------------- |

docs/03-architecture/supported-browsers.mdx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,57 @@ In addition, to reduce bundle size, Next.js will only load these polyfills for b
4343

4444
If your own code or any external npm dependencies require features not supported by your target browsers (such as IE 11), you need to add polyfills yourself.
4545

46+
<PagesOnly>
4647
In this case, you should add a top-level import for the **specific polyfill** you need in your [Custom `<App>`](/docs/pages/building-your-application/routing/custom-app) or the individual component.
48+
</PagesOnly>
49+
50+
<AppOnly>
51+
52+
To include polyfills, you can import them into the [`instrumentation-client.js` file](/docs/app/api-reference/file-conventions/instrumentation-client).
53+
54+
```ts filename="instrumentation-client.ts"
55+
import './polyfills'
56+
```
57+
58+
</AppOnly>
59+
60+
The best approach is to isolate unsupported features to specific UI sections and conditionally load the polyfill if needed.
61+
62+
```ts filename="hooks/analytics.ts" switcher
63+
import { useCallback } from 'react'
64+
65+
export const useAnalytics = () => {
66+
const tracker = useCallback(async (data: unknown) => {
67+
if (!('structuredClone' in globalThis)) {
68+
import('polyfills/structured-clone').then((mod) => {
69+
globalThis.structuredClone = mod.default
70+
})
71+
}
72+
73+
/* Do some work that uses structured clone */
74+
}, [])
75+
76+
return tracker
77+
}
78+
```
79+
80+
```js filename="hooks/analytics.js" switcher
81+
import { useCallback } from 'react'
82+
83+
export const useAnalytics = () => {
84+
const tracker = useCallback(async (data) => {
85+
if (!('structuredClone' in globalThis)) {
86+
import('polyfills/structured-clone').then((mod) => {
87+
globalThis.structuredClone = mod.default
88+
})
89+
}
90+
91+
/* Do some work that uses structured clone */
92+
}, [])
93+
94+
return tracker
95+
}
96+
```
4797

4898
## JavaScript Language Features
4999

0 commit comments

Comments
 (0)