diff --git a/bench/module-cost/package.json b/bench/module-cost/package.json index 96814e0e6e875..62bf806540248 100644 --- a/bench/module-cost/package.json +++ b/bench/module-cost/package.json @@ -12,6 +12,6 @@ "devDependencies": { "rimraf": "6.0.1", "next": "workspace:*", - "playwright": "^1.40.0" + "playwright": "1.58.2" } } diff --git a/crates/next-api/src/project.rs b/crates/next-api/src/project.rs index 9d1f2048b5c64..2c18bf4d46e92 100644 --- a/crates/next-api/src/project.rs +++ b/crates/next-api/src/project.rs @@ -368,7 +368,7 @@ pub struct ProjectOptions { /// The version of Next.js that is running. pub next_version: RcStr, - /// Whether server-side HMR is enabled (--experimental-server-fast-refresh). + /// Whether server-side HMR is enabled (disabled with --no-server-fast-refresh). pub server_hmr: bool, } @@ -960,7 +960,7 @@ pub struct Project { /// Whether to enable persistent caching is_persistent_caching_enabled: bool, - /// Whether server-side HMR is enabled (--experimental-server-fast-refresh). + /// Whether server-side HMR is enabled (disabled with --no-server-fast-refresh). server_hmr: bool, } diff --git a/crates/next-core/src/next_import_map.rs b/crates/next-core/src/next_import_map.rs index 573c374c64fae..8dfb95eb91037 100644 --- a/crates/next-core/src/next_import_map.rs +++ b/crates/next-core/src/next_import_map.rs @@ -71,7 +71,16 @@ pub async fn get_next_client_import_map( .await?; match &ty { - ClientContextType::Pages { .. } => {} + ClientContextType::Pages { .. } => { + // Resolve next/error to the ESM entry point so the bundler can + // tree-shake the error-boundary dependency chain from Pages + // Router bundles that only use the default Error component. + insert_exact_alias_or_js( + &mut import_map, + rcstr!("next/error"), + request_to_import_mapping(project_path.clone(), rcstr!("next/dist/api/error")), + ); + } ClientContextType::App { app_dir } => { // Keep in sync with file:///./../../../packages/next/src/lib/needs-experimental-react.ts let taint = *next_config.enable_taint().await?; @@ -404,6 +413,7 @@ pub async fn get_next_edge_import_map( rcstr!("next/app") => rcstr!("next/dist/api/app"), rcstr!("next/document") => rcstr!("next/dist/api/document"), rcstr!("next/dynamic") => rcstr!("next/dist/api/dynamic"), + rcstr!("next/error") => rcstr!("next/dist/api/error"), rcstr!("next/form") => rcstr!("next/dist/api/form"), rcstr!("next/head") => rcstr!("next/dist/api/head"), rcstr!("next/headers") => rcstr!("next/dist/api/headers"), @@ -953,6 +963,7 @@ async fn apply_vendored_react_aliases_server( if react_condition == "server" { // This is used in the server runtime to import React Server Components. alias.extend(fxindexmap! { + rcstr!("next/error") => rcstr!("next/dist/api/error.react-server"), rcstr!("next/navigation") => rcstr!("next/dist/api/navigation.react-server"), rcstr!("next/link") => rcstr!("next/dist/client/app-dir/link.react-server"), }); @@ -983,6 +994,7 @@ async fn rsc_aliases( if ty.should_use_react_server_condition() { // This is used in the server runtime to import React Server Components. alias.extend(fxindexmap! { + rcstr!("next/error") => rcstr!("next/dist/api/error.react-server"), rcstr!("next/navigation") => rcstr!("next/dist/api/navigation.react-server"), rcstr!("next/link") => rcstr!("next/dist/client/app-dir/link.react-server"), }); diff --git a/crates/next-core/src/pages_structure.rs b/crates/next-core/src/pages_structure.rs index 3c05755771fa1..9957f236c8c18 100644 --- a/crates/next-core/src/pages_structure.rs +++ b/crates/next-core/src/pages_structure.rs @@ -298,7 +298,7 @@ async fn get_pages_structure_for_root_directory( PagesStructureItem::new( pages_path.join("_error")?, page_extensions, - Some(next_package.join("error.js")?), + Some(next_package.join("dist/pages/_error.js")?), error_router_path.clone(), error_router_path, ) diff --git a/crates/next-custom-transforms/src/transforms/react_server_components.rs b/crates/next-custom-transforms/src/transforms/react_server_components.rs index 4ee1c2671612e..91ab3478b4977 100644 --- a/crates/next-custom-transforms/src/transforms/react_server_components.rs +++ b/crates/next-custom-transforms/src/transforms/react_server_components.rs @@ -678,6 +678,7 @@ impl ReactServerComponentValidator { "useFormState", ], ), + (atom!("next/error").into(), vec!["unstable_catchError"]), ( atom!("next/navigation").into(), vec![ diff --git a/crates/next-napi-bindings/src/next_api/project.rs b/crates/next-napi-bindings/src/next_api/project.rs index 9fd58ba767e41..809bf16ac1ca1 100644 --- a/crates/next-napi-bindings/src/next_api/project.rs +++ b/crates/next-napi-bindings/src/next_api/project.rs @@ -212,7 +212,7 @@ pub struct NapiProjectOptions { /// The version of Next.js that is running. pub next_version: RcStr, - /// Whether server-side HMR is enabled (--experimental-server-fast-refresh). + /// Whether server-side HMR is enabled (disabled with --no-server-fast-refresh). pub server_hmr: Option, } diff --git a/crates/next-napi-bindings/src/next_api/utils.rs b/crates/next-napi-bindings/src/next_api/utils.rs index 75add6798cceb..dd6f47c8b5bef 100644 --- a/crates/next-napi-bindings/src/next_api/utils.rs +++ b/crates/next-napi-bindings/src/next_api/utils.rs @@ -98,6 +98,12 @@ pub fn root_task_dispose( Ok(()) } +/// [Peeks] at the [`Issue`] held by the given source and returns it as a [`PlainDiagnostic`]. +/// It does not [consume] any [`Issue`]s held by the source. +/// +/// [Peeks]: turbo_tasks::CollectiblesSource::peek_collectibles +/// [`Issue`]: turbopack_core::issue::Issue +/// [consume]: turbo_tasks::CollectiblesSource::take_collectibles pub async fn get_issues( source: OperationVc, filter: Vc, @@ -107,10 +113,11 @@ pub async fn get_issues( )) } -/// Reads the [turbopack_core::diagnostics::Diagnostic] held -/// by the given source and returns it as a -/// [turbopack_core::diagnostics::PlainDiagnostic]. It does -/// not consume any Diagnostics held by the source. +/// [Peeks] at the [`Diagnostic`]s held by the given source and returns it as a [`PlainDiagnostic`]. +/// It does not [consume] any [`Diagnostic`]s held by the source. +/// +/// [Peeks]: turbo_tasks::CollectiblesSource::peek_collectibles +/// [consume]: turbo_tasks::CollectiblesSource::take_collectibles pub async fn get_diagnostics( source: OperationVc, ) -> Result>>> { diff --git a/crates/next-taskless/src/constants.rs b/crates/next-taskless/src/constants.rs index 102ad8b317caa..9df8fb2a291e8 100644 --- a/crates/next-taskless/src/constants.rs +++ b/crates/next-taskless/src/constants.rs @@ -70,10 +70,13 @@ pub const NODE_EXTERNALS: [&str; 68] = [ "zlib", ]; -/// List of node.js internals that are supported by edge runtime. -/// If anything Node.js builtin module apart from these these imports are -/// used and user does not provide alias for the polyfill, a runtime error will be thrown. -/// See https://vercel.com/docs/functions/runtimes/edge-runtime#compatible-node.js-modules +/// The Node.js built-in modules that are supported by edge runtime. +/// +/// If any Node.js builtin module apart from these these imports are used and the user does not +/// provide an alias for it (i.e. a polyfill), a runtime error will be thrown. +/// +/// See +// // SAFETY: This has to be sorted alphabetically since we are doing binary search on it pub const EDGE_NODE_EXTERNALS: [&str; 7] = [ "assert", diff --git a/lerna.json b/lerna.json index 2782b547bf6e5..c459ae6782fbc 100644 --- a/lerna.json +++ b/lerna.json @@ -15,5 +15,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "16.2.0-canary.100" + "version": "16.2.0-canary.101" } \ No newline at end of file diff --git a/package.json b/package.json index 88a07ea296fad..336f6a24e9c0d 100644 --- a/package.json +++ b/package.json @@ -255,8 +255,8 @@ "octokit": "3.1.0", "outdent": "0.8.0", "pixrem": "5.0.0", - "playwright": "1.48.0", - "playwright-chromium": "1.48.0", + "playwright": "1.58.2", + "playwright-chromium": "1.58.2", "postcss": "8.4.31", "postcss-nested": "4.2.1", "postcss-pseudoelements": "5.0.0", diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index d19d4c4454fe9..8a62eed4e619c 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index eadc89fa4e5c0..d104f53e1b8ba 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "description": "ESLint configuration used by Next.js.", "license": "MIT", "repository": { @@ -12,7 +12,7 @@ "dist" ], "dependencies": { - "@next/eslint-plugin-next": "16.2.0-canary.100", + "@next/eslint-plugin-next": "16.2.0-canary.101", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index 47adb3ce1d9fc..752847d480b84 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,7 +1,7 @@ { "name": "@next/eslint-plugin-internal", "private": true, - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "description": "ESLint plugin for working on Next.js.", "exports": { ".": "./src/eslint-plugin-internal.js" diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index bf120e204cc25..72a86eb161729 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "description": "ESLint plugin for Next.js.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/font/package.json b/packages/font/package.json index 01e51898bb234..905088ba35aab 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,7 +1,7 @@ { "name": "@next/font", "private": true, - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "repository": { "url": "vercel/next.js", "directory": "packages/font" diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index 0732fe54ad072..99331e926f2c0 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 3be2bf4fdc8b0..41c6863d78f88 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 11b62b0d471f4..b768fec592615 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 716333f13109b..9642e6ae23829 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-playwright/package.json b/packages/next-playwright/package.json index d49854095f84c..0f82a72aadfb4 100644 --- a/packages/next-playwright/package.json +++ b/packages/next-playwright/package.json @@ -1,6 +1,6 @@ { "name": "@next/playwright", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "repository": { "url": "vercel/next.js", "directory": "packages/next-playwright" @@ -26,6 +26,7 @@ } }, "devDependencies": { + "@playwright/test": "1.58.2", "typescript": "5.9.2" } } diff --git a/packages/next-playwright/src/index.ts b/packages/next-playwright/src/index.ts index 9c568ab7438dc..da5bf342892d5 100644 --- a/packages/next-playwright/src/index.ts +++ b/packages/next-playwright/src/index.ts @@ -74,11 +74,14 @@ export async function instant( // navigation-testing-lock.ts, which acquires the in-memory navigation lock. const { hostname } = new URL(resolveURL(page, options)) await step('Acquire Instant Lock', () => - page - .context() - .addCookies([ - { name: INSTANT_COOKIE, value: '[0]', domain: hostname, path: '/' }, - ]) + page.context().addCookies([ + { + name: INSTANT_COOKIE, + value: JSON.stringify([0, `p${Math.random()}`]), + domain: hostname, + path: '/', + }, + ]) ) try { return await fn() diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 1e873d269a85b..07696cf566df2 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index 00fe35514a736..1fd79bdf47357 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index 4c511bbd4dc97..073530b7712d3 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-routing/package.json b/packages/next-routing/package.json index 0876c55f4e4eb..919dbf5d2a130 100644 --- a/packages/next-routing/package.json +++ b/packages/next-routing/package.json @@ -1,6 +1,6 @@ { "name": "@next/routing", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "keywords": [ "react", "next", diff --git a/packages/next-rspack/package.json b/packages/next-rspack/package.json index 035c41438c984..84a6cf97d1570 100644 --- a/packages/next-rspack/package.json +++ b/packages/next-rspack/package.json @@ -1,6 +1,6 @@ { "name": "next-rspack", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "repository": { "url": "vercel/next.js", "directory": "packages/next-rspack" diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 182cba9c2780b..e7cb81f99011e 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "private": true, "files": [ "native/" @@ -16,8 +16,9 @@ "build-native-wasi": "npx --package=@napi-rs/cli@3.0.0-alpha.45 napi build --platform --target wasm32-wasip1-threads -p next-napi-bindings --cwd ../../ --output-dir packages/next-swc/native --no-default-features", "build-wasm": "wasm-pack build ../../crates/wasm --scope=next", "cache-build-native": "[ -d native ] && echo $(ls native)", - "rust-check-fmt": "cd ../..; cargo fmt -- --check", "rust-check-clippy": "cargo clippy --workspace --all-targets -- -D warnings -A deprecated", + "rust-check-doc": "RUSTDOCFLAGS='-Zunstable-options --output-format=json' cargo doc --no-deps --workspace", + "rust-check-fmt": "cd ../..; cargo fmt -- --check", "rust-check-napi": "cargo check -p next-napi-bindings", "test-cargo-unit": "cargo nextest run --workspace --exclude next-napi-bindings --exclude turbo-tasks-macros --cargo-profile release-with-assertions-no-lto --no-fail-fast" }, diff --git a/packages/next-swc/turbo.jsonc b/packages/next-swc/turbo.jsonc index 546655503ea78..36fced8ad2368 100644 --- a/packages/next-swc/turbo.jsonc +++ b/packages/next-swc/turbo.jsonc @@ -136,9 +136,14 @@ "outputs": ["native/*.node", "native/index.d.ts"], }, "rust-check": { - "dependsOn": ["rust-check-fmt", "rust-check-clippy", "rust-check-napi"], + "dependsOn": [ + "rust-check-clippy", + "rust-check-doc", + "rust-check-fmt", + "rust-check-napi", + ], }, - "rust-check-fmt": { + "rust-check-clippy": { "inputs": [ "../../.cargo/**", "../../crates/**", @@ -148,9 +153,8 @@ "../../.github/workflows/build_and_deploy.yml", "../../rust-toolchain.toml", ], - "cache": false, }, - "rust-check-clippy": { + "rust-check-doc": { "inputs": [ "../../.cargo/**", "../../crates/**", @@ -161,6 +165,18 @@ "../../rust-toolchain.toml", ], }, + "rust-check-fmt": { + "inputs": [ + "../../.cargo/**", + "../../crates/**", + "../../turbopack/crates/**", + "../../**/Cargo.toml", + "../../**/Cargo.lock", + "../../.github/workflows/build_and_deploy.yml", + "../../rust-toolchain.toml", + ], + "cache": false, + }, "rust-check-napi": { "inputs": [ "../../.cargo/**", diff --git a/packages/next/error.d.ts b/packages/next/error.d.ts index 100590d637d4d..1c621105395a4 100644 --- a/packages/next/error.d.ts +++ b/packages/next/error.d.ts @@ -1,3 +1,3 @@ -import Error from './dist/pages/_error' -export * from './dist/pages/_error' +import Error from './dist/api/error' +export * from './dist/api/error' export default Error diff --git a/packages/next/error.js b/packages/next/error.js index 899cd04662710..63681f93e0b5a 100644 --- a/packages/next/error.js +++ b/packages/next/error.js @@ -1 +1,9 @@ module.exports = require('./dist/pages/_error') + +// Keep the catch-error graph lazy so default Error consumers do not load it. +Object.defineProperty(module.exports, 'unstable_catchError', { + enumerable: true, + get() { + return require('./dist/client/components/catch-error').unstable_catchError + }, +}) diff --git a/packages/next/errors.json b/packages/next/errors.json index 0d5837e008f04..71bf011edbd97 100644 --- a/packages/next/errors.json +++ b/packages/next/errors.json @@ -1133,5 +1133,9 @@ "1132": "Route %s used \\`draftMode()\\` inside \\`generateStaticParams\\`. This is not supported because \\`generateStaticParams\\` runs at build time without an HTTP request. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context", "1133": "createSearchParamsFromClient should not be called inside generateStaticParams.", "1134": "Route %s used \\`headers()\\` inside \\`generateStaticParams\\`. This is not supported because \\`generateStaticParams\\` runs at build time without an HTTP request. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context", - "1135": "\"use cache\" cannot be used outside of App Router. Expected a WorkUnitStore." + "1135": "\"use cache\" cannot be used outside of App Router. Expected a WorkUnitStore.", + "1136": "Page \"%s\" cannot use both \\`export const unstable_dynamicStaleTime\\` and \\`export const unstable_instant\\`.", + "1137": "\"%s\" cannot use \\`export const unstable_dynamicStaleTime\\`. This config is only supported in page files, not layouts.", + "1138": "`unstable_retry()` can only be used in the App Router. Use `reset()` in the Pages Router.", + "1139": "`unstable_catchError` can only be used in Client Components." } diff --git a/packages/next/package.json b/packages/next/package.json index b40a8bb539f0d..a37c642f87e65 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "16.2.0-canary.100", + "version": "16.2.0-canary.101", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -97,7 +97,7 @@ ] }, "dependencies": { - "@next/env": "16.2.0-canary.100", + "@next/env": "16.2.0-canary.101", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -161,13 +161,13 @@ "@modelcontextprotocol/sdk": "1.18.1", "@mswjs/interceptors": "0.23.0", "@napi-rs/triples": "1.2.0", - "@next/font": "16.2.0-canary.100", - "@next/polyfill-module": "16.2.0-canary.100", - "@next/polyfill-nomodule": "16.2.0-canary.100", - "@next/react-refresh-utils": "16.2.0-canary.100", - "@next/swc": "16.2.0-canary.100", + "@next/font": "16.2.0-canary.101", + "@next/polyfill-module": "16.2.0-canary.101", + "@next/polyfill-nomodule": "16.2.0-canary.101", + "@next/react-refresh-utils": "16.2.0-canary.101", + "@next/swc": "16.2.0-canary.101", "@opentelemetry/api": "1.6.0", - "@playwright/test": "1.51.1", + "@playwright/test": "1.58.2", "@rspack/core": "1.6.7", "@storybook/addon-a11y": "8.6.0", "@storybook/addon-essentials": "8.6.0", diff --git a/packages/next/src/api/error.react-server.ts b/packages/next/src/api/error.react-server.ts new file mode 100644 index 0000000000000..333f4671f8420 --- /dev/null +++ b/packages/next/src/api/error.react-server.ts @@ -0,0 +1,7 @@ +export function unstable_catchError(): never { + throw new Error( + '`unstable_catchError` can only be used in Client Components.' + ) +} + +export type { ErrorInfo } from '../client/components/error-boundary' diff --git a/packages/next/src/api/error.ts b/packages/next/src/api/error.ts new file mode 100644 index 0000000000000..acd4f8a763669 --- /dev/null +++ b/packages/next/src/api/error.ts @@ -0,0 +1,6 @@ +// Pages Router only +export { default } from '../pages/_error' +export * from '../pages/_error' + +export { unstable_catchError } from '../client/components/catch-error' +export type { ErrorInfo } from '../client/components/error-boundary' diff --git a/packages/next/src/bin/next.ts b/packages/next/src/bin/next.ts index f21e69ea27581..d08775192b38d 100755 --- a/packages/next/src/bin/next.ts +++ b/packages/next/src/bin/next.ts @@ -313,10 +313,7 @@ program '--experimental-https-ca, ', 'Path to a HTTPS certificate authority file.' ) - .option( - '--experimental-server-fast-refresh', - 'Enable experimental server-side Fast Refresh.' - ) + .option('--no-server-fast-refresh', 'Disable server-side Fast Refresh') .option( '--experimental-upload-trace, ', 'Reports a subset of the debugging trace to a remote HTTP URL. Includes sensitive data.' diff --git a/packages/next/src/build/analysis/get-page-static-info.ts b/packages/next/src/build/analysis/get-page-static-info.ts index d86e0e6510d7c..67b1c9387b942 100644 --- a/packages/next/src/build/analysis/get-page-static-info.ts +++ b/packages/next/src/build/analysis/get-page-static-info.ts @@ -691,6 +691,23 @@ export async function getAppPageStaticInfo({ ) } + // Prevent unstable_dynamicStaleTime in layouts. + if ('unstable_dynamicStaleTime' in config) { + const isLayout = /\/layout\.[^/]+$/.test(pageFilePath) + if (isLayout) { + throw new Error( + `"${page}" cannot use \`export const unstable_dynamicStaleTime\`. This config is only supported in page files, not layouts.` + ) + } + } + + // Prevent combining unstable_dynamicStaleTime and unstable_instant. + if ('unstable_dynamicStaleTime' in config && 'unstable_instant' in config) { + throw new Error( + `Page "${page}" cannot use both \`export const unstable_dynamicStaleTime\` and \`export const unstable_instant\`.` + ) + } + return { type: PAGE_TYPES.APP, rsc, diff --git a/packages/next/src/build/create-compiler-aliases.ts b/packages/next/src/build/create-compiler-aliases.ts index acb8fc59c5741..a258469fc5f79 100644 --- a/packages/next/src/build/create-compiler-aliases.ts +++ b/packages/next/src/build/create-compiler-aliases.ts @@ -202,6 +202,7 @@ export function createServerOnlyClientOnlyAliases( export function createNextApiEsmAliases() { const mapping = { + error: 'next/dist/api/error', head: 'next/dist/api/head', image: 'next/dist/api/image', constants: 'next/dist/api/constants', @@ -237,6 +238,7 @@ export function createAppRouterApiAliases(isServerOnlyLayer: boolean) { } if (isServerOnlyLayer) { + mapping['error'] = 'next/dist/api/error.react-server' mapping['navigation'] = 'next/dist/api/navigation.react-server' mapping['link'] = 'next/dist/client/app-dir/link.react-server' } diff --git a/packages/next/src/build/segment-config/app/app-segment-config.ts b/packages/next/src/build/segment-config/app/app-segment-config.ts index 84169de8961ee..6f8dde47af09d 100644 --- a/packages/next/src/build/segment-config/app/app-segment-config.ts +++ b/packages/next/src/build/segment-config/app/app-segment-config.ts @@ -145,6 +145,13 @@ const AppSegmentConfigSchema = z.object({ */ unstable_instant: InstantConfigSchema.optional(), + /** + * The stale time for dynamic responses in seconds. + * Controls how long the client-side router cache retains dynamic page data. + * Pages only — not allowed in layouts. + */ + unstable_dynamicStaleTime: z.number().int().nonnegative().optional(), + /** * The preferred region for the page. */ @@ -188,6 +195,11 @@ export function parseAppSegmentConfig( message: `Invalid unstable_instant value ${JSON.stringify(ctx.data)} on "${route}", must be an object with \`prefetch: "static"\` or \`prefetch: "runtime"\`, or \`false\`. Read more at https://nextjs.org/docs/messages/invalid-instant-configuration`, } } + case 'unstable_dynamicStaleTime': { + return { + message: `Invalid unstable_dynamicStaleTime value ${JSON.stringify(ctx.data)} on "${route}", must be a non-negative number`, + } + } default: } } @@ -242,6 +254,13 @@ export type AppSegmentConfig = { */ unstable_instant?: Instant + /** + * The stale time for dynamic responses in seconds. + * Controls how long the client-side router cache retains dynamic page data. + * Pages only — not allowed in layouts. + */ + unstable_dynamicStaleTime?: number + /** * The preferred region for the page. */ diff --git a/packages/next/src/build/swc/generated-native.d.ts b/packages/next/src/build/swc/generated-native.d.ts index 4ae6b55464ef8..006e7cbb987c3 100644 --- a/packages/next/src/build/swc/generated-native.d.ts +++ b/packages/next/src/build/swc/generated-native.d.ts @@ -256,7 +256,7 @@ export interface NapiProjectOptions { isPersistentCachingEnabled: boolean /** The version of Next.js that is running. */ nextVersion: RcStr - /** Whether server-side HMR is enabled (--experimental-server-fast-refresh). */ + /** Whether server-side HMR is enabled (disabled with --no-server-fast-refresh). */ serverHmr?: boolean } /** [NapiProjectOptions] with all fields optional. */ diff --git a/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts b/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts index 437a54038e24b..66a8f15a3aab8 100644 --- a/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts +++ b/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts @@ -72,6 +72,7 @@ checkFields | false dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static' dynamicParams?: boolean diff --git a/packages/next/src/cli/next-dev.ts b/packages/next/src/cli/next-dev.ts index 8178d20c736b6..b268e7af60e8d 100644 --- a/packages/next/src/cli/next-dev.ts +++ b/packages/next/src/cli/next-dev.ts @@ -55,7 +55,7 @@ export type NextDevOptions = { experimentalUploadTrace?: string experimentalNextConfigStripTypes?: boolean experimentalCpuProf?: boolean - experimentalServerFastRefresh?: boolean + serverFastRefresh?: boolean } type PortSource = 'cli' | 'default' | 'env' @@ -249,7 +249,7 @@ const nextDev = async ( const enabledFeatures = Object.fromEntries( Object.entries({ - experimentalServerFastRefresh: options.experimentalServerFastRefresh, + serverFastRefreshDisabled: options.serverFastRefresh === false, experimentalCpuProf: options.experimentalCpuProf, }).filter(([_, value]) => value) ) @@ -269,7 +269,7 @@ const nextDev = async ( allowRetry, isDev: true, hostname: host, - experimentalServerFastRefresh: options.experimentalServerFastRefresh, + serverFastRefresh: options.serverFastRefresh, } const startServerPath = require.resolve('../server/lib/start-server') diff --git a/packages/next/src/client/components/builtin/global-error.tsx b/packages/next/src/client/components/builtin/global-error.tsx index 652ccab051748..df806a89f3c3a 100644 --- a/packages/next/src/client/components/builtin/global-error.tsx +++ b/packages/next/src/client/components/builtin/global-error.tsx @@ -1,7 +1,7 @@ 'use client' import React from 'react' -import { HandleISRError } from '../handle-isr-error' +import { handleISRError } from '../handle-isr-error' import { errorStyles, errorThemeCss, WarningIcon } from './error-styles' export type GlobalErrorComponent = React.ComponentType<{ @@ -18,13 +18,14 @@ function DefaultGlobalError({ error }: { error: any }) { ? 'A server error occurred. Reload to try again.' : 'Reload to try again, or go back.' + handleISRError({ error }) + return (