From c26b4a68242317feaa13dd48d89c9f759411b3d1 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Mon, 26 Jan 2026 09:23:52 +0100 Subject: [PATCH 1/7] Rename `renderOpts.nextExport` to `isBuildTimePrerendering` (#88951) Rename to reflect what this property actually means --- packages/next/src/build/templates/app-page.ts | 8 +----- packages/next/src/export/index.ts | 2 +- packages/next/src/export/routes/app-route.ts | 2 +- .../next/src/server/app-render/app-render.tsx | 25 ++++++++++--------- .../app-render/create-error-handler.tsx | 8 +++--- packages/next/src/server/app-render/types.ts | 2 +- .../src/server/async-storage/work-store.ts | 4 +-- packages/next/src/server/render.tsx | 10 +++++--- 8 files changed, 29 insertions(+), 32 deletions(-) diff --git a/packages/next/src/build/templates/app-page.ts b/packages/next/src/build/templates/app-page.ts index f03844d47ff3d..8ad6b032e80c7 100644 --- a/packages/next/src/build/templates/app-page.ts +++ b/packages/next/src/build/templates/app-page.ts @@ -686,7 +686,7 @@ export async function handler( isDebugDynamicAccesses || isDebugFallbackShell ? { - nextExport: true, + isBuildTimePrerendering: true, supportsDynamicResponse: false, isStaticGeneration: true, isDebugDynamicAccesses: isDebugDynamicAccesses, @@ -736,12 +736,6 @@ export async function handler( }, } - if (isDebugStaticShell || isDebugDynamicAccesses) { - context.renderOpts.nextExport = true - context.renderOpts.supportsDynamicResponse = false - context.renderOpts.isDebugDynamicAccesses = isDebugDynamicAccesses - } - // When we're revalidating in the background, we should not allow dynamic // responses. if (forceStaticRender) { diff --git a/packages/next/src/export/index.ts b/packages/next/src/export/index.ts index 50ab3db0c5363..4341befcdca2f 100644 --- a/packages/next/src/export/index.ts +++ b/packages/next/src/export/index.ts @@ -474,7 +474,7 @@ async function exportAppImpl( // Start the rendering process const renderOpts: WorkerRenderOptsPartial = { previewProps: prerenderManifest?.preview, - nextExport: true, + isBuildTimePrerendering: true, assetPrefix: nextConfig.assetPrefix.replace(/\/$/, ''), distDir, dev: false, diff --git a/packages/next/src/export/routes/app-route.ts b/packages/next/src/export/routes/app-route.ts index e9fbaf6573bb5..3f13e80bf7fe5 100644 --- a/packages/next/src/export/routes/app-route.ts +++ b/packages/next/src/export/routes/app-route.ts @@ -80,7 +80,7 @@ export async function exportAppRoute( renderOpts: { cacheComponents, experimental, - nextExport: true, + isBuildTimePrerendering: true, supportsDynamicResponse: false, incrementalCache, waitUntil: afterRunner.context.waitUntil, diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 7e4c2101e8e9a..97d24dddaaae9 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -618,7 +618,7 @@ async function generateDynamicFlightRenderResult( dev = false, onInstrumentationRequestError, setReactDebugChannel, - nextExport = false, + isBuildTimePrerendering = false, } = renderOpts function onFlightDataRenderError(err: DigestedError, silenceLog: boolean) { @@ -632,7 +632,7 @@ async function generateDynamicFlightRenderResult( const onError = createReactServerErrorHandler( dev, - nextExport, + isBuildTimePrerendering, workStore.reactServerErrorsByDigest, onFlightDataRenderError ) @@ -775,7 +775,7 @@ async function generateDynamicFlightRenderResultWithStagesInDev( onInstrumentationRequestError, setReactDebugChannel, setCacheStatus, - nextExport = false, + isBuildTimePrerendering = false, } = renderOpts function onFlightDataRenderError(err: DigestedError, silenceLog: boolean) { @@ -789,7 +789,7 @@ async function generateDynamicFlightRenderResultWithStagesInDev( const onError = createReactServerErrorHandler( dev, - nextExport, + isBuildTimePrerendering, workStore.reactServerErrorsByDigest, onFlightDataRenderError ) @@ -921,7 +921,8 @@ async function generateRuntimePrefetchResult( requestStore: RequestStore ): Promise { const { workStore, renderOpts } = ctx - const { nextExport = false, onInstrumentationRequestError } = renderOpts + const { isBuildTimePrerendering = false, onInstrumentationRequestError } = + renderOpts function onFlightDataRenderError(err: DigestedError, silenceLog: boolean) { return onInstrumentationRequestError?.( @@ -935,7 +936,7 @@ async function generateRuntimePrefetchResult( const onError = createReactServerErrorHandler( false, - nextExport, + isBuildTimePrerendering, workStore.reactServerErrorsByDigest, onFlightDataRenderError ) @@ -2513,7 +2514,7 @@ async function renderToStream( crossOrigin, dev = false, experimental, - nextExport = false, + isBuildTimePrerendering = false, onInstrumentationRequestError, page, reactMaxHeadersLength, @@ -2612,7 +2613,7 @@ async function renderToStream( } const serverComponentsErrorHandler = createReactServerErrorHandler( dev, - nextExport, + isBuildTimePrerendering, reactServerErrorsByDigest, onHTMLRenderRSCError, renderSpan @@ -2633,7 +2634,7 @@ async function renderToStream( const allCapturedErrors: Array = [] const htmlRendererErrorHandler = createHTMLErrorHandler( dev, - nextExport, + isBuildTimePrerendering, reactServerErrorsByDigest, allCapturedErrors, onHTMLRenderSSRError, @@ -4118,7 +4119,7 @@ async function prerenderToStream( dev = false, experimental, isDebugDynamicAccesses, - nextExport = false, + isBuildTimePrerendering = false, onInstrumentationRequestError, page, reactMaxHeadersLength, @@ -4181,7 +4182,7 @@ async function prerenderToStream( } const serverComponentsErrorHandler = createReactServerErrorHandler( dev, - nextExport, + isBuildTimePrerendering, reactServerErrorsByDigest, onHTMLRenderRSCError ) @@ -4202,7 +4203,7 @@ async function prerenderToStream( const allCapturedErrors: Array = [] const htmlRendererErrorHandler = createHTMLErrorHandler( dev, - nextExport, + isBuildTimePrerendering, reactServerErrorsByDigest, allCapturedErrors, onHTMLRenderSSRError diff --git a/packages/next/src/server/app-render/create-error-handler.tsx b/packages/next/src/server/app-render/create-error-handler.tsx index 57f90bfa9077e..54350deced814 100644 --- a/packages/next/src/server/app-render/create-error-handler.tsx +++ b/packages/next/src/server/app-render/create-error-handler.tsx @@ -51,7 +51,7 @@ export function getDigestForWellKnownError(error: unknown): string | undefined { export function createReactServerErrorHandler( shouldFormatError: boolean, - isNextExport: boolean, + isBuildTimePrerendering: boolean, reactServerErrors: Map, onReactServerRenderError: (err: DigestedError, silenceLog: boolean) => void, spanToRecordOn?: any @@ -121,7 +121,7 @@ export function createReactServerErrorHandler( // Don't log the suppressed error during export if ( !( - isNextExport && + isBuildTimePrerendering && err?.message?.includes( 'The specific message is omitted in production builds to avoid leaking sensitive details.' ) @@ -147,7 +147,7 @@ export function createReactServerErrorHandler( export function createHTMLErrorHandler( shouldFormatError: boolean, - isNextExport: boolean, + isBuildTimePrerendering: boolean, reactServerErrors: Map, allCapturedErrors: Array, onHTMLRenderSSRError: (err: DigestedError, errorInfo?: ErrorInfo) => void, @@ -204,7 +204,7 @@ export function createHTMLErrorHandler( // Don't log the suppressed error during export if ( !( - isNextExport && + isBuildTimePrerendering && err?.message?.includes( 'The specific message is omitted in production builds to avoid leaking sensitive details.' ) diff --git a/packages/next/src/server/app-render/types.ts b/packages/next/src/server/app-render/types.ts index 013f655cdde04..29f47b977fbea 100644 --- a/packages/next/src/server/app-render/types.ts +++ b/packages/next/src/server/app-render/types.ts @@ -122,7 +122,7 @@ export interface RenderOptsPartial { errorsRscStream: ReadableStream, htmlRequestId: string ) => void - nextExport?: boolean + isBuildTimePrerendering?: boolean nextConfigOutput?: 'standalone' | 'export' onInstrumentationRequestError?: ServerOnInstrumentationRequestError isDraftMode?: boolean diff --git a/packages/next/src/server/async-storage/work-store.ts b/packages/next/src/server/async-storage/work-store.ts index 11b27fbea29b2..2ac6c70463a39 100644 --- a/packages/next/src/server/async-storage/work-store.ts +++ b/packages/next/src/server/async-storage/work-store.ts @@ -55,7 +55,7 @@ export type WorkStoreContext = { | 'assetPrefix' | 'supportsDynamicResponse' | 'shouldWaitOnAllReady' - | 'nextExport' + | 'isBuildTimePrerendering' | 'isDraftMode' | 'isDebugDynamicAccesses' | 'dev' @@ -125,7 +125,7 @@ export function createWorkStore({ // so that it can access the fs cache without mocks renderOpts.incrementalCache || (globalThis as any).__incrementalCache, cacheLifeProfiles: renderOpts.cacheLifeProfiles, - isBuildTimePrerendering: renderOpts.nextExport, + isBuildTimePrerendering: renderOpts.isBuildTimePrerendering, hasReadableErrorStacks: renderOpts.hasReadableErrorStacks, fetchCache: renderOpts.fetchCache, isOnDemandRevalidate: renderOpts.isOnDemandRevalidate, diff --git a/packages/next/src/server/render.tsx b/packages/next/src/server/render.tsx index 048beb9439f91..300b3733cc2c1 100644 --- a/packages/next/src/server/render.tsx +++ b/packages/next/src/server/render.tsx @@ -243,7 +243,7 @@ function renderPageTree( export type RenderOptsPartial = { assetPrefix?: string err?: Error | null - nextExport?: boolean + isBuildTimePrerendering?: boolean dev?: boolean ErrorDebug?: PagesDevOverlayBridgeType isNextDataRequest?: boolean @@ -517,7 +517,7 @@ export async function renderToHTMLImpl( stripInternalQueries(query) const isSSG = !!getStaticProps - const isBuildTimeSSG = isSSG && renderOpts.nextExport + const isBuildTimeSSG = isSSG && renderOpts.isBuildTimePrerendering const defaultAppGetInitialProps = App.getInitialProps === (App as any).origGetInitialProps @@ -532,7 +532,7 @@ export async function renderToHTMLImpl( (Component as any).origGetInitialProps if ( - renderOpts.nextExport && + renderOpts.isBuildTimePrerendering && hasPageGetInitialProps && !defaultErrorGetInitialProps ) { @@ -834,7 +834,9 @@ export async function renderToHTMLImpl( let props: any const nextExport = - !isSSG && (renderOpts.nextExport || (dev && (isAutoExport || isFallback))) + !isSSG && + (renderOpts.isBuildTimePrerendering || + (dev && (isAutoExport || isFallback))) const styledJsxInsertedHTML = () => { const styles = jsxStyleRegistry.styles() From a57e4e809b0f39668a57280a55bac1a4b20e6e2f Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Mon, 26 Jan 2026 09:49:43 +0100 Subject: [PATCH 2/7] Turbopack: remove Asset supertrait from Module trait. Modules don't have content (#86416) Reverts vercel/next.js#86415 --- .../ecmascript_client_reference_module.rs | 14 +--------- .../src/next_dynamic/dynamic_module.rs | 14 ---------- .../server_component_module.rs | 17 +----------- .../server_utility_module.rs | 15 +---------- .../transforms/swc_ecma_transform_plugins.rs | 14 ++++++++-- crates/next-core/src/raw_ecmascript_module.rs | 10 +------ .../app-dir/tailwind-css/postcss.config.js | 6 ----- .../app-dir/tailwind-css/postcss.config.mjs | 10 +++++++ .../postcss-config-cjs/app/postcss.config.cjs | 4 +++ .../crates/turbopack-core/src/changed.rs | 15 +++++++++-- .../turbopack-core/src/chunk/evaluate.rs | 3 +-- .../crates/turbopack-core/src/chunk/mod.rs | 4 +-- .../turbopack-core/src/introspect/module.rs | 8 ++++-- turbopack/crates/turbopack-core/src/module.rs | 6 ++--- .../turbopack-core/src/node_addon_module.rs | 9 ------- .../crates/turbopack-core/src/raw_module.rs | 9 ------- turbopack/crates/turbopack-core/src/rebase.rs | 15 ++++++++--- .../crates/turbopack-core/src/traced_asset.rs | 15 ++++++++--- turbopack/crates/turbopack-css/src/asset.rs | 9 ------- .../crates/turbopack-css/src/chunk/mod.rs | 2 +- turbopack/crates/turbopack-css/src/embed.rs | 4 +-- .../crates/turbopack-css/src/module_asset.rs | 9 ------- .../src/async_chunk/module.rs | 9 ------- .../src/chunk/placeable.rs | 2 +- .../src/inlined_bytes_module.rs | 12 ++------- .../crates/turbopack-ecmascript/src/lib.rs | 11 +------- .../src/manifest/chunk_asset.rs | 9 ------- .../turbopack-ecmascript/src/merged_module.rs | 9 ------- .../src/references/external_module.rs | 26 +++---------------- .../src/references/require_context.rs | 9 ------- .../side_effect_optimization/facade/module.rs | 12 --------- .../side_effect_optimization/locals/module.rs | 9 ------- .../src/tree_shake/asset.rs | 9 ------- .../src/tree_shake/side_effect_module.rs | 17 ------------ .../src/typescript/mod.rs | 10 +------ .../turbopack-ecmascript/src/webpack/mod.rs | 9 ------- .../src/worker_chunk/module.rs | 9 ------- turbopack/crates/turbopack-json/src/lib.rs | 12 ++------- .../turbopack-node/src/transforms/postcss.rs | 12 +++++---- turbopack/crates/turbopack-static/src/css.rs | 9 ------- turbopack/crates/turbopack-static/src/ecma.rs | 9 ------- .../crates/turbopack-wasm/src/module_asset.rs | 9 ------- turbopack/crates/turbopack-wasm/src/raw.rs | 9 ------- 43 files changed, 98 insertions(+), 336 deletions(-) delete mode 100644 test/e2e/app-dir/tailwind-css/postcss.config.js create mode 100644 test/e2e/app-dir/tailwind-css/postcss.config.mjs diff --git a/crates/next-core/src/next_client_reference/ecmascript_client_reference/ecmascript_client_reference_module.rs b/crates/next-core/src/next_client_reference/ecmascript_client_reference/ecmascript_client_reference_module.rs index 8afd9b5af6729..a0d624912d963 100644 --- a/crates/next-core/src/next_client_reference/ecmascript_client_reference/ecmascript_client_reference_module.rs +++ b/crates/next-core/src/next_client_reference/ecmascript_client_reference/ecmascript_client_reference_module.rs @@ -6,7 +6,7 @@ use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{IntoTraitRef, ResolvedVc, ValueToString, Vc}; use turbo_tasks_fs::{File, FileContent}; use turbopack_core::{ - asset::{Asset, AssetContent}, + asset::AssetContent, chunk::{ AsyncModuleInfo, ChunkGroupType, ChunkItem, ChunkType, ChunkableModule, ChunkableModuleReference, ChunkingContext, ChunkingType, ChunkingTypeOption, @@ -249,18 +249,6 @@ impl Module for EcmascriptClientReferenceModule { } } -#[turbo_tasks::value_impl] -impl Asset for EcmascriptClientReferenceModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - AssetContent::File( - FileContent::Content("// This is a proxy module for Next.js client references.".into()) - .resolved_cell(), - ) - .cell() - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for EcmascriptClientReferenceModule { #[turbo_tasks::function] diff --git a/crates/next-core/src/next_dynamic/dynamic_module.rs b/crates/next-core/src/next_dynamic/dynamic_module.rs index 07a0c3b6b1225..3ff31f98e31a5 100644 --- a/crates/next-core/src/next_dynamic/dynamic_module.rs +++ b/crates/next-core/src/next_dynamic/dynamic_module.rs @@ -4,9 +4,7 @@ use anyhow::Result; use indoc::formatdoc; use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{ResolvedVc, Vc}; -use turbo_tasks_fs::FileContent; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext, ModuleChunkItemIdExt}, ident::AssetIdent, module::{Module, ModuleSideEffects}, @@ -78,18 +76,6 @@ impl Module for NextDynamicEntryModule { } } -#[turbo_tasks::value_impl] -impl Asset for NextDynamicEntryModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - AssetContent::File( - FileContent::Content("// This is a marker module for Next.js dynamic.".into()) - .resolved_cell(), - ) - .cell() - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for NextDynamicEntryModule { #[turbo_tasks::function] diff --git a/crates/next-core/src/next_server_component/server_component_module.rs b/crates/next-core/src/next_server_component/server_component_module.rs index 5a36f168ea613..7c52489f86057 100644 --- a/crates/next-core/src/next_server_component/server_component_module.rs +++ b/crates/next-core/src/next_server_component/server_component_module.rs @@ -4,9 +4,8 @@ use anyhow::Result; use indoc::formatdoc; use turbo_rcstr::rcstr; use turbo_tasks::{ResolvedVc, Vc}; -use turbo_tasks_fs::{FileContent, FileSystemPath}; +use turbo_tasks_fs::FileSystemPath; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext, ModuleChunkItemIdExt}, ident::AssetIdent, module::{Module, ModuleSideEffects}, @@ -94,20 +93,6 @@ impl Module for NextServerComponentModule { } } -#[turbo_tasks::value_impl] -impl Asset for NextServerComponentModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - AssetContent::File( - FileContent::Content( - "// This is a marker module for Next.js server components.".into(), - ) - .resolved_cell(), - ) - .cell() - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for NextServerComponentModule { #[turbo_tasks::function] diff --git a/crates/next-core/src/next_server_utility/server_utility_module.rs b/crates/next-core/src/next_server_utility/server_utility_module.rs index dd2b7c41f9592..4271301726337 100644 --- a/crates/next-core/src/next_server_utility/server_utility_module.rs +++ b/crates/next-core/src/next_server_utility/server_utility_module.rs @@ -4,9 +4,8 @@ use anyhow::Result; use indoc::formatdoc; use turbo_rcstr::rcstr; use turbo_tasks::{ResolvedVc, Vc}; -use turbo_tasks_fs::{FileContent, FileSystemPath}; +use turbo_tasks_fs::FileSystemPath; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext, ModuleChunkItemIdExt}, ident::AssetIdent, module::{Module, ModuleSideEffects}, @@ -75,18 +74,6 @@ impl Module for NextServerUtilityModule { } } -#[turbo_tasks::value_impl] -impl Asset for NextServerUtilityModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - AssetContent::File( - FileContent::Content("// This is a marker module for Next.js server utilities.".into()) - .resolved_cell(), - ) - .cell() - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for NextServerUtilityModule { #[turbo_tasks::function] diff --git a/crates/next-core/src/next_shared/transforms/swc_ecma_transform_plugins.rs b/crates/next-core/src/next_shared/transforms/swc_ecma_transform_plugins.rs index 0e2677040fdbf..93a8ea3a8061b 100644 --- a/crates/next-core/src/next_shared/transforms/swc_ecma_transform_plugins.rs +++ b/crates/next-core/src/next_shared/transforms/swc_ecma_transform_plugins.rs @@ -68,10 +68,11 @@ pub async fn get_swc_ecma_transform_rule_impl( enable_mdx_rs: bool, ) -> Result> { use anyhow::bail; - use turbo_tasks::TryFlatJoinIterExt; + use turbo_tasks::{TryFlatJoinIterExt, ValueToString}; use turbo_tasks_fs::FileContent; use turbopack_core::{ asset::Asset, + module::Module, reference_type::{CommonJsReferenceSubType, ReferenceType}, resolve::{handle_resolve_error, parse::Request, resolve}, }; @@ -133,7 +134,16 @@ pub async fn get_swc_ecma_transform_rule_impl( return Ok(None); }; - let content = &*plugin_module.content().file_content().await?; + let Some(plugin_source) = &*plugin_module.source().await? else { + use anyhow::bail; + + bail!( + "Expected source for plugin module: {}", + plugin_module.ident().to_string().await? + ); + }; + + let content = &*plugin_source.content().file_content().await?; let FileContent::Content(file) = content else { bail!("Expected file content for plugin module"); }; diff --git a/crates/next-core/src/raw_ecmascript_module.rs b/crates/next-core/src/raw_ecmascript_module.rs index 72eeaf2842571..fcbf67e724fa6 100644 --- a/crates/next-core/src/raw_ecmascript_module.rs +++ b/crates/next-core/src/raw_ecmascript_module.rs @@ -9,7 +9,7 @@ use turbo_tasks::{FxIndexMap, FxIndexSet, ResolvedVc, TryJoinIterExt, ValueToStr use turbo_tasks_fs::{FileContent, rope::Rope}; use turbopack::{ModuleAssetContext, module_options::CustomModuleType}; use turbopack_core::{ - asset::{Asset, AssetContent}, + asset::Asset, chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext}, code_builder::CodeBuilder, compile_time_info::{ @@ -103,14 +103,6 @@ impl Module for RawEcmascriptModule { } } -#[turbo_tasks::value_impl] -impl Asset for RawEcmascriptModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for RawEcmascriptModule { #[turbo_tasks::function] diff --git a/test/e2e/app-dir/tailwind-css/postcss.config.js b/test/e2e/app-dir/tailwind-css/postcss.config.js deleted file mode 100644 index 33ad091d26d8a..0000000000000 --- a/test/e2e/app-dir/tailwind-css/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/test/e2e/app-dir/tailwind-css/postcss.config.mjs b/test/e2e/app-dir/tailwind-css/postcss.config.mjs new file mode 100644 index 0000000000000..2b56d6c8d9bbc --- /dev/null +++ b/test/e2e/app-dir/tailwind-css/postcss.config.mjs @@ -0,0 +1,10 @@ +// importing externals should work fine +import 'fs' +import 'path' + +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/test/e2e/postcss-config-cjs/app/postcss.config.cjs b/test/e2e/postcss-config-cjs/app/postcss.config.cjs index 5331562012af4..c3bc87b4c663f 100644 --- a/test/e2e/postcss-config-cjs/app/postcss.config.cjs +++ b/test/e2e/postcss-config-cjs/app/postcss.config.cjs @@ -1,3 +1,7 @@ +// importing externals should work fine +require('fs') +require('path') + // If you want to use other PostCSS plugins, see the following: // https://tailwindcss.com/docs/using-with-preprocessors module.exports = { diff --git a/turbopack/crates/turbopack-core/src/changed.rs b/turbopack/crates/turbopack-core/src/changed.rs index 4008933bd6a92..c8d3ee112e6c2 100644 --- a/turbopack/crates/turbopack-core/src/changed.rs +++ b/turbopack/crates/turbopack-core/src/changed.rs @@ -23,7 +23,7 @@ pub async fn get_referenced_modules( /// Returns a completion that changes when any content of any asset in the whole /// asset graph changes. #[turbo_tasks::function] -pub async fn any_content_changed_of_module( +pub async fn any_source_content_changed_of_module( root: ResolvedVc>, ) -> Result> { let completions = AdjacencyMap::new() @@ -31,7 +31,7 @@ pub async fn any_content_changed_of_module( .await .completed()? .into_postorder_topological() - .map(|m| content_changed(*ResolvedVc::upcast(m))) + .map(|m| source_changed(*m)) .map(|v| v.to_resolved()) .try_join() .await?; @@ -65,3 +65,14 @@ pub async fn content_changed(asset: Vc>) -> Result asset.content().file_content().await?; Ok(Completion::new()) } + +/// Returns a completion that changes when the content of the given asset +/// changes. +#[turbo_tasks::function] +pub async fn source_changed(asset: Vc>) -> Result> { + if let Some(source) = *asset.source().await? { + // Reading the file content is enough to add as dependency + source.content().file_content().await?; + } + Ok(Completion::new()) +} diff --git a/turbopack/crates/turbopack-core/src/chunk/evaluate.rs b/turbopack/crates/turbopack-core/src/chunk/evaluate.rs index c20aa32da3244..5b3cd3290e2db 100644 --- a/turbopack/crates/turbopack-core/src/chunk/evaluate.rs +++ b/turbopack/crates/turbopack-core/src/chunk/evaluate.rs @@ -3,7 +3,6 @@ use turbo_tasks::{ResolvedVc, Upcast, ValueToString, Vc}; use super::ChunkableModule; use crate::{ - asset::Asset, context::AssetContext, module::Module, reference_type::{EntryReferenceSubType, ReferenceType}, @@ -15,7 +14,7 @@ use crate::{ /// The chunking context implementation will resolve the dynamic entry to a /// well-known value or trait object. #[turbo_tasks::value_trait] -pub trait EvaluatableAsset: Asset + Module + ChunkableModule {} +pub trait EvaluatableAsset: Module + ChunkableModule {} pub trait EvaluatableAssetExt { fn to_evaluatable( diff --git a/turbopack/crates/turbopack-core/src/chunk/mod.rs b/turbopack/crates/turbopack-core/src/chunk/mod.rs index 28d533d35058f..62e10f441f1bd 100644 --- a/turbopack/crates/turbopack-core/src/chunk/mod.rs +++ b/turbopack/crates/turbopack-core/src/chunk/mod.rs @@ -89,7 +89,7 @@ pub struct ModuleIds(Vec); /// A [Module] that can be converted into a [Chunk]. #[turbo_tasks::value_trait] -pub trait ChunkableModule: Module + Asset { +pub trait ChunkableModule: Module { #[turbo_tasks::function] fn as_chunk_item( self: Vc, @@ -114,7 +114,7 @@ impl ChunkableModules { // with other module types (as a MergeableModule cannot prevent itself from being merged with other // module types) #[turbo_tasks::value_trait] -pub trait MergeableModule: Module + Asset { +pub trait MergeableModule: Module { /// Even though MergeableModule is implemented, this allows a dynamic condition to determine /// mergeability #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-core/src/introspect/module.rs b/turbopack/crates/turbopack-core/src/introspect/module.rs index a1f9bf7924f42..de76e6426ac9a 100644 --- a/turbopack/crates/turbopack-core/src/introspect/module.rs +++ b/turbopack/crates/turbopack-core/src/introspect/module.rs @@ -33,8 +33,12 @@ impl Introspectable for IntrospectableModule { } #[turbo_tasks::function] - fn details(&self) -> Vc { - content_to_details(self.0.content()) + async fn details(&self) -> Result> { + if let Some(source) = *self.0.source().await? { + Ok(content_to_details(source.content())) + } else { + Ok(Vc::cell("No source".into())) + } } #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-core/src/module.rs b/turbopack/crates/turbopack-core/src/module.rs index b55a6dd35394d..9684ae1136b36 100644 --- a/turbopack/crates/turbopack-core/src/module.rs +++ b/turbopack/crates/turbopack-core/src/module.rs @@ -1,7 +1,7 @@ use turbo_rcstr::RcStr; use turbo_tasks::{ResolvedVc, TaskInput, ValueToString, Vc}; -use crate::{asset::Asset, ident::AssetIdent, reference::ModuleReferences, source::OptionSource}; +use crate::{ident::AssetIdent, reference::ModuleReferences, source::OptionSource}; #[derive(Clone, Copy, Debug, TaskInput, Hash)] #[turbo_tasks::value(shared)] @@ -28,7 +28,7 @@ pub enum ModuleSideEffects { /// A module. This usually represents parsed source code, which has references /// to other modules. #[turbo_tasks::value_trait] -pub trait Module: Asset { +pub trait Module { /// The identifier of the [Module]. It's expected to be unique and capture /// all properties of the [Module]. #[turbo_tasks::function] @@ -64,7 +64,7 @@ pub trait Module: Asset { } #[turbo_tasks::value_trait] -pub trait StyleModule: Module + Asset { +pub trait StyleModule: Module { /// The style type of the module. #[turbo_tasks::function] fn style_type(&self) -> Vc; diff --git a/turbopack/crates/turbopack-core/src/node_addon_module.rs b/turbopack/crates/turbopack-core/src/node_addon_module.rs index 3c359537e84bd..278ae22cf49f4 100644 --- a/turbopack/crates/turbopack-core/src/node_addon_module.rs +++ b/turbopack/crates/turbopack-core/src/node_addon_module.rs @@ -7,7 +7,6 @@ use turbo_tasks::{FxIndexSet, ResolvedVc, TryJoinIterExt, Vc}; use turbo_tasks_fs::{FileSystemEntryType, FileSystemPath}; use crate::{ - asset::{Asset, AssetContent}, file_source::FileSource, ident::AssetIdent, module::{Module, ModuleSideEffects}, @@ -108,14 +107,6 @@ impl Module for NodeAddonModule { } } -#[turbo_tasks::value_impl] -impl Asset for NodeAddonModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - #[turbo_tasks::function] async fn dir_references(package_dir: FileSystemPath) -> Result> { let matches = read_matches( diff --git a/turbopack/crates/turbopack-core/src/raw_module.rs b/turbopack/crates/turbopack-core/src/raw_module.rs index cc0b430eeed33..7d93bb0a1df43 100644 --- a/turbopack/crates/turbopack-core/src/raw_module.rs +++ b/turbopack/crates/turbopack-core/src/raw_module.rs @@ -1,7 +1,6 @@ use turbo_tasks::{ResolvedVc, Vc}; use crate::{ - asset::{Asset, AssetContent}, ident::AssetIdent, module::{Module, ModuleSideEffects}, source::{OptionSource, Source}, @@ -31,14 +30,6 @@ impl Module for RawModule { } } -#[turbo_tasks::value_impl] -impl Asset for RawModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - #[turbo_tasks::value_impl] impl RawModule { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-core/src/rebase.rs b/turbopack/crates/turbopack-core/src/rebase.rs index 13a676b529c47..8f2d38f3efca1 100644 --- a/turbopack/crates/turbopack-core/src/rebase.rs +++ b/turbopack/crates/turbopack-core/src/rebase.rs @@ -1,7 +1,7 @@ use std::hash::Hash; -use anyhow::Result; -use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc}; +use anyhow::{Result, bail}; +use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc}; use turbo_tasks_fs::FileSystemPath; use crate::{ @@ -74,7 +74,14 @@ impl OutputAsset for RebasedAsset { #[turbo_tasks::value_impl] impl Asset for RebasedAsset { #[turbo_tasks::function] - fn content(&self) -> Vc { - self.module.content() + async fn content(&self) -> Result> { + if let Some(source) = *self.module.source().await? { + Ok(source.content()) + } else { + bail!( + "Module {} has no source", + self.module.ident().to_string().await? + ) + } } } diff --git a/turbopack/crates/turbopack-core/src/traced_asset.rs b/turbopack/crates/turbopack-core/src/traced_asset.rs index ec8e092fe8343..1202526eccc40 100644 --- a/turbopack/crates/turbopack-core/src/traced_asset.rs +++ b/turbopack/crates/turbopack-core/src/traced_asset.rs @@ -1,5 +1,5 @@ -use anyhow::Result; -use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc}; +use anyhow::{Result, bail}; +use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc}; use turbo_tasks_fs::FileSystemPath; use crate::{ @@ -54,7 +54,14 @@ impl OutputAsset for TracedAsset { #[turbo_tasks::value_impl] impl Asset for TracedAsset { #[turbo_tasks::function] - fn content(&self) -> Vc { - self.module.content() + async fn content(&self) -> Result> { + if let Some(source) = *self.module.source().await? { + Ok(source.content()) + } else { + bail!( + "Module {} has no source", + self.module.ident().to_string().await?, + ) + } } } diff --git a/turbopack/crates/turbopack-css/src/asset.rs b/turbopack/crates/turbopack-css/src/asset.rs index 9cd9b5e4b38f7..e82b14134bde2 100644 --- a/turbopack/crates/turbopack-css/src/asset.rs +++ b/turbopack/crates/turbopack-css/src/asset.rs @@ -3,7 +3,6 @@ use turbo_rcstr::rcstr; use turbo_tasks::{IntoTraitRef, ResolvedVc, TryJoinIterExt, ValueToString, Vc}; use turbo_tasks_fs::{FileContent, FileSystemPath}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext, MinifyType}, context::AssetContext, environment::Environment, @@ -171,14 +170,6 @@ impl StyleModule for CssModuleAsset { } } -#[turbo_tasks::value_impl] -impl Asset for CssModuleAsset { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for CssModuleAsset { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-css/src/chunk/mod.rs b/turbopack/crates/turbopack-css/src/chunk/mod.rs index 6db9d5427b9a9..4e748bfd4593f 100644 --- a/turbopack/crates/turbopack-css/src/chunk/mod.rs +++ b/turbopack/crates/turbopack-css/src/chunk/mod.rs @@ -441,7 +441,7 @@ impl GenerateSourceMap for CssChunk { // TODO: remove #[turbo_tasks::value_trait] -pub trait CssChunkPlaceable: ChunkableModule + Module + Asset {} +pub trait CssChunkPlaceable: ChunkableModule + Module {} #[derive(Clone, Debug)] #[turbo_tasks::value(shared)] diff --git a/turbopack/crates/turbopack-css/src/embed.rs b/turbopack/crates/turbopack-css/src/embed.rs index a3552ca2f5a86..6859bf617ae91 100644 --- a/turbopack/crates/turbopack-css/src/embed.rs +++ b/turbopack/crates/turbopack-css/src/embed.rs @@ -1,8 +1,8 @@ use turbo_tasks::Vc; -use turbopack_core::{asset::Asset, chunk::ChunkingContext, module::Module, output::OutputAsset}; +use turbopack_core::{chunk::ChunkingContext, module::Module, output::OutputAsset}; #[turbo_tasks::value_trait] -pub trait CssEmbed: Module + Asset { +pub trait CssEmbed: Module { #[turbo_tasks::function] fn embedded_asset( self: Vc, diff --git a/turbopack/crates/turbopack-css/src/module_asset.rs b/turbopack/crates/turbopack-css/src/module_asset.rs index 2df6ea1a732ed..655a7eb35c146 100644 --- a/turbopack/crates/turbopack-css/src/module_asset.rs +++ b/turbopack/crates/turbopack-css/src/module_asset.rs @@ -8,7 +8,6 @@ use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{FxIndexMap, IntoTraitRef, ResolvedVc, ValueToString, Vc}; use turbo_tasks_fs::{FileSystemPath, rope::Rope}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext, ModuleChunkItemIdExt}, context::{AssetContext, ProcessResult}, ident::AssetIdent, @@ -120,14 +119,6 @@ impl Module for ModuleCssAsset { } } -#[turbo_tasks::value_impl] -impl Asset for ModuleCssAsset { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - /// A CSS class that is exported from a CSS module. /// /// See [`ModuleCssClasses`] for more information. diff --git a/turbopack/crates/turbopack-ecmascript/src/async_chunk/module.rs b/turbopack/crates/turbopack-ecmascript/src/async_chunk/module.rs index 4b35fa32b7da7..c0f4b0d462d4a 100644 --- a/turbopack/crates/turbopack-ecmascript/src/async_chunk/module.rs +++ b/turbopack/crates/turbopack-ecmascript/src/async_chunk/module.rs @@ -2,7 +2,6 @@ use anyhow::Result; use turbo_rcstr::rcstr; use turbo_tasks::{ResolvedVc, Vc}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ChunkableModule, ChunkingContext, availability_info::AvailabilityInfo}, ident::AssetIdent, module::{Module, ModuleSideEffects}, @@ -72,14 +71,6 @@ impl Module for AsyncLoaderModule { } } -#[turbo_tasks::value_impl] -impl Asset for AsyncLoaderModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - panic!("content() should not be called"); - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for AsyncLoaderModule { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-ecmascript/src/chunk/placeable.rs b/turbopack/crates/turbopack-ecmascript/src/chunk/placeable.rs index 22aeec9fc8711..71e836639b6b2 100644 --- a/turbopack/crates/turbopack-ecmascript/src/chunk/placeable.rs +++ b/turbopack/crates/turbopack-ecmascript/src/chunk/placeable.rs @@ -26,7 +26,7 @@ use crate::references::{ }; #[turbo_tasks::value_trait] -pub trait EcmascriptChunkPlaceable: ChunkableModule + Module + Asset { +pub trait EcmascriptChunkPlaceable: ChunkableModule + Module { #[turbo_tasks::function] fn get_exports(self: Vc) -> Vc; #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-ecmascript/src/inlined_bytes_module.rs b/turbopack/crates/turbopack-ecmascript/src/inlined_bytes_module.rs index 394e2d202a2b3..dee7ad9be1a7a 100644 --- a/turbopack/crates/turbopack-ecmascript/src/inlined_bytes_module.rs +++ b/turbopack/crates/turbopack-ecmascript/src/inlined_bytes_module.rs @@ -5,7 +5,7 @@ use turbo_rcstr::rcstr; use turbo_tasks::{ResolvedVc, ValueToString, Vc}; use turbo_tasks_fs::{FileContent, rope::RopeBuilder}; use turbopack_core::{ - asset::{Asset, AssetContent}, + asset::Asset, chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext}, ident::AssetIdent, module::{Module, ModuleSideEffects}, @@ -56,14 +56,6 @@ impl Module for InlinedBytesJsModule { } } -#[turbo_tasks::value_impl] -impl Asset for InlinedBytesJsModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for InlinedBytesJsModule { #[turbo_tasks::function] @@ -125,7 +117,7 @@ impl ChunkItem for InlinedBytesJsChunkItem { impl EcmascriptChunkItem for InlinedBytesJsChunkItem { #[turbo_tasks::function] async fn content(&self) -> Result> { - let content = self.module.content().file_content().await?; + let content = self.module.await?.source.content().file_content().await?; match &*content { FileContent::Content(data) => { let mut inner_code = RopeBuilder::default(); diff --git a/turbopack/crates/turbopack-ecmascript/src/lib.rs b/turbopack/crates/turbopack-ecmascript/src/lib.rs index dcaf54cd9ffad..3818d826d220e 100644 --- a/turbopack/crates/turbopack-ecmascript/src/lib.rs +++ b/turbopack/crates/turbopack-ecmascript/src/lib.rs @@ -80,7 +80,6 @@ use turbo_tasks::{ }; use turbo_tasks_fs::{FileJsonContent, FileSystemPath, glob::Glob, rope::Rope}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ AsyncModuleInfo, ChunkItem, ChunkType, ChunkableModule, ChunkingContext, EvaluatableAsset, MergeableModule, MergeableModuleExposure, MergeableModules, MergeableModulesExposed, @@ -403,7 +402,7 @@ pub trait EcmascriptParsable { } #[turbo_tasks::value_trait] -pub trait EcmascriptAnalyzable: Module + Asset { +pub trait EcmascriptAnalyzable: Module { #[turbo_tasks::function] fn analyze(self: Vc) -> Vc; @@ -780,14 +779,6 @@ impl Module for EcmascriptModuleAsset { } } -#[turbo_tasks::value_impl] -impl Asset for EcmascriptModuleAsset { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for EcmascriptModuleAsset { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-ecmascript/src/manifest/chunk_asset.rs b/turbopack/crates/turbopack-ecmascript/src/manifest/chunk_asset.rs index 826ec5c644c2c..74a2db1f0c030 100644 --- a/turbopack/crates/turbopack-ecmascript/src/manifest/chunk_asset.rs +++ b/turbopack/crates/turbopack-ecmascript/src/manifest/chunk_asset.rs @@ -2,7 +2,6 @@ use anyhow::Result; use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ ChunkableModule, ChunkingContext, ChunkingContextExt, availability_info::AvailabilityInfo, }, @@ -158,14 +157,6 @@ impl Module for ManifestAsyncModule { } } -#[turbo_tasks::value_impl] -impl Asset for ManifestAsyncModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - panic!("content() should not be called"); - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for ManifestAsyncModule { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-ecmascript/src/merged_module.rs b/turbopack/crates/turbopack-ecmascript/src/merged_module.rs index eef2535a4dea2..6d4d23f8cda2c 100644 --- a/turbopack/crates/turbopack-ecmascript/src/merged_module.rs +++ b/turbopack/crates/turbopack-ecmascript/src/merged_module.rs @@ -1,7 +1,6 @@ use anyhow::{Context, Result}; use turbo_tasks::{ResolvedVc, Vc}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ AsyncModuleInfo, ChunkItem, ChunkType, ChunkableModule, ChunkingContext, MergeableModuleExposure, MergeableModules, MergeableModulesExposed, @@ -60,14 +59,6 @@ impl MergedEcmascriptModule { } } -#[turbo_tasks::value_impl] -impl Asset for MergedEcmascriptModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - panic!("content() should not be called"); - } -} - #[turbo_tasks::value_impl] impl Module for MergedEcmascriptModule { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-ecmascript/src/references/external_module.rs b/turbopack/crates/turbopack-ecmascript/src/references/external_module.rs index 5080760762958..83755dd89a82a 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/external_module.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/external_module.rs @@ -4,9 +4,7 @@ use anyhow::{Context, Result}; use bincode::{Decode, Encode}; use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{NonLocalValue, ResolvedVc, TaskInput, TryJoinIterExt, Vc, trace::TraceRawVcs}; -use turbo_tasks_fs::{ - FileContent, FileSystem, FileSystemPath, LinkType, VirtualFileSystem, rope::RopeBuilder, -}; +use turbo_tasks_fs::{FileSystem, FileSystemPath, LinkType, VirtualFileSystem, rope::RopeBuilder}; use turbo_tasks_hash::{encode_hex, hash_xxh3_hash64}; use turbopack_core::{ asset::{Asset, AssetContent}, @@ -332,7 +330,8 @@ impl Module for CachedExternalModule { // graph for chunking. `compute_async_module_info` computes // `is_self_async` for every module, but at least for traced modules, // that value is never used as `ChunkingType::Traced.is_inherit_async() - // == false`. Optimize this case by using `ModuleWithoutSelfAsync` to + // == false`. Optimize this case by using + // `SideEffectfulModuleWithoutSelfAsync` to // short circuit that computation and thus defer parsing traced modules // to emitting to not block all of chunking on this. .map(|m| Vc::upcast(SideEffectfulModuleWithoutSelfAsync::new(*m))), @@ -362,15 +361,6 @@ impl Module for CachedExternalModule { } } -#[turbo_tasks::value_impl] -impl Asset for CachedExternalModule { - #[turbo_tasks::function] - fn content(self: Vc) -> Vc { - // should be `NotFound` as this function gets called to detect source changes - AssetContent::file(FileContent::NotFound.cell()) - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for CachedExternalModule { #[turbo_tasks::function] @@ -518,14 +508,6 @@ impl SideEffectfulModuleWithoutSelfAsync { } } -#[turbo_tasks::value_impl] -impl Asset for SideEffectfulModuleWithoutSelfAsync { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.module.content() - } -} - #[turbo_tasks::value_impl] impl Module for SideEffectfulModuleWithoutSelfAsync { #[turbo_tasks::function] @@ -535,7 +517,7 @@ impl Module for SideEffectfulModuleWithoutSelfAsync { #[turbo_tasks::function] fn source(&self) -> Vc { - Vc::cell(None) + self.module.source() } #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-ecmascript/src/references/require_context.rs b/turbopack/crates/turbopack-ecmascript/src/references/require_context.rs index f4f708b03ef9c..0ae6626d1efcc 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/require_context.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/require_context.rs @@ -21,7 +21,6 @@ use turbo_tasks::{ }; use turbo_tasks_fs::{DirectoryContent, DirectoryEntry, FileSystemPath}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ ChunkItem, ChunkType, ChunkableModule, ChunkableModuleReference, ChunkingContext, MinifyType, ModuleChunkItemIdExt, @@ -439,14 +438,6 @@ impl Module for RequireContextAsset { } } -#[turbo_tasks::value_impl] -impl Asset for RequireContextAsset { - #[turbo_tasks::function] - fn content(&self) -> Vc { - unimplemented!() - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for RequireContextAsset { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-ecmascript/src/side_effect_optimization/facade/module.rs b/turbopack/crates/turbopack-ecmascript/src/side_effect_optimization/facade/module.rs index 557bef18c0254..87a734e327098 100644 --- a/turbopack/crates/turbopack-ecmascript/src/side_effect_optimization/facade/module.rs +++ b/turbopack/crates/turbopack-ecmascript/src/side_effect_optimization/facade/module.rs @@ -2,9 +2,7 @@ use std::collections::BTreeMap; use anyhow::{Result, bail}; use turbo_tasks::{ResolvedVc, Vc}; -use turbo_tasks_fs::{File, FileContent}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ AsyncModuleInfo, ChunkableModule, ChunkingContext, EvaluatableAsset, MergeableModule, MergeableModules, MergeableModulesExposed, @@ -193,16 +191,6 @@ impl Module for EcmascriptModuleFacadeModule { } } -#[turbo_tasks::value_impl] -impl Asset for EcmascriptModuleFacadeModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - let f = File::from(""); - - AssetContent::file(FileContent::Content(f).cell()) - } -} - #[turbo_tasks::value_impl] impl EcmascriptAnalyzable for EcmascriptModuleFacadeModule { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-ecmascript/src/side_effect_optimization/locals/module.rs b/turbopack/crates/turbopack-ecmascript/src/side_effect_optimization/locals/module.rs index 79953176a15dc..aca4f45ff4bba 100644 --- a/turbopack/crates/turbopack-ecmascript/src/side_effect_optimization/locals/module.rs +++ b/turbopack/crates/turbopack-ecmascript/src/side_effect_optimization/locals/module.rs @@ -3,7 +3,6 @@ use std::collections::BTreeMap; use anyhow::{Result, bail}; use turbo_tasks::{ResolvedVc, Vc}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ AsyncModuleInfo, ChunkableModule, ChunkingContext, MergeableModule, MergeableModules, MergeableModulesExposed, @@ -77,14 +76,6 @@ impl Module for EcmascriptModuleLocalsModule { } } -#[turbo_tasks::value_impl] -impl Asset for EcmascriptModuleLocalsModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.module.content() - } -} - #[turbo_tasks::value_impl] impl EcmascriptAnalyzable for EcmascriptModuleLocalsModule { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-ecmascript/src/tree_shake/asset.rs b/turbopack/crates/turbopack-ecmascript/src/tree_shake/asset.rs index 2c1a21f2b1bde..787fb24bb8b46 100644 --- a/turbopack/crates/turbopack-ecmascript/src/tree_shake/asset.rs +++ b/turbopack/crates/turbopack-ecmascript/src/tree_shake/asset.rs @@ -2,7 +2,6 @@ use anyhow::Result; use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{ResolvedVc, Vc}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{AsyncModuleInfo, ChunkableModule, ChunkingContext, EvaluatableAsset}, ident::AssetIdent, module::{Module, ModuleSideEffects}, @@ -345,14 +344,6 @@ impl Module for EcmascriptModulePartAsset { } } -#[turbo_tasks::value_impl] -impl Asset for EcmascriptModulePartAsset { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.full_module.content() - } -} - #[turbo_tasks::value_impl] impl EcmascriptChunkPlaceable for EcmascriptModulePartAsset { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-ecmascript/src/tree_shake/side_effect_module.rs b/turbopack/crates/turbopack-ecmascript/src/tree_shake/side_effect_module.rs index bc7ffb1205ef5..34a5262a0de4f 100644 --- a/turbopack/crates/turbopack-ecmascript/src/tree_shake/side_effect_module.rs +++ b/turbopack/crates/turbopack-ecmascript/src/tree_shake/side_effect_module.rs @@ -1,9 +1,7 @@ use anyhow::Result; use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc}; -use turbo_tasks_fs::FileContent; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ChunkableModule, ChunkingContext, EvaluatableAsset}, ident::AssetIdent, module::{Module, ModuleSideEffects}, @@ -122,21 +120,6 @@ impl Module for SideEffectsModule { } } -#[turbo_tasks::value_impl] -impl Asset for SideEffectsModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - AssetContent::File( - FileContent::Content( - "// This is a proxy module for reexportings a module with additional side effects." - .into(), - ) - .resolved_cell(), - ) - .cell() - } -} - #[turbo_tasks::value_impl] impl EcmascriptChunkPlaceable for SideEffectsModule { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-ecmascript/src/typescript/mod.rs b/turbopack/crates/turbopack-ecmascript/src/typescript/mod.rs index 392b63ef44778..adc34f8f762b4 100644 --- a/turbopack/crates/turbopack-ecmascript/src/typescript/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/typescript/mod.rs @@ -4,7 +4,7 @@ use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc}; use turbo_tasks_fs::DirectoryContent; use turbopack_core::{ - asset::{Asset, AssetContent}, + asset::Asset, ident::AssetIdent, module::{Module, ModuleSideEffects}, raw_module::RawModule, @@ -180,14 +180,6 @@ impl Module for TsConfigModuleAsset { } } -#[turbo_tasks::value_impl] -impl Asset for TsConfigModuleAsset { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - #[turbo_tasks::value] #[derive(Hash, Debug)] pub struct CompilerReference { diff --git a/turbopack/crates/turbopack-ecmascript/src/webpack/mod.rs b/turbopack/crates/turbopack-ecmascript/src/webpack/mod.rs index 11c559a57d337..534439a8a8725 100644 --- a/turbopack/crates/turbopack-ecmascript/src/webpack/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/webpack/mod.rs @@ -3,7 +3,6 @@ use swc_core::ecma::ast::Lit; use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{ResolvedVc, ValueToString, Vc}; use turbopack_core::{ - asset::{Asset, AssetContent}, file_source::FileSource, ident::AssetIdent, module::{Module, ModuleSideEffects}, @@ -69,14 +68,6 @@ impl Module for WebpackModuleAsset { } } -#[turbo_tasks::value_impl] -impl Asset for WebpackModuleAsset { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - #[turbo_tasks::value(shared)] pub struct WebpackChunkAssetReference { #[turbo_tasks(trace_ignore)] diff --git a/turbopack/crates/turbopack-ecmascript/src/worker_chunk/module.rs b/turbopack/crates/turbopack-ecmascript/src/worker_chunk/module.rs index e2dc1742dbb39..806c293ce63b5 100644 --- a/turbopack/crates/turbopack-ecmascript/src/worker_chunk/module.rs +++ b/turbopack/crates/turbopack-ecmascript/src/worker_chunk/module.rs @@ -2,7 +2,6 @@ use anyhow::Result; use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{ResolvedVc, ValueToString, Vc}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ ChunkGroupType, ChunkableModule, ChunkableModuleReference, ChunkingContext, ChunkingType, ChunkingTypeOption, @@ -75,14 +74,6 @@ impl Module for WorkerLoaderModule { } } -#[turbo_tasks::value_impl] -impl Asset for WorkerLoaderModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - panic!("content() should not be called"); - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for WorkerLoaderModule { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-json/src/lib.rs b/turbopack/crates/turbopack-json/src/lib.rs index 86643337737e1..b9476284400fe 100644 --- a/turbopack/crates/turbopack-json/src/lib.rs +++ b/turbopack/crates/turbopack-json/src/lib.rs @@ -16,7 +16,7 @@ use turbo_rcstr::rcstr; use turbo_tasks::{ResolvedVc, ValueToString, Vc}; use turbo_tasks_fs::{FileContent, FileJsonContent}; use turbopack_core::{ - asset::{Asset, AssetContent}, + asset::Asset, chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext}, code_builder::CodeBuilder, ident::AssetIdent, @@ -64,14 +64,6 @@ impl Module for JsonModuleAsset { } } -#[turbo_tasks::value_impl] -impl Asset for JsonModuleAsset { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for JsonModuleAsset { #[turbo_tasks::function] @@ -135,7 +127,7 @@ impl EcmascriptChunkItem for JsonChunkItem { async fn content(&self) -> Result> { // We parse to JSON and then stringify again to ensure that the // JSON is valid. - let content = self.module.content().file_content(); + let content = self.module.await?.source.content().file_content(); let data = content.parse_json().await?; match &*data { FileJsonContent::Content(data) => { diff --git a/turbopack/crates/turbopack-node/src/transforms/postcss.rs b/turbopack/crates/turbopack-node/src/transforms/postcss.rs index 673573e3bb8f3..220ede5d591c5 100644 --- a/turbopack/crates/turbopack-node/src/transforms/postcss.rs +++ b/turbopack/crates/turbopack-node/src/transforms/postcss.rs @@ -12,7 +12,7 @@ use turbo_tasks_fs::{ }; use turbopack_core::{ asset::{Asset, AssetContent}, - changed::any_content_changed_of_module, + changed::any_source_content_changed_of_module, context::{AssetContext, ProcessResult}, file_source::FileSource, ident::AssetIdent, @@ -195,7 +195,7 @@ async fn config_changed( .module(); Ok(Vc::::cell(vec![ - any_content_changed_of_module(config_asset) + any_source_content_changed_of_module(config_asset) .to_resolved() .await?, extra_configs_changed(asset_context, postcss_config_path) @@ -231,9 +231,11 @@ async fn extra_configs_changed( .try_into_module() .await? { - Some(module) => { - Some(any_content_changed_of_module(*module).to_resolved().await?) - } + Some(module) => Some( + any_source_content_changed_of_module(*module) + .to_resolved() + .await?, + ), None => None, } } else { diff --git a/turbopack/crates/turbopack-static/src/css.rs b/turbopack/crates/turbopack-static/src/css.rs index 40e8f95f1e0f4..a12266af3e6ee 100644 --- a/turbopack/crates/turbopack-static/src/css.rs +++ b/turbopack/crates/turbopack-static/src/css.rs @@ -1,7 +1,6 @@ use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{ResolvedVc, Vc}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::ChunkingContext, ident::AssetIdent, module::{Module, ModuleSideEffects}, @@ -57,14 +56,6 @@ impl Module for StaticUrlCssModule { } } -#[turbo_tasks::value_impl] -impl Asset for StaticUrlCssModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - #[turbo_tasks::value_impl] impl CssEmbed for StaticUrlCssModule { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-static/src/ecma.rs b/turbopack/crates/turbopack-static/src/ecma.rs index 5921b5d05abb9..24e414058ea84 100644 --- a/turbopack/crates/turbopack-static/src/ecma.rs +++ b/turbopack/crates/turbopack-static/src/ecma.rs @@ -2,7 +2,6 @@ use anyhow::Result; use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{ResolvedVc, Vc}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{AssetSuffix, ChunkItem, ChunkType, ChunkableModule, ChunkingContext}, ident::AssetIdent, module::{Module, ModuleSideEffects}, @@ -69,14 +68,6 @@ impl Module for StaticUrlJsModule { } } -#[turbo_tasks::value_impl] -impl Asset for StaticUrlJsModule { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for StaticUrlJsModule { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-wasm/src/module_asset.rs b/turbopack/crates/turbopack-wasm/src/module_asset.rs index db923935d7658..828db4580d1ca 100644 --- a/turbopack/crates/turbopack-wasm/src/module_asset.rs +++ b/turbopack/crates/turbopack-wasm/src/module_asset.rs @@ -3,7 +3,6 @@ use turbo_rcstr::rcstr; use turbo_tasks::{IntoTraitRef, ResolvedVc, Vc, fxindexmap}; use turbo_tasks_fs::FileSystemPath; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ AsyncModuleInfo, ChunkItem, ChunkType, ChunkableModule, ChunkingContext, chunk_group::references_to_output_assets, @@ -153,14 +152,6 @@ impl Module for WebAssemblyModuleAsset { } } -#[turbo_tasks::value_impl] -impl Asset for WebAssemblyModuleAsset { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for WebAssemblyModuleAsset { #[turbo_tasks::function] diff --git a/turbopack/crates/turbopack-wasm/src/raw.rs b/turbopack/crates/turbopack-wasm/src/raw.rs index 3365e7b2ec4fd..c16f0522774b0 100644 --- a/turbopack/crates/turbopack-wasm/src/raw.rs +++ b/turbopack/crates/turbopack-wasm/src/raw.rs @@ -2,7 +2,6 @@ use anyhow::{Result, bail}; use turbo_rcstr::rcstr; use turbo_tasks::{IntoTraitRef, ResolvedVc, Vc}; use turbopack_core::{ - asset::{Asset, AssetContent}, chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext}, context::AssetContext, ident::AssetIdent, @@ -72,14 +71,6 @@ impl Module for RawWebAssemblyModuleAsset { } } -#[turbo_tasks::value_impl] -impl Asset for RawWebAssemblyModuleAsset { - #[turbo_tasks::function] - fn content(&self) -> Vc { - self.source.content() - } -} - #[turbo_tasks::value_impl] impl ChunkableModule for RawWebAssemblyModuleAsset { #[turbo_tasks::function] From 0a6b9ba5b350826f39d35c77f5d71d5eed331b79 Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 26 Jan 2026 09:51:39 +0100 Subject: [PATCH 3/7] docs: improve clarity in cache components and server/client docs (#88946) - Applying learnings from a recipe - React.cache usage under a use cache scope --- .../05-server-and-client-components.mdx | 185 ++++++++++++++++++ .../06-cache-components.mdx | 4 + .../01-directives/use-cache.mdx | 32 +++ 3 files changed, 221 insertions(+) diff --git a/docs/01-app/01-getting-started/05-server-and-client-components.mdx b/docs/01-app/01-getting-started/05-server-and-client-components.mdx index 53e82fe5d099d..832473b9877bf 100644 --- a/docs/01-app/01-getting-started/05-server-and-client-components.mdx +++ b/docs/01-app/01-getting-started/05-server-and-client-components.mdx @@ -412,6 +412,191 @@ Your Server Component will now be able to directly render your provider, and all > **Good to know**: You should render providers as deep as possible in the tree – notice how `ThemeProvider` only wraps `{children}` instead of the entire `` document. This makes it easier for Next.js to optimize the static parts of your Server Components. +### Sharing data with context and React.cache + +You can share fetched data across both Server and Client Components by combining [`React.cache`](https://react.dev/reference/react/cache) with context providers. + +Create a cached function that fetches data: + +```ts filename="app/lib/user.ts" switcher +import { cache } from 'react' + +export const getUser = cache(async () => { + const res = await fetch('https://api.example.com/user') + return res.json() +}) +``` + +```js filename="app/lib/user.js" switcher +import { cache } from 'react' + +export const getUser = cache(async () => { + const res = await fetch('https://api.example.com/user') + return res.json() +}) +``` + +Create a context provider that stores the promise: + +```tsx filename="app/user-provider.tsx" switcher +'use client' + +import { createContext } from 'react' + +type User = { + id: string + name: string +} + +export const UserContext = createContext | null>(null) + +export default function UserProvider({ + children, + userPromise, +}: { + children: React.ReactNode + userPromise: Promise +}) { + return {children} +} +``` + +```jsx filename="app/user-provider.js" switcher +'use client' + +import { createContext } from 'react' + +export const UserContext = createContext(null) + +export default function UserProvider({ children, userPromise }) { + return {children} +} +``` + +In a layout, pass the promise to the provider without awaiting: + +```tsx filename="app/layout.tsx" switcher +import UserProvider from './user-provider' +import { getUser } from './lib/user' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + const userPromise = getUser() // Don't await + + return ( + + + {children} + + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import UserProvider from './user-provider' +import { getUser } from './lib/user' + +export default function RootLayout({ children }) { + const userPromise = getUser() // Don't await + + return ( + + + {children} + + + ) +} +``` + +Client Components use [`use()`](https://react.dev/reference/react/use) to resolve the promise from context, wrapped in `` for fallback UI: + +```tsx filename="app/ui/profile.tsx" switcher +'use client' + +import { use, useContext } from 'react' +import { UserContext } from '../user-provider' + +export function Profile() { + const userPromise = useContext(UserContext) + if (!userPromise) { + throw new Error('useContext must be used within a UserProvider') + } + const user = use(userPromise) + return

Welcome, {user.name}

+} +``` + +```jsx filename="app/ui/profile.js" switcher +'use client' + +import { use, useContext } from 'react' +import { UserContext } from '../user-provider' + +export function Profile() { + const userPromise = useContext(UserContext) + if (!userPromise) { + throw new Error('useContext must be used within a UserProvider') + } + const user = use(userPromise) + return

Welcome, {user.name}

+} +``` + +```tsx filename="app/page.tsx" switcher +import { Suspense } from 'react' +import { Profile } from './ui/profile' + +export default function Page() { + return ( + Loading profile...}> + + + ) +} +``` + +```jsx filename="app/page.js" switcher +import { Suspense } from 'react' +import { Profile } from './ui/profile' + +export default function Page() { + return ( + Loading profile...}> + + + ) +} +``` + +Server Components can also call `getUser()` directly: + +```tsx filename="app/dashboard/page.tsx" switcher +import { getUser } from '../lib/user' + +export default async function DashboardPage() { + const user = await getUser() // Cached - same request, no duplicate fetch + return

Dashboard for {user.name}

+} +``` + +```jsx filename="app/dashboard/page.js" switcher +import { getUser } from '../lib/user' + +export default async function DashboardPage() { + const user = await getUser() // Cached - same request, no duplicate fetch + return

Dashboard for {user.name}

+} +``` + +Since `getUser` is wrapped with `React.cache`, multiple calls within the same request return the same memoized result, whether called directly in Server Components or resolved via context in Client Components. + +> **Good to know**: `React.cache` is scoped to the current request only. Each request gets its own memoization scope with no sharing between requests. + ### Third-party components When using a third-party component that relies on client-only features, you can wrap it in a Client Component to ensure it works as expected. diff --git a/docs/01-app/01-getting-started/06-cache-components.mdx b/docs/01-app/01-getting-started/06-cache-components.mdx index 83221cdb24a54..ac9d16cbb4bd5 100644 --- a/docs/01-app/01-getting-started/06-cache-components.mdx +++ b/docs/01-app/01-getting-started/06-cache-components.mdx @@ -194,6 +194,10 @@ Use [`connection()`](/docs/app/api-reference/functions/connection) if you need t > **Good to know**: Runtime data cannot be cached with `use cache` because it requires request context. Components that access runtime APIs must always be wrapped in ``. However, you can extract values from runtime data and pass them as arguments to cached functions. See the [with runtime data](#with-runtime-data) section for an example. +One approach for reading runtime data like cookies without blocking the static shell is to pass a promise to a client context provider. See [Sharing data with context and React.cache](/docs/app/getting-started/server-and-client-components#sharing-data-with-context-and-reactcache) for an example. + +> **Good to know:** `React.cache` operates in an isolated scope inside `use cache` boundaries. See [React.cache isolation](/docs/app/api-reference/directives/use-cache#reactcache-isolation) for more information. + ### Non-deterministic operations Operations like `Math.random()`, `Date.now()`, or `crypto.randomUUID()` produce different values each time they execute. To ensure these run at request time (generating unique values per request), Cache Components requires you to explicitly signal this intent by calling these operations after dynamic or runtime data access. diff --git a/docs/01-app/03-api-reference/01-directives/use-cache.mdx b/docs/01-app/03-api-reference/01-directives/use-cache.mdx index d75de012f1349..1be704ed351f4 100644 --- a/docs/01-app/03-api-reference/01-directives/use-cache.mdx +++ b/docs/01-app/03-api-reference/01-directives/use-cache.mdx @@ -189,6 +189,10 @@ async function CachedForm({ action }: { action: () => Promise }) { ## Constraints +Cached functions execute in an isolated environment. The following constraints ensure cache behavior remains predictable and secure. + +### Runtime APIs + Cached functions and components **cannot** directly access runtime APIs like `cookies()`, `headers()`, or `searchParams`. Instead, read these values outside the cached scope and pass them as arguments. ### Runtime caching considerations @@ -206,6 +210,34 @@ If the default in-memory cache isn't enough, consider **[`use cache: remote`](/d Very rarely, for compliance requirements or when you can't refactor your code to pass runtime data as arguments to a `use cache` scope, you might need [`use cache: private`](/docs/app/api-reference/directives/use-cache-private). +### React.cache isolation + +[`React.cache`](https://react.dev/reference/react/cache) operates in an isolated scope inside `use cache` boundaries. Values stored via `React.cache` outside a `use cache` function are not visible inside it. + +This means you cannot use `React.cache` to pass data into a `use cache` scope: + +```tsx +import { cache } from 'react' + +const store = cache(() => ({ current: null as string | null })) + +function Parent() { + const shared = store() + shared.current = 'value from parent' + return +} + +async function Child() { + 'use cache' + const shared = store() + // shared.current is null, not 'value from parent' + // use cache has its own isolated React.cache scope + return
{shared.current}
+} +``` + +This isolation ensures cached functions have predictable, self-contained behavior. To pass data into a `use cache` scope, use function arguments instead. + ## `use cache` at runtime On the **server**, cache entries are stored in-memory and respect the `revalidate` and `expire` times from your `cacheLife` configuration. You can customize the cache storage by configuring [`cacheHandlers`](/docs/app/api-reference/config/next-config-js/cacheHandlers) in your `next.config.js` file. From f2aa053c9f885cbfa2ada5642d20efad63984d3d Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 26 Jan 2026 12:19:00 +0100 Subject: [PATCH 4/7] docs: revalidatePath w/ rewrites and trailing slash (#88956) Follow up docs change for: https://github.com/vercel/next.js/pull/88732 and https://github.com/vercel/next.js/pull/88623 --- .../04-functions/revalidatePath.mdx | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/docs/01-app/03-api-reference/04-functions/revalidatePath.mdx b/docs/01-app/03-api-reference/04-functions/revalidatePath.mdx index c25f4a238f7bc..fdaa544930346 100644 --- a/docs/01-app/03-api-reference/04-functions/revalidatePath.mdx +++ b/docs/01-app/03-api-reference/04-functions/revalidatePath.mdx @@ -22,10 +22,10 @@ description: API Reference for the revalidatePath function. revalidatePath(path: string, type?: 'page' | 'layout'): void; ``` -- `path`: Either a route pattern corresponding to the data you want to revalidate, for example `/product/[slug]`, or a specific URL, `/product/123`. Do not append `/page` or `/layout`, use the `type` parameter instead. Must not exceed 1024 characters. This value is case-sensitive. -- `type`: (optional) `'page'` or `'layout'` string to change the type of path to revalidate. If `path` contains a dynamic segment, for example `/product/[slug]`, this parameter is required. If `path` is a specific URL, `/product/1`, omit `type`. +- `path`: Either a string that represents your route file structure. This can be a literal path like `/product/123`, or a route pattern with dynamic segments like `/product/[slug]`. Do not append `/page` or `/layout`, use the `type` parameter instead. Must not exceed 1024 characters. This value is case-sensitive. You do not need to include a trailing slash, regardless of your [`trailingSlash`](/docs/app/api-reference/config/next-config-js/trailingSlash) config. +- `type`: (optional) `'page'` or `'layout'` string to change the type of path to revalidate. If `path` contains a dynamic segment, for example `/product/[slug]`, this parameter is required. If `path` is a literal path like `/product/1`, omit `type`. -Use a specific URL when you want to refresh a [single page](#revalidating-a-specific-url). Use a route pattern plus `type` to refresh [multiple URLs](#revalidating-a-page-path). +Use a literal path when you want to refresh a [single page](#revalidating-a-specific-path). Use a route pattern plus `type` to refresh [all matching pages](#revalidating-a-page-path). ## Returns @@ -49,6 +49,37 @@ export async function GET() { } ``` +## Using `revalidatePath` with rewrites + +When using [rewrites](/docs/app/api-reference/config/next-config-js/rewrites), you must pass the **destination** path (the actual route file location), not the source path that appears in the browser's address bar. + +For example, if you have a rewrite from `/blog` to `/news`: + +```js filename="next.config.js" +module.exports = { + async rewrites() { + return [ + { + source: '/blog', + destination: '/news', + }, + ] + }, +} +``` + +To revalidate this page, use the destination path: + +```ts +// Correct: use the destination path +revalidatePath('/news') + +// Incorrect: the source path won't match the cache entry +revalidatePath('/blog') +``` + +This is because `revalidatePath` operates on the route file structure, not the URL visible to users. Cache entries are tagged based on which route file renders them. + ## Relationship with `revalidateTag` and `updateTag` `revalidatePath`, [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) and [`updateTag`](/docs/app/api-reference/functions/updateTag) serve different purposes: @@ -99,14 +130,14 @@ This pattern ensures that both the specific page and any other pages using the s ## Examples -### Revalidating a specific URL +### Revalidating a specific path ```ts import { revalidatePath } from 'next/cache' revalidatePath('/blog/post-1') ``` -This will invalidate one specific URL for revalidation on the next page visit. +This will invalidate one specific path for revalidation on the next page visit. ### Revalidating a Page path @@ -117,7 +148,7 @@ revalidatePath('/blog/[slug]', 'page') revalidatePath('/(main)/blog/[slug]', 'page') ``` -This will invalidate any URL that matches the provided `page` file for revalidation on the next page visit. This will _not_ invalidate pages beneath the specific page. For example, `/blog/[slug]` won't invalidate `/blog/[slug]/[author]`. +This will invalidate any path that matches the provided `page` file for revalidation on the next page visit. This will _not_ invalidate pages beneath the specific page. For example, `/blog/[slug]` won't invalidate `/blog/[slug]/[author]`. ### Revalidating a Layout path @@ -128,7 +159,7 @@ revalidatePath('/blog/[slug]', 'layout') revalidatePath('/(main)/post/[slug]', 'layout') ``` -This will invalidate any URL that matches the provided `layout` file for revalidation on the next page visit. This will cause pages beneath with the same layout to be invalidated and revalidated on the next visit. For example, in the above case, `/blog/[slug]/[another]` would also be invalidated and revalidated on the next visit. +This will invalidate any path that matches the provided `layout` file for revalidation on the next page visit. This will cause pages beneath with the same layout to be invalidated and revalidated on the next visit. For example, in the above case, `/blog/[slug]/[another]` would also be invalidated and revalidated on the next visit. ### Revalidating all data From b9ee1db28318f6d5ffa7b8749c1a9cc8dd56f89e Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:15:10 +0100 Subject: [PATCH 5/7] [test] Improve deployment skew test for Pages Router data routes (#89038) Actually test the behavior by switching between two builds, as opposed to selectively blocking some routes. --- test/integration/ssg-data-404/next.config.js | 8 + .../ssg-data-404/test/index.test.ts | 169 ++++++++++++------ 2 files changed, 123 insertions(+), 54 deletions(-) create mode 100644 test/integration/ssg-data-404/next.config.js diff --git a/test/integration/ssg-data-404/next.config.js b/test/integration/ssg-data-404/next.config.js new file mode 100644 index 0000000000000..1664ec4902a54 --- /dev/null +++ b/test/integration/ssg-data-404/next.config.js @@ -0,0 +1,8 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { + distDir: process.env.DIST_DIR && '.next.' + process.env.DIST_DIR, +} + +module.exports = nextConfig diff --git a/test/integration/ssg-data-404/test/index.test.ts b/test/integration/ssg-data-404/test/index.test.ts index 4668107f98eb4..a8deb833fe853 100644 --- a/test/integration/ssg-data-404/test/index.test.ts +++ b/test/integration/ssg-data-404/test/index.test.ts @@ -14,20 +14,17 @@ import { const appDir = join(__dirname, '..') -let app -let appPort -let proxyServer let proxyPort -let should404Data = false -const runTests = () => { - it('should hard navigate when a new deployment occurs', async () => { +const runTests = (switchDeployment: (bool) => void) => { + it('index to gsp', async () => { + switchDeployment(false) const browser = await webdriver(proxyPort, '/') await browser.eval('window.beforeNav = 1') - expect(await browser.elementByCss('#index').text()).toBe('Index page') + await browser.waitForElementByCss('#index') - should404Data = true + switchDeployment(true) await browser.eval(`(function() { window.next.router.push('/gsp') @@ -35,8 +32,17 @@ const runTests = () => { await browser.waitForElementByCss('#gsp') expect(await browser.eval('window.beforeNav')).toBeFalsy() + }) + + it('gsp to gssp', async () => { + switchDeployment(false) + const browser = await webdriver(proxyPort, '/gsp') await browser.eval('window.beforeNav = 1') + await browser.waitForElementByCss('#gsp') + + switchDeployment(true) + await browser.eval(`(function() { window.next.router.push('/gssp') })()`) @@ -46,61 +52,108 @@ const runTests = () => { }) } -describe('SSG data 404', () => { +describe('SSG data 404 - hard navigate when a new deployment occurs', () => { if (process.platform === 'win32') { it('should skip this suite on Windows', () => {}) return } - ;(process.env.TURBOPACK_BUILD ? describe.skip : describe)( - 'development mode', - () => { - beforeAll(async () => { - appPort = await findPort() - app = await launchApp(appDir, appPort) + describe('development mode', () => { + let should404Data = false + let apps = [] + let proxyServer - const proxy = httpProxy.createProxyServer({ - target: `http://localhost:${appPort}`, - }) - proxyPort = await findPort() + beforeAll(async () => { + const appPort = await findPort() + apps.push(await launchApp(appDir, appPort)) - proxyServer = http.createServer((req, res) => { - req.on('error', (e) => { - require('console').error(e) - }) - res.on('error', (e) => { - require('console').error(e) - }) - if (should404Data && req.url.match(/\/_next\/data/)) { - res.statusCode = 404 - return res.end('not found') - } - proxy.web(req, res) - }) + const proxy = httpProxy.createProxyServer({ + target: `http://localhost:${appPort}`, + }) + proxyPort = await findPort() - await new Promise((resolve) => { - proxyServer.listen(proxyPort, () => resolve()) + proxyServer = http.createServer((req, res) => { + req.on('error', (e) => { + require('console').error(e) }) + res.on('error', (e) => { + require('console').error(e) + }) + if (should404Data && req.url.match(/\/_next\/data/)) { + res.statusCode = 404 + return res.end('not found') + } + proxy.web(req, res) }) - afterAll(async () => { - await killApp(app) - proxyServer.close() + + await new Promise((resolve) => { + proxyServer.listen(proxyPort, () => resolve()) }) + }) + afterAll(async () => { + for (const app of apps) { + await killApp(app) + } + proxyServer.close() + }) + + runTests((v) => { + should404Data = v + }) + }) + + describe.each([ + { name: 'with build id' }, + { + name: 'with deployment id', + NEXT_DEPLOYMENT_ID1: 'deployment-id-1', + NEXT_DEPLOYMENT_ID2: 'deployment-id-2', + }, + ])( + 'production mode $name', + ({ NEXT_DEPLOYMENT_ID1, NEXT_DEPLOYMENT_ID2 }) => { + let shouldSwitchDeployment = false + let apps = [] + let proxyServer - runTests() - } - ) - ;(process.env.TURBOPACK_DEV ? describe.skip : describe)( - 'production mode', - () => { beforeAll(async () => { - await nextBuild(appDir) + await nextBuild(appDir, [], { + env: { + DIST_DIR: '1', + NEXT_DEPLOYMENT_ID: NEXT_DEPLOYMENT_ID1, + }, + }) + let appPort1 = await findPort() + apps.push( + await nextStart(appDir, appPort1, { + env: { + DIST_DIR: '1', + NEXT_DEPLOYMENT_ID: NEXT_DEPLOYMENT_ID1, + }, + }) + ) - appPort = await findPort() - app = await nextStart(appDir, appPort) + await nextBuild(appDir, [], { + env: { + DIST_DIR: '2', + NEXT_DEPLOYMENT_ID: NEXT_DEPLOYMENT_ID2, + }, + }) + let appPort2 = await findPort() + apps.push( + await nextStart(appDir, appPort2, { + env: { + DIST_DIR: '2', + NEXT_DEPLOYMENT_ID: NEXT_DEPLOYMENT_ID2, + }, + }) + ) - const proxy = httpProxy.createProxyServer({ - target: `http://localhost:${appPort}`, + const proxy1 = httpProxy.createProxyServer({ + target: `http://localhost:${appPort1}`, + }) + const proxy2 = httpProxy.createProxyServer({ + target: `http://localhost:${appPort2}`, }) proxyPort = await findPort() @@ -111,11 +164,15 @@ describe('SSG data 404', () => { res.on('error', (e) => { require('console').error(e) }) - if (should404Data && req.url.match(/\/_next\/data/)) { - res.statusCode = 404 - return res.end('not found') + + if (shouldSwitchDeployment) { + proxy2.web(req, res, undefined, (e) => { + require('console').error(e) + }) + return } - proxy.web(req, res, undefined, (e) => { + + proxy1.web(req, res, undefined, (e) => { require('console').error(e) }) }) @@ -125,11 +182,15 @@ describe('SSG data 404', () => { }) }) afterAll(async () => { - await killApp(app) + for (const app of apps) { + await killApp(app) + } proxyServer.close() }) - runTests() + runTests((v) => { + shouldSwitchDeployment = v + }) } ) }) From cabe5458a86ccf63fc3134f99041798bb1380261 Mon Sep 17 00:00:00 2001 From: msmx-mnakagawa Date: Mon, 26 Jan 2026 21:23:30 +0900 Subject: [PATCH 6/7] docs: fix typos in `README.md`s (#89022) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > [!NOTE] > This PR only includes documentation updates and has no impact on runtime behavior. ### What? Just fixed typos in docs. ### How? - `direcly` → `directly` - `existance` → `existence` - `overriden` → `overridden` --- packages/next/src/next-devtools/README.md | 2 +- turbopack/crates/turbo-persistence/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/next/src/next-devtools/README.md b/packages/next/src/next-devtools/README.md index b04c77f521c6b..ec6bed8dde5f2 100644 --- a/packages/next/src/next-devtools/README.md +++ b/packages/next/src/next-devtools/README.md @@ -31,7 +31,7 @@ This will start the Storybook server at `http://localhost:6006`. ### Styling -Next.js direcly injects CSS into the DOM via `