diff --git a/docs/01-app/03-api-reference/05-config/01-next-config-js/prefetchInlining.mdx b/docs/01-app/03-api-reference/05-config/01-next-config-js/prefetchInlining.mdx new file mode 100644 index 000000000000..5ab46a6cfe1d --- /dev/null +++ b/docs/01-app/03-api-reference/05-config/01-next-config-js/prefetchInlining.mdx @@ -0,0 +1,99 @@ +--- +title: prefetchInlining +description: Override how the App Router bundles small prefetch responses together. +version: experimental +related: + title: Related + description: View related API references and guides. + links: + - app/api-reference/components/link + - app/guides/prefetching +--- + +When the App Router prefetches a route, it can bundle small segment responses into a single response instead of requesting each one separately. This reduces the number of prefetch requests at the cost of duplicating some shared segment data across routes. This behavior is on by default, and most apps should leave it that way. + +The `experimental.prefetchInlining` option lets you override this behavior or disable inlining while debugging navigation issues or measuring request volume. For most applications, there is no need to change the default behavior. + +> **Good to know**: The inlining behavior is a permanent part of the App Router. Only the `experimental.prefetchInlining` configuration is experimental, so its options may still change. + +## Usage + +To turn off prefetch inlining, set `experimental.prefetchInlining` to `false`: + +```ts filename="next.config.ts" switcher +import type { NextConfig } from 'next' + +const nextConfig: NextConfig = { + experimental: { + prefetchInlining: false, + }, +} + +export default nextConfig +``` + +```js filename="next.config.js" switcher +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + prefetchInlining: false, + }, +} + +module.exports = nextConfig +``` + +To override the thresholds instead of disabling inlining, pass an object. Any value you omit keeps its default: + +```ts filename="next.config.ts" switcher +import type { NextConfig } from 'next' + +const nextConfig: NextConfig = { + experimental: { + prefetchInlining: { + maxSize: 2048, + maxBundleSize: 10240, + }, + }, +} + +export default nextConfig +``` + +```js filename="next.config.js" switcher +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + prefetchInlining: { + maxSize: 2048, + maxBundleSize: 10240, + }, + }, +} + +module.exports = nextConfig +``` + +## Reference + +| Value | Description | +| -------- | ---------------------------------------------------------------------------- | +| `true` | Inlines prefetch responses with the default thresholds. This is the default. | +| `false` | Disables prefetch inlining. Each segment is prefetched as its own request. | +| `object` | Inlines prefetch responses using the `maxSize` or `maxBundleSize` you set. | + +When you pass an object, the following options control the thresholds. Both are measured in bytes of the gzip-compressed segment response: + +| Option | Type | Default | Description | +| --------------- | -------- | ------- | --------------------------------------------------------------------------------------- | +| `maxSize` | `number` | `2048` | Largest a single segment response can be to still be eligible for inlining. | +| `maxBundleSize` | `number` | `10240` | Largest total size that can be inlined into one bundled prefetch response along a path. | + +Lower thresholds keep more per-segment deduplication; higher thresholds inline more data and cut request count further. + +## Version History + +| Version | Change | +| ------- | --------------------------------------------------- | +| 16.3.0 | `experimental.prefetchInlining` enabled by default. | +| 16.2.0 | `experimental.prefetchInlining` added. | diff --git a/packages/create-next-app/helpers/generate-agent-files.ts b/packages/create-next-app/helpers/generate-agent-files.ts index 5da1eabb090b..1dfc8c68f330 100644 --- a/packages/create-next-app/helpers/generate-agent-files.ts +++ b/packages/create-next-app/helpers/generate-agent-files.ts @@ -10,6 +10,8 @@ export function generateAgentFiles(root: string): void { # This is NOT the Next.js you know This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in \`node_modules/next/dist/docs/\` before writing any code. Heed deprecation notices. + +**Keep this block, including in commits.** It is part of the project's agent setup, maintained by \`next dev\` for every agent that works here. If it appears as an uncommitted change, that is intentional — commit it as-is. Do not remove it to clean up a diff; it will be regenerated. ` diff --git a/packages/next-codemod/bin/agents-md.ts b/packages/next-codemod/bin/agents-md.ts index 9c55cea2e3ad..6ec55eef2e44 100644 --- a/packages/next-codemod/bin/agents-md.ts +++ b/packages/next-codemod/bin/agents-md.ts @@ -10,6 +10,8 @@ import pc from 'picocolors' import { BadInput } from './shared' import { getNextjsVersion, + getBundledDocsInfo, + getBundledDocsLinkPath, pullDocs, collectDocFiles, buildDocTree, @@ -74,8 +76,20 @@ export async function runAgentsMd(options: AgentsMdOptions): Promise { } const claudeMdPath = path.join(cwd, targetFile) - const docsPath = path.join(cwd, DOCS_DIR_NAME) - const docsLinkPath = `./${DOCS_DIR_NAME}` + + // Next.js >= 16.2.0 ships its docs inside the published package. When the + // installed version matches the requested one, index the bundled docs + // directly instead of downloading a copy into .next-docs. + const bundledDocs = getBundledDocsInfo(cwd) + const useBundledDocs = + bundledDocs !== null && bundledDocs.version === nextjsVersion + + const docsPath = useBundledDocs + ? bundledDocs.docsPath + : path.join(cwd, DOCS_DIR_NAME) + const docsLinkPath = useBundledDocs + ? getBundledDocsLinkPath(cwd, bundledDocs.docsPath) + : `./${DOCS_DIR_NAME}` let sizeBefore = 0 let isNewFile = true @@ -87,18 +101,24 @@ export async function runAgentsMd(options: AgentsMdOptions): Promise { isNewFile = false } - console.log( - `\nDownloading Next.js ${pc.cyan(nextjsVersion)} documentation to ${pc.cyan(DOCS_DIR_NAME)}...` - ) + if (useBundledDocs) { + console.log( + `\nUsing the docs bundled with Next.js ${pc.cyan(nextjsVersion)} at ${pc.cyan(docsLinkPath)} (no download needed).` + ) + } else { + console.log( + `\nDownloading Next.js ${pc.cyan(nextjsVersion)} documentation to ${pc.cyan(DOCS_DIR_NAME)}...` + ) - const pullResult = await pullDocs({ - cwd, - version: nextjsVersion, - docsDir: docsPath, - }) + const pullResult = await pullDocs({ + cwd, + version: nextjsVersion, + docsDir: docsPath, + }) - if (!pullResult.success) { - throw new BadInput(`Failed to pull docs: ${pullResult.error}`) + if (!pullResult.success) { + throw new BadInput(`Failed to pull docs: ${pullResult.error}`) + } } const docFiles = collectDocFiles(docsPath) @@ -115,7 +135,9 @@ export async function runAgentsMd(options: AgentsMdOptions): Promise { const sizeAfter = Buffer.byteLength(newContent, 'utf-8') - const gitignoreResult = ensureGitignoreEntry(cwd) + // .next-docs only exists on the download path; bundled docs live in + // node_modules, which is already ignored. + const gitignoreResult = useBundledDocs ? null : ensureGitignoreEntry(cwd) const action = isNewFile ? 'Created' : 'Updated' const sizeInfo = isNewFile @@ -123,7 +145,7 @@ export async function runAgentsMd(options: AgentsMdOptions): Promise { : `${formatSize(sizeBefore)} → ${formatSize(sizeAfter)}` console.log(`${pc.green('✓')} ${action} ${pc.bold(targetFile)} (${sizeInfo})`) - if (gitignoreResult.updated) { + if (gitignoreResult?.updated) { console.log( `${pc.green('✓')} Added ${pc.bold(DOCS_DIR_NAME)} to .gitignore` ) diff --git a/packages/next-codemod/lib/__tests__/agents-md-e2e.test.js b/packages/next-codemod/lib/__tests__/agents-md-e2e.test.js index db1e7d1d9f8c..65bb9fa1f313 100644 --- a/packages/next-codemod/lib/__tests__/agents-md-e2e.test.js +++ b/packages/next-codemod/lib/__tests__/agents-md-e2e.test.js @@ -293,6 +293,123 @@ This is my project documentation. } }, 30000) // Increase timeout for git clone + describe('bundled docs (Next.js >= 16.2.0)', () => { + // Simulate an install of a Next.js version that ships docs inside the + // published package at node_modules/next/dist/docs. + function setupBundledNext(projectDir, version) { + const nextDir = path.join(projectDir, 'node_modules', 'next') + const gettingStartedDir = path.join( + nextDir, + 'dist', + 'docs', + '01-app', + '01-getting-started' + ) + fs.mkdirSync(gettingStartedDir, { recursive: true }) + fs.writeFileSync( + path.join(nextDir, 'package.json'), + JSON.stringify({ name: 'next', version }) + ) + fs.writeFileSync( + path.join(nextDir, 'dist', 'docs', 'index.md'), + '# Next.js Docs' + ) + fs.writeFileSync( + path.join(gettingStartedDir, '01-installation.md'), + '# Installation' + ) + fs.writeFileSync( + path.join(gettingStartedDir, '02-project-structure.md'), + '# Project Structure' + ) + } + + it('indexes bundled docs instead of downloading when installed Next.js ships them', async () => { + setupBundledNext(testProjectDir, '16.2.0') + + const originalCwd = process.cwd() + process.chdir(testProjectDir) + + try { + await runAgentsMd({ output: 'CLAUDE.md' }) + + // No .next-docs copy and no .gitignore entry for it + expect(fs.existsSync(path.join(testProjectDir, '.next-docs'))).toBe( + false + ) + expect(fs.existsSync(path.join(testProjectDir, '.gitignore'))).toBe( + false + ) + + const claudeMdContent = fs.readFileSync( + path.join(testProjectDir, 'CLAUDE.md'), + 'utf-8' + ) + expect(claudeMdContent).toContain( + 'root: ./node_modules/next/dist/docs' + ) + expect(claudeMdContent).toContain('01-installation.md') + + const output = consoleOutput.join('\n') + expect(output).toContain('bundled with Next.js') + expect(output).not.toContain('Downloading') + } finally { + process.chdir(originalCwd) + } + }) + + it('uses bundled docs when --version matches the installed version', async () => { + setupBundledNext(testProjectDir, '16.2.0') + + const originalCwd = process.cwd() + process.chdir(testProjectDir) + + try { + await runAgentsMd({ version: '16.2.0', output: 'AGENTS.md' }) + + expect(fs.existsSync(path.join(testProjectDir, '.next-docs'))).toBe( + false + ) + + const agentsMdContent = fs.readFileSync( + path.join(testProjectDir, 'AGENTS.md'), + 'utf-8' + ) + expect(agentsMdContent).toContain( + 'root: ./node_modules/next/dist/docs' + ) + } finally { + process.chdir(originalCwd) + } + }) + + it('falls back to downloading when --version differs from the installed version', async () => { + setupBundledNext(testProjectDir, '16.2.0') + + const originalCwd = process.cwd() + process.chdir(testProjectDir) + + try { + await runAgentsMd({ version: '15.0.0', output: 'CLAUDE.md' }) + + expect(fs.existsSync(path.join(testProjectDir, '.next-docs'))).toBe( + true + ) + + const claudeMdContent = fs.readFileSync( + path.join(testProjectDir, 'CLAUDE.md'), + 'utf-8' + ) + expect(claudeMdContent).toContain('root: ./.next-docs') + + const output = consoleOutput.join('\n') + expect(output).toContain('Downloading') + } finally { + process.chdir(originalCwd) + } + }, 30000) // Increase timeout for git clone + }) + describe('getNextjsVersion', () => { const fixturesDir = path.join(__dirname, 'fixtures/agents-md') diff --git a/packages/next-codemod/lib/agents-md.ts b/packages/next-codemod/lib/agents-md.ts index 8f37567f8703..1e816a0284fa 100644 --- a/packages/next-codemod/lib/agents-md.ts +++ b/packages/next-codemod/lib/agents-md.ts @@ -43,6 +43,42 @@ export function getNextjsVersion(cwd: string): NextjsVersionResult { } } +interface BundledDocsInfo { + docsPath: string + version: string +} + +/** + * Next.js ships its documentation inside the published package (at + * `dist/docs`) since 16.2.0. When the install resolved from `cwd` has + * bundled docs, the index can point at them directly instead of + * downloading a copy into `.next-docs`. + */ +export function getBundledDocsInfo(cwd: string): BundledDocsInfo | null { + try { + const nextPkgPath = require.resolve('next/package.json', { paths: [cwd] }) + const pkg = JSON.parse(fs.readFileSync(nextPkgPath, 'utf-8')) + const docsPath = path.join(path.dirname(nextPkgPath), 'dist', 'docs') + if (!pkg.version || collectDocFiles(docsPath).length === 0) { + return null + } + return { docsPath, version: pkg.version } + } catch { + return null + } +} + +export function getBundledDocsLinkPath(cwd: string, docsPath: string): string { + // Prefer the conventional path when it resolves from the project + // (covers hoisted installs; pnpm exposes next via a node_modules symlink). + const conventional = path.join(cwd, 'node_modules', 'next', 'dist', 'docs') + if (fs.existsSync(conventional)) { + return './node_modules/next/dist/docs' + } + const relative = path.relative(cwd, docsPath).replace(/\\/g, '/') + return relative.startsWith('.') ? relative : `./${relative}` +} + function versionToGitHubTag(version: string): string { return version.startsWith('v') ? version : `v${version}` } diff --git a/packages/next/src/build/duration-to-string.test.ts b/packages/next/src/build/duration-to-string.test.ts new file mode 100644 index 000000000000..6cd9f4822a01 --- /dev/null +++ b/packages/next/src/build/duration-to-string.test.ts @@ -0,0 +1,66 @@ +import { + durationToString, + hrtimeBigIntDurationToString, + hrtimeDurationToString, + hrtimeToSeconds, +} from './duration-to-string' + +describe('durationToString', () => { + it.each([ + [0, '0ms'], + [0.5, '500ms'], + [2, '2000ms'], + [2.5, '2.5s'], + [40, '40.0s'], + [45.4, '45s'], + [120, '120s'], + [150, '2.5min'], + ])('formats %s seconds as %s', (duration, expected) => { + expect(durationToString(duration)).toBe(expected) + }) +}) + +describe('hrtimeBigIntDurationToString', () => { + it.each([ + [BigInt(0), '0.0ms'], + [BigInt(500_000), '0.5ms'], + [BigInt(1_500_000), '1.5ms'], + [BigInt(2_000_000), '2ms'], + [BigInt(1_500_000_000), '1500ms'], + [BigInt(2_000_000_000), '2.0s'], + [BigInt(2_500_000_000), '2.5s'], + [BigInt(40_000_000_000), '40s'], + [BigInt(45_400_000_000), '45s'], + [BigInt(120_000_000_000), '2.0min'], + [BigInt(150_000_000_000), '2.5min'], + ])('formats %s nanoseconds as %s', (duration, expected) => { + expect(hrtimeBigIntDurationToString(duration)).toBe(expected) + }) +}) + +describe('hrtimeToSeconds', () => { + it.each([ + [[0, 0], 0], + [[1, 500_000_000], 1.5], + [[2, 1], 2.000000001], + ] as Array<[[number, number], number]>)( + 'converts %j to %s seconds', + (hrtime, expected) => { + expect(hrtimeToSeconds(hrtime)).toBe(expected) + } + ) +}) + +describe('hrtimeDurationToString', () => { + it.each([ + [[0, 500_000_000], '500ms'], + [[2, 500_000_000], '2.5s'], + [[45, 400_000_000], '45s'], + [[150, 0], '2.5min'], + ] as Array<[[number, number], string]>)( + 'formats %j as %s', + (hrtime, expected) => { + expect(hrtimeDurationToString(hrtime)).toBe(expected) + } + ) +}) diff --git a/packages/next/src/build/duration-to-string.ts b/packages/next/src/build/duration-to-string.ts index d93267289772..a16de48cfc1a 100644 --- a/packages/next/src/build/duration-to-string.ts +++ b/packages/next/src/build/duration-to-string.ts @@ -8,7 +8,6 @@ const MILLISECONDS_PER_SECOND = 1000 // Time thresholds and conversion factors for nanoseconds const NANOSECONDS_PER_SECOND = 1_000_000_000 const NANOSECONDS_PER_MILLISECOND = 1_000_000 -const NANOSECONDS_PER_MICROSECOND = 1_000 const NANOSECONDS_IN_MINUTE = 60_000_000_000 // 60 * 1_000_000_000 const MINUTES_THRESHOLD_NANOSECONDS = 120_000_000_000 // 2 minutes in nanoseconds const SECONDS_THRESHOLD_HIGH_NANOSECONDS = 40_000_000_000 // 40 seconds in nanoseconds @@ -46,7 +45,7 @@ export function durationToString(compilerDuration: number) { * - >= 40 seconds: show in whole seconds (e.g., "45s") * - >= 2 seconds: show in seconds with 1 decimal place (e.g., "3.2s") * - >= 2 milliseconds: show in whole milliseconds (e.g., "250ms") - * - < 2 milliseconds: show in whole microseconds (e.g., "500µs") + * - < 2 milliseconds: show in milliseconds with 1 decimal place (e.g., "0.5ms") * * @param durationBigInt - Duration in nanoseconds as a BigInt * @returns Formatted duration string with appropriate unit and precision @@ -62,7 +61,7 @@ function durationToStringWithNanoseconds(durationBigInt: bigint): string { } else if (duration >= MILLISECONDS_THRESHOLD_NANOSECONDS) { return `${(duration / NANOSECONDS_PER_MILLISECOND).toFixed(0)}ms` } else { - return `${(duration / NANOSECONDS_PER_MICROSECOND).toFixed(0)}µs` + return `${(duration / NANOSECONDS_PER_MILLISECOND).toFixed(1)}ms` } } diff --git a/packages/next/src/client/app-dir/link.tsx b/packages/next/src/client/app-dir/link.tsx index 5a624dd81e35..d0b25e1965ed 100644 --- a/packages/next/src/client/app-dir/link.tsx +++ b/packages/next/src/client/app-dir/link.tsx @@ -7,7 +7,6 @@ import { AppRouterContext } from '../../shared/lib/app-router-context.shared-run import { useMergedRef } from '../use-merged-ref' import { isAbsoluteUrl } from '../../shared/lib/utils' import { addBasePath } from '../add-base-path' -import { warnOnce } from '../../shared/lib/utils/warn-once' import { ScrollBehavior } from '../components/router-reducer/router-reducer-types' import type { PENDING_LINK_STATUS } from '../components/links' import { @@ -513,6 +512,8 @@ export default function LinkComponent( const formattedHref = formatStringOrUrl(resolvedHref) if (process.env.NODE_ENV !== 'production') { + const { warnOnce } = + require('../../shared/lib/utils/warn-once') as typeof import('../../shared/lib/utils/warn-once') if (props.locale) { warnOnce( 'The `locale` prop is not supported in `next/link` while using the `app` router. Read more about app router internalization: https://nextjs.org/docs/app/building-your-application/routing/internationalization' diff --git a/packages/next/src/client/components/http-access-fallback/error-boundary.tsx b/packages/next/src/client/components/http-access-fallback/error-boundary.tsx index 2012422b0521..22da33622b4e 100644 --- a/packages/next/src/client/components/http-access-fallback/error-boundary.tsx +++ b/packages/next/src/client/components/http-access-fallback/error-boundary.tsx @@ -19,7 +19,6 @@ import { getAccessFallbackErrorTypeByStatus, isHTTPAccessFallbackError, } from './http-access-fallback' -import { warnOnce } from '../../../shared/lib/utils/warn-once' import { MissingSlotContext } from '../../../shared/lib/app-router-context.shared-runtime' interface HTTPAccessFallbackBoundaryProps { @@ -62,6 +61,8 @@ class HTTPAccessFallbackErrorBoundary extends React.Component< // A missing children slot is the typical not-found case, so no need to warn !this.props.missingSlots.has('children') ) { + const { warnOnce } = + require('../../../shared/lib/utils/warn-once') as typeof import('../../../shared/lib/utils/warn-once') let warningMessage = 'No default component was found for a parallel route rendered on this page. Falling back to nearest NotFound boundary.\n' + 'Learn more: https://nextjs.org/docs/app/building-your-application/routing/parallel-routes#defaultjs\n\n' diff --git a/packages/next/src/client/image-component.tsx b/packages/next/src/client/image-component.tsx index 7bace3ab334d..c76554a18d1b 100644 --- a/packages/next/src/client/image-component.tsx +++ b/packages/next/src/client/image-component.tsx @@ -26,7 +26,6 @@ import type { } from '../shared/lib/image-config' import { imageConfigDefault } from '../shared/lib/image-config' import { ImageConfigContext } from '../shared/lib/image-config-context.shared-runtime' -import { warnOnce } from '../shared/lib/utils/warn-once' import { RouterContext } from '../shared/lib/router-context.shared-runtime' // This is replaced by webpack alias @@ -116,6 +115,8 @@ function handleLoading( onLoadingCompleteRef.current(img) } if (process.env.NODE_ENV !== 'production') { + const { warnOnce } = + require('../shared/lib/utils/warn-once') as typeof import('../shared/lib/utils/warn-once') const origSrc = new URL(src, 'http://n').searchParams.get('url') || src if (img.getAttribute('data-nimg') === 'fill') { if (!unoptimized && (!sizesInput || sizesInput === '100vw')) { diff --git a/packages/next/src/server/lib/generate-agent-files.ts b/packages/next/src/server/lib/generate-agent-files.ts index 7e398c230d12..c3f1f70886f2 100644 --- a/packages/next/src/server/lib/generate-agent-files.ts +++ b/packages/next/src/server/lib/generate-agent-files.ts @@ -26,6 +26,8 @@ function buildAgentRulesBlock(): string { # This is NOT the Next.js you know This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in \`node_modules/next/dist/docs/\` before writing any code. Heed deprecation notices. + +**Keep this block, including in commits.** It is part of the project's agent setup, maintained by \`next dev\` for every agent that works here. If it appears as an uncommitted change, that is intentional — commit it as-is. Do not remove it to clean up a diff; it will be regenerated. ${AGENT_RULES_END_MARKER}` } diff --git a/packages/next/src/shared/lib/get-img-props.ts b/packages/next/src/shared/lib/get-img-props.ts index a14824d1d954..ee9f277da020 100644 --- a/packages/next/src/shared/lib/get-img-props.ts +++ b/packages/next/src/shared/lib/get-img-props.ts @@ -1,4 +1,3 @@ -import { warnOnce } from './utils/warn-once' import { getAssetToken, getDeploymentId } from './deployment-id' import { getImageBlurSvg } from './image-blur-svg' import { imageConfigDefault } from './image-config' @@ -462,6 +461,8 @@ export function getImgProps( const qualityInt = getInt(quality) if (process.env.NODE_ENV !== 'production') { + const { warnOnce } = + require('./utils/warn-once') as typeof import('./utils/warn-once') if (config.output === 'export' && isDefaultLoader && !unoptimized) { throw new Error( `Image Optimization using the default loader is not compatible with \`{ output: 'export' }\`. diff --git a/packages/next/src/shared/lib/head.tsx b/packages/next/src/shared/lib/head.tsx index f40fa3c6d215..0625d3a977b2 100644 --- a/packages/next/src/shared/lib/head.tsx +++ b/packages/next/src/shared/lib/head.tsx @@ -3,7 +3,6 @@ import React, { useContext, type JSX } from 'react' import Effect from './side-effect' import { HeadManagerContext } from './head-manager-context.shared-runtime' -import { warnOnce } from './utils/warn-once' export function defaultHead(): JSX.Element[] { const head = [ @@ -129,6 +128,8 @@ function reduceComponents( .map((c: React.ReactElement, i: number) => { const key = c.key || i if (process.env.NODE_ENV === 'development') { + const { warnOnce } = + require('./utils/warn-once') as typeof import('./utils/warn-once') // omit JSON-LD structured data snippets from the warning if (c.type === 'script' && c.props['type'] !== 'application/ld+json') { const srcMessage = c.props['src'] diff --git a/packages/next/src/shared/lib/router/utils/disable-smooth-scroll.ts b/packages/next/src/shared/lib/router/utils/disable-smooth-scroll.ts index 0fd4d7ffbb6c..abf3efbb8e89 100644 --- a/packages/next/src/shared/lib/router/utils/disable-smooth-scroll.ts +++ b/packages/next/src/shared/lib/router/utils/disable-smooth-scroll.ts @@ -1,5 +1,3 @@ -import { warnOnce } from '../../utils/warn-once' - /** * Run function with `scroll-behavior: auto` applied to ``. * This css change will be reverted after the function finishes. @@ -24,6 +22,8 @@ export function disableSmoothScrollDuringRouteTransition( process.env.NODE_ENV === 'development' && getComputedStyle(htmlElement).scrollBehavior === 'smooth' ) { + const { warnOnce } = + require('../../utils/warn-once') as typeof import('../../utils/warn-once') warnOnce( 'Detected `scroll-behavior: smooth` on the `` element. To disable smooth scrolling during route transitions, ' + 'add `data-scroll-behavior="smooth"` to your element. ' +