diff --git a/crates/next-core/src/next_config.rs b/crates/next-core/src/next_config.rs index e7ca235b642b25..5d576e1b4af55f 100644 --- a/crates/next-core/src/next_config.rs +++ b/crates/next-core/src/next_config.rs @@ -98,6 +98,7 @@ pub struct NextConfig { #[bincode(with = "turbo_bincode::serde_self_describing")] sass_options: Option, trailing_slash: Option, + disable_preflight_fetch: Option, asset_prefix: Option, base_path: Option, skip_proxy_url_normalize: Option, @@ -1709,6 +1710,11 @@ impl NextConfig { Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false)) } + #[turbo_tasks::function] + pub fn disable_preflight_fetch(&self) -> Vc { + Vc::cell(self.disable_preflight_fetch.unwrap_or(false)) + } + /// Returns the final asset prefix. If an assetPrefix is set, it's used. /// Otherwise, the basePath is used. #[turbo_tasks::function] diff --git a/docs/01-app/03-api-reference/05-config/01-next-config-js/disablePreflightFetch.mdx b/docs/01-app/03-api-reference/05-config/01-next-config-js/disablePreflightFetch.mdx new file mode 100644 index 00000000000000..e956c8e3fc72b9 --- /dev/null +++ b/docs/01-app/03-api-reference/05-config/01-next-config-js/disablePreflightFetch.mdx @@ -0,0 +1,16 @@ +--- +title: disablePreflightFetch +description: Disable client-side preflight fetches during prefetching. +--- + +{/* The content of this doc is shared between the app and pages router. You can use the `Content` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */} + +By default, Next.js may issue additional "preflight" requests during client-side prefetching (for example, a background `HEAD` request) to avoid downloading full payloads or to validate a route before fetching more data. + +If you need to **disable** these preflight requests, open `next.config.js` and set `disablePreflightFetch`: + +```js filename="next.config.js" +module.exports = { + disablePreflightFetch: true, +} +``` diff --git a/packages/next/src/build/define-env.ts b/packages/next/src/build/define-env.ts index 3cb0064a27fab2..d5e3f7234c376a 100644 --- a/packages/next/src/build/define-env.ts +++ b/packages/next/src/build/define-env.ts @@ -217,6 +217,8 @@ export function getDefineEnv({ 'process.env.__NEXT_DYNAMIC_ON_HOVER': Boolean( config.experimental.dynamicOnHover ), + 'process.env.__NEXT_DISABLE_PREFLIGHT_FETCH': + config.disablePreflightFetch ?? false, 'process.env.__NEXT_OPTIMISTIC_CLIENT_CACHE': config.experimental.optimisticClientCache ?? true, 'process.env.__NEXT_MIDDLEWARE_PREFETCH': diff --git a/packages/next/src/client/components/segment-cache/cache.ts b/packages/next/src/client/components/segment-cache/cache.ts index 81e183d000f4e9..2785412fcb4f9e 100644 --- a/packages/next/src/client/components/segment-cache/cache.ts +++ b/packages/next/src/client/components/segment-cache/cache.ts @@ -1350,22 +1350,28 @@ export async function fetchRouteOnCacheMiss( // NOTE: We could embed the route tree into the HTML document, to avoid // a second request. We're not doing that currently because it would make // the HTML document larger and affect normal page loads. - const headResponse = await fetch(url, { - method: 'HEAD', - }) - if (headResponse.status < 200 || headResponse.status >= 400) { - // The target page responded w/o a successful status code - // Could be a WAF serving a 403, or a 5xx from a backend - // - // Note that we can't use headResponse.ok here, because - // Response#ok returns `false` with 3xx responses. - rejectRouteCacheEntry(entry, Date.now() + 10 * 1000) - return null - } + if (!process.env.__NEXT_DISABLE_PREFLIGHT_FETCH) { + const headResponse = await fetch(url, { + method: 'HEAD', + }) + if (headResponse.status < 200 || headResponse.status >= 400) { + // The target page responded w/o a successful status code + // Could be a WAF serving a 403, or a 5xx from a backend + // + // Note that we can't use headResponse.ok here, because + // Response#ok returns `false` with 3xx responses. + rejectRouteCacheEntry(entry, Date.now() + 10 * 1000) + return null + } - urlAfterRedirects = headResponse.redirected - ? new URL(headResponse.url) - : url + urlAfterRedirects = headResponse.redirected + ? new URL(headResponse.url) + : url + } else { + // When preflight fetches are disabled, skip the extra request and + // proceed with the original URL. + urlAfterRedirects = url + } response = await fetchPrefetchResponse( addSegmentPathToUrlInOutputExportMode(urlAfterRedirects, segmentPath), diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts index 9cd8dafdad61f4..ef00a8c7a48e6a 100644 --- a/packages/next/src/server/config-schema.ts +++ b/packages/next/src/server/config-schema.ts @@ -694,6 +694,7 @@ export const configSchema: zod.ZodType = z.lazy(() => expireTime: z.number().optional(), target: z.string().optional(), trailingSlash: z.boolean().optional(), + disablePreflightFetch: z.boolean().optional(), transpilePackages: z.array(z.string()).optional(), turbopack: zTurbopackConfig.optional(), typescript: z diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts index 7947c8023c304e..0d7bfb684c4f36 100644 --- a/packages/next/src/server/config-shared.ts +++ b/packages/next/src/server/config-shared.ts @@ -1019,6 +1019,15 @@ export interface NextConfig { */ trailingSlash?: boolean + /** + * Disables the client-side "preflight" fetches that Next.js performs during + * prefetching (for example, background `HEAD` requests). When `true`, Next.js + * will skip issuing these preflight requests. + * + * @default false + */ + disablePreflightFetch?: boolean + /** * Next.js comes with built-in support for environment variables * @@ -1433,6 +1442,7 @@ export const defaultConfig = Object.freeze({ basePath: '', sassOptions: {}, trailingSlash: false, + disablePreflightFetch: false, i18n: null, productionBrowserSourceMaps: false, excludeDefaultMomentLocales: true, diff --git a/packages/next/src/shared/lib/router/router.ts b/packages/next/src/shared/lib/router/router.ts index 233f8b31f73402..3930fdc50e4e69 100644 --- a/packages/next/src/shared/lib/router/router.ts +++ b/packages/next/src/shared/lib/router/router.ts @@ -609,8 +609,12 @@ function fetchNextData({ if (inflightCache[cacheKey] !== undefined) { return inflightCache[cacheKey] } + // Background prefetches use `HEAD` by default to avoid downloading the full + // payload. This can be disabled via next.config.js (`disablePreflightFetch`). + const shouldUseHead = + isBackground && !process.env.__NEXT_DISABLE_PREFLIGHT_FETCH return (inflightCache[cacheKey] = getData( - isBackground ? { method: 'HEAD' } : {} + shouldUseHead ? { method: 'HEAD' } : {} )) }