mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
docs: clarify instrumentation-client polyfill loading and hydration timing (#95988)
The `instrumentation-client` Polyfills docs recommended a conditional dynamic `import().then()` to load a polyfill. That import is fire-and-forget: Next.js loads this file synchronously and does not await it before hydration, so the polyfill can install after components have already run (see #83905). - In "Execution timing" mention that only synchronous top-level code completes before hydration. Call out that a conditional `import()` here is not awaited, and cross-reference `supported-browsers` for load-on-demand strategies and the built-in baseline. - In **`supported-browsers.mdx`**, "fix" the `Conditionally loading polyfills` example to `await import()` instead of a floating `.then()`, otherwise, a reader needs to put their code within the `.then` chain of the polyfill load. Closes: https://linear.app/vercel/issue/DOC-6506/revisit-polyfills-with-instrumentation-client
This commit is contained in:
@@ -124,6 +124,8 @@ The `instrumentation-client.js` file executes at a specific point in the applica
|
||||
|
||||
This timing makes it ideal for setting up error tracking, analytics, and performance monitoring that needs to capture early application lifecycle events.
|
||||
|
||||
Only synchronous, top-level code is guaranteed to complete before hydration. Asynchronous work started here (a `Promise`, `import()`, or top-level `await`) is not awaited and may resolve after hydration has begun, so treat it as fire-and-forget. When something must be in place before your components run, use one of the synchronous patterns in [Polyfills](#polyfills).
|
||||
|
||||
## See also
|
||||
|
||||
`next.config.js` plugins (for example, wrappers like `withSentry`) can register their own client instrumentation module via the [`instrumentationClientInject`](/docs/app/api-reference/config/next-config-js/instrumentationClientInject) option. Injected modules run before this file, in array order, and may export the same router transition start hook. Application code should continue to use this file convention directly.
|
||||
@@ -236,11 +238,23 @@ export function onRouterTransitionStart(url) {
|
||||
|
||||
### Polyfills
|
||||
|
||||
Load polyfills before application code runs. Use static imports for immediate loading and dynamic imports for conditional loading based on feature detection.
|
||||
As noted in [Execution timing](#execution-timing), only synchronous, top-level code here runs before hydration, so a conditional `import()` or top-level `await` may resolve after hydration has already started.
|
||||
|
||||
```ts filename="instrumentation-client.ts" switcher
|
||||
import './lib/polyfills'
|
||||
To guarantee a polyfill is applied before your components run, statically import it and apply it synchronously after feature detection. Because the import is static, the polyfill ships to every visitor:
|
||||
|
||||
```ts filename="instrumentation-client.ts"
|
||||
import ResizeObserverPolyfill from './lib/polyfills/resize-observer'
|
||||
|
||||
if (!window.ResizeObserver) {
|
||||
window.ResizeObserver = ResizeObserverPolyfill
|
||||
}
|
||||
```
|
||||
|
||||
Avoid loading a polyfill with a conditional `import()` here. The import is fire-and-forget, so the polyfill may be applied after hydration has already begun, which can be too late for your components:
|
||||
|
||||
```ts filename="instrumentation-client.ts"
|
||||
// Avoid: the dynamic import is fire-and-forget, so `ResizeObserver`
|
||||
// may still be undefined when your components run.
|
||||
if (!window.ResizeObserver) {
|
||||
import('./lib/polyfills/resize-observer').then((mod) => {
|
||||
window.ResizeObserver = mod.default
|
||||
@@ -248,15 +262,9 @@ if (!window.ResizeObserver) {
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="instrumentation-client.js" switcher
|
||||
import './lib/polyfills'
|
||||
For anything loaded on demand, prefer polyfilling in the code that uses the feature. See [Custom Polyfills](/docs/architecture/supported-browsers#custom-polyfills) for that and other strategies.
|
||||
|
||||
if (!window.ResizeObserver) {
|
||||
import('./lib/polyfills/resize-observer').then((mod) => {
|
||||
window.ResizeObserver = mod.default
|
||||
})
|
||||
}
|
||||
```
|
||||
Next.js already injects a baseline of [widely used polyfills](/docs/architecture/supported-browsers#polyfills) (such as `fetch`, `URL`, and `Object.assign`) for the browsers that need them, so you only need to add polyfills for features outside that baseline.
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -76,9 +76,8 @@ import { useCallback } from 'react'
|
||||
export const useAnalytics = () => {
|
||||
const tracker = useCallback(async (data: unknown) => {
|
||||
if (!('structuredClone' in globalThis)) {
|
||||
import('polyfills/structured-clone').then((mod) => {
|
||||
const mod = await import('polyfills/structured-clone')
|
||||
globalThis.structuredClone = mod.default
|
||||
})
|
||||
}
|
||||
|
||||
/* Do some work that uses structured clone */
|
||||
@@ -94,9 +93,8 @@ import { useCallback } from 'react'
|
||||
export const useAnalytics = () => {
|
||||
const tracker = useCallback(async (data) => {
|
||||
if (!('structuredClone' in globalThis)) {
|
||||
import('polyfills/structured-clone').then((mod) => {
|
||||
const mod = await import('polyfills/structured-clone')
|
||||
globalThis.structuredClone = mod.default
|
||||
})
|
||||
}
|
||||
|
||||
/* Do some work that uses structured clone */
|
||||
|
||||
Reference in New Issue
Block a user