mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
refactor: rename experimental.dynamicIO to experimental.cacheComponents (#81562)
## What? Rename `experimental.dynamicIO` to `experimental.cacheComponents` across the Next.js codebase. ## Why? We're going to be merging the functionality of the `ppr`, `dynamicIO` and `useCache` experimental flags into the singular `cacheComponents` flag to reduce complexity of the codebase and simplify adoption for users wanting to experiment with experimental features. ## How? - Renamed the configuration option from `experimental.dynamicIO` to `experimental.cacheComponents` - Added deprecation handling with automatic migration for the old option name - Updated all documentation, tests, and internal references - Updated Rust code in SWC transforms and Turbopack - Maintained backward compatibility with deprecation warnings NAR-158
This commit is contained in:
@@ -2,4 +2,4 @@
|
||||
"next": patch
|
||||
---
|
||||
|
||||
Enable `ppr` when `dynamicIO` is enabled
|
||||
Enable `ppr` when `cacheComponents` is enabled
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
"next": patch
|
||||
---
|
||||
|
||||
[dynamicIO] Avoid timeout errors with dynamic params in `"use cache"`
|
||||
[cacheComponents] Avoid timeout errors with dynamic params in `"use cache"`
|
||||
|
||||
@@ -85,7 +85,7 @@ body:
|
||||
- 'create-next-app'
|
||||
- 'CSS'
|
||||
- 'Draft Mode'
|
||||
- 'dynamicIO'
|
||||
- 'cacheComponents'
|
||||
- 'Dynamic Routes'
|
||||
- 'Error Handling'
|
||||
- 'Error Overlay'
|
||||
|
||||
@@ -727,8 +727,7 @@ pub struct ExperimentalConfig {
|
||||
server_actions: Option<ServerActionsOrLegacyBool>,
|
||||
sri: Option<SubResourceIntegrity>,
|
||||
react_compiler: Option<ReactCompilerOptionsOrBoolean>,
|
||||
#[serde(rename = "dynamicIO")]
|
||||
dynamic_io: Option<bool>,
|
||||
cache_components: Option<bool>,
|
||||
use_cache: Option<bool>,
|
||||
// ---
|
||||
// UNSUPPORTED
|
||||
@@ -1583,8 +1582,8 @@ impl NextConfig {
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub fn enable_dynamic_io(&self) -> Vc<bool> {
|
||||
Vc::cell(self.experimental.dynamic_io.unwrap_or(false))
|
||||
pub fn enable_cache_components(&self) -> Vc<bool> {
|
||||
Vc::cell(self.experimental.cache_components.unwrap_or(false))
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
@@ -1593,9 +1592,9 @@ impl NextConfig {
|
||||
self.experimental
|
||||
.use_cache
|
||||
// "use cache" was originally implicitly enabled with the
|
||||
// dynamicIO flag, so we transfer the value for dynamicIO to the
|
||||
// cacheComponents flag, so we transfer the value for cacheComponents to the
|
||||
// explicit useCache flag to ensure backwards compatibility.
|
||||
.unwrap_or(self.experimental.dynamic_io.unwrap_or(false)),
|
||||
.unwrap_or(self.experimental.cache_components.unwrap_or(false)),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -179,10 +179,10 @@ pub async fn get_next_server_transforms_rules(
|
||||
};
|
||||
|
||||
if is_app_dir &&
|
||||
// `dynamicIO` is not supported in the edge runtime.
|
||||
// `cacheComponents` is not supported in the edge runtime.
|
||||
// (also, the code generated by the dynamic imports transform relies on `CacheSignal`, which uses nodejs-specific APIs)
|
||||
next_runtime != NextRuntime::Edge &&
|
||||
*next_config.enable_dynamic_io().await?
|
||||
*next_config.enable_cache_components().await?
|
||||
{
|
||||
rules.push(get_next_track_dynamic_imports_transform_rule(mdx_rs));
|
||||
}
|
||||
|
||||
@@ -34,12 +34,12 @@ pub async fn get_next_react_server_components_transform_rule(
|
||||
app_dir: Option<FileSystemPath>,
|
||||
) -> Result<ModuleRule> {
|
||||
let enable_mdx_rs = next_config.mdx_rs().await?.is_some();
|
||||
let dynamic_io_enabled = *next_config.enable_dynamic_io().await?;
|
||||
let cache_components_enabled = *next_config.enable_cache_components().await?;
|
||||
let use_cache_enabled = *next_config.enable_use_cache().await?;
|
||||
Ok(get_ecma_transform_rule(
|
||||
Box::new(NextJsReactServerComponents::new(
|
||||
is_react_server_layer,
|
||||
dynamic_io_enabled,
|
||||
cache_components_enabled,
|
||||
use_cache_enabled,
|
||||
app_dir,
|
||||
)),
|
||||
@@ -51,7 +51,7 @@ pub async fn get_next_react_server_components_transform_rule(
|
||||
#[derive(Debug)]
|
||||
struct NextJsReactServerComponents {
|
||||
is_react_server_layer: bool,
|
||||
dynamic_io_enabled: bool,
|
||||
cache_components_enabled: bool,
|
||||
use_cache_enabled: bool,
|
||||
app_dir: Option<FileSystemPath>,
|
||||
}
|
||||
@@ -59,13 +59,13 @@ struct NextJsReactServerComponents {
|
||||
impl NextJsReactServerComponents {
|
||||
fn new(
|
||||
is_react_server_layer: bool,
|
||||
dynamic_io_enabled: bool,
|
||||
cache_components_enabled: bool,
|
||||
use_cache_enabled: bool,
|
||||
app_dir: Option<FileSystemPath>,
|
||||
) -> Self {
|
||||
Self {
|
||||
is_react_server_layer,
|
||||
dynamic_io_enabled,
|
||||
cache_components_enabled,
|
||||
use_cache_enabled,
|
||||
app_dir,
|
||||
}
|
||||
@@ -86,7 +86,7 @@ impl CustomTransformer for NextJsReactServerComponents {
|
||||
file_name,
|
||||
Config::WithOptions(Options {
|
||||
is_react_server_layer: self.is_react_server_layer,
|
||||
dynamic_io_enabled: self.dynamic_io_enabled,
|
||||
cache_components_enabled: self.cache_components_enabled,
|
||||
use_cache_enabled: self.use_cache_enabled,
|
||||
}),
|
||||
self.app_dir.as_ref().map(|path| path.path.clone().into()),
|
||||
|
||||
@@ -51,7 +51,7 @@ impl Config {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Options {
|
||||
pub is_react_server_layer: bool,
|
||||
pub dynamic_io_enabled: bool,
|
||||
pub cache_components_enabled: bool,
|
||||
pub use_cache_enabled: bool,
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ pub struct Options {
|
||||
/// same purpose, so does not run this transform.
|
||||
struct ReactServerComponents<C: Comments> {
|
||||
is_react_server_layer: bool,
|
||||
dynamic_io_enabled: bool,
|
||||
cache_components_enabled: bool,
|
||||
use_cache_enabled: bool,
|
||||
filepath: String,
|
||||
app_dir: Option<PathBuf>,
|
||||
@@ -94,14 +94,14 @@ enum RSCErrorKind {
|
||||
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
enum NextConfigProperty {
|
||||
DynamicIo,
|
||||
CacheComponents,
|
||||
UseCache,
|
||||
}
|
||||
|
||||
impl Display for NextConfigProperty {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
NextConfigProperty::DynamicIo => write!(f, "experimental.dynamicIO"),
|
||||
NextConfigProperty::CacheComponents => write!(f, "experimental.cacheComponents"),
|
||||
NextConfigProperty::UseCache => write!(f, "experimental.useCache"),
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,7 @@ impl<C: Comments> VisitMut for ReactServerComponents<C> {
|
||||
// Run the validator first to assert, collect directives and imports.
|
||||
let mut validator = ReactServerComponentValidator::new(
|
||||
self.is_react_server_layer,
|
||||
self.dynamic_io_enabled,
|
||||
self.cache_components_enabled,
|
||||
self.use_cache_enabled,
|
||||
self.filepath.clone(),
|
||||
self.app_dir.clone(),
|
||||
@@ -547,7 +547,7 @@ fn collect_top_level_directives_and_imports(
|
||||
/// A visitor to assert given module file is a valid React server component.
|
||||
struct ReactServerComponentValidator {
|
||||
is_react_server_layer: bool,
|
||||
dynamic_io_enabled: bool,
|
||||
cache_components_enabled: bool,
|
||||
use_cache_enabled: bool,
|
||||
filepath: String,
|
||||
app_dir: Option<PathBuf>,
|
||||
@@ -566,14 +566,14 @@ type RcVec<T> = Rc<Vec<T>>;
|
||||
impl ReactServerComponentValidator {
|
||||
pub fn new(
|
||||
is_react_server_layer: bool,
|
||||
dynamic_io_enabled: bool,
|
||||
cache_components_enabled: bool,
|
||||
use_cache_enabled: bool,
|
||||
filename: String,
|
||||
app_dir: Option<PathBuf>,
|
||||
) -> Self {
|
||||
Self {
|
||||
is_react_server_layer,
|
||||
dynamic_io_enabled,
|
||||
cache_components_enabled,
|
||||
use_cache_enabled,
|
||||
filepath: filename,
|
||||
app_dir,
|
||||
@@ -818,12 +818,12 @@ impl ReactServerComponentValidator {
|
||||
.insert(export_name.clone(), (InvalidExportKind::Metadata, *span));
|
||||
}
|
||||
"runtime" => {
|
||||
if self.dynamic_io_enabled {
|
||||
if self.cache_components_enabled {
|
||||
possibly_invalid_exports.insert(
|
||||
export_name.clone(),
|
||||
(
|
||||
InvalidExportKind::RouteSegmentConfig(
|
||||
NextConfigProperty::DynamicIo,
|
||||
NextConfigProperty::CacheComponents,
|
||||
),
|
||||
*span,
|
||||
),
|
||||
@@ -841,12 +841,12 @@ impl ReactServerComponentValidator {
|
||||
}
|
||||
}
|
||||
"dynamicParams" | "dynamic" | "fetchCache" | "revalidate" => {
|
||||
if self.dynamic_io_enabled {
|
||||
if self.cache_components_enabled {
|
||||
possibly_invalid_exports.insert(
|
||||
export_name.clone(),
|
||||
(
|
||||
InvalidExportKind::RouteSegmentConfig(
|
||||
NextConfigProperty::DynamicIo,
|
||||
NextConfigProperty::CacheComponents,
|
||||
),
|
||||
*span,
|
||||
),
|
||||
@@ -1058,8 +1058,8 @@ pub fn server_components_assert(
|
||||
Config::WithOptions(x) => x.is_react_server_layer,
|
||||
_ => false,
|
||||
};
|
||||
let dynamic_io_enabled: bool = match &config {
|
||||
Config::WithOptions(x) => x.dynamic_io_enabled,
|
||||
let cache_components_enabled: bool = match &config {
|
||||
Config::WithOptions(x) => x.cache_components_enabled,
|
||||
_ => false,
|
||||
};
|
||||
let use_cache_enabled: bool = match &config {
|
||||
@@ -1072,7 +1072,7 @@ pub fn server_components_assert(
|
||||
};
|
||||
ReactServerComponentValidator::new(
|
||||
is_react_server_layer,
|
||||
dynamic_io_enabled,
|
||||
cache_components_enabled,
|
||||
use_cache_enabled,
|
||||
filename,
|
||||
app_dir,
|
||||
@@ -1091,8 +1091,8 @@ pub fn server_components<C: Comments>(
|
||||
Config::WithOptions(x) => x.is_react_server_layer,
|
||||
_ => false,
|
||||
};
|
||||
let dynamic_io_enabled: bool = match &config {
|
||||
Config::WithOptions(x) => x.dynamic_io_enabled,
|
||||
let cache_components_enabled: bool = match &config {
|
||||
Config::WithOptions(x) => x.cache_components_enabled,
|
||||
_ => false,
|
||||
};
|
||||
let use_cache_enabled: bool = match &config {
|
||||
@@ -1101,7 +1101,7 @@ pub fn server_components<C: Comments>(
|
||||
};
|
||||
visit_mut_pass(ReactServerComponents {
|
||||
is_react_server_layer,
|
||||
dynamic_io_enabled,
|
||||
cache_components_enabled,
|
||||
use_cache_enabled,
|
||||
comments,
|
||||
filepath: match &*filename {
|
||||
|
||||
@@ -91,7 +91,7 @@ fn next_ssg_errors(input: PathBuf) {
|
||||
fn react_server_components_errors(input: PathBuf) {
|
||||
use next_custom_transforms::transforms::react_server_components::{Config, Options};
|
||||
let is_react_server_layer = input.iter().any(|s| s.to_str() == Some("server-graph"));
|
||||
let dynamic_io_enabled = input.iter().any(|s| s.to_str() == Some("dynamic-io"));
|
||||
let cache_components_enabled = input.iter().any(|s| s.to_str() == Some("cache-components"));
|
||||
let use_cache_enabled = input.iter().any(|s| s.to_str() == Some("use-cache"));
|
||||
let output = input.parent().unwrap().join("output.js");
|
||||
test_fixture(
|
||||
@@ -101,7 +101,7 @@ fn react_server_components_errors(input: PathBuf) {
|
||||
FileName::Real(PathBuf::from("/some-project/src/page.js")).into(),
|
||||
Config::WithOptions(Options {
|
||||
is_react_server_layer,
|
||||
dynamic_io_enabled,
|
||||
cache_components_enabled,
|
||||
use_cache_enabled,
|
||||
}),
|
||||
tr.comments.as_ref().clone(),
|
||||
@@ -153,7 +153,7 @@ fn react_server_actions_errors(input: PathBuf) {
|
||||
FileName::Real(PathBuf::from("/app/item.js")).into(),
|
||||
Config::WithOptions(Options {
|
||||
is_react_server_layer,
|
||||
dynamic_io_enabled: true,
|
||||
cache_components_enabled: true,
|
||||
use_cache_enabled: true,
|
||||
}),
|
||||
tr.comments.as_ref().clone(),
|
||||
@@ -217,7 +217,7 @@ fn use_cache_not_allowed(input: PathBuf) {
|
||||
FileName::Real(PathBuf::from("/app/item.js")).into(),
|
||||
Config::WithOptions(Options {
|
||||
is_react_server_layer: true,
|
||||
dynamic_io_enabled: false,
|
||||
cache_components_enabled: false,
|
||||
use_cache_enabled: false,
|
||||
}),
|
||||
tr.comments.as_ref().clone(),
|
||||
|
||||
+5
-5
@@ -1,31 +1,31 @@
|
||||
x Route segment config "runtime" is not compatible with `nextConfig.experimental.dynamicIO`. Please remove it.
|
||||
x Route segment config "runtime" is not compatible with `nextConfig.experimental.cacheComponents`. Please remove it.
|
||||
,-[input.js:1:1]
|
||||
1 | export const runtime = 'edge'
|
||||
: ^^^^^^^
|
||||
2 | export const dynamic = 'force-dynamic'
|
||||
`----
|
||||
x Route segment config "dynamic" is not compatible with `nextConfig.experimental.dynamicIO`. Please remove it.
|
||||
x Route segment config "dynamic" is not compatible with `nextConfig.experimental.cacheComponents`. Please remove it.
|
||||
,-[input.js:2:1]
|
||||
1 | export const runtime = 'edge'
|
||||
2 | export const dynamic = 'force-dynamic'
|
||||
: ^^^^^^^
|
||||
3 | export const dynamicParams = false
|
||||
`----
|
||||
x Route segment config "dynamicParams" is not compatible with `nextConfig.experimental.dynamicIO`. Please remove it.
|
||||
x Route segment config "dynamicParams" is not compatible with `nextConfig.experimental.cacheComponents`. Please remove it.
|
||||
,-[input.js:3:1]
|
||||
2 | export const dynamic = 'force-dynamic'
|
||||
3 | export const dynamicParams = false
|
||||
: ^^^^^^^^^^^^^
|
||||
4 | export const fetchCache = 'force-no-store'
|
||||
`----
|
||||
x Route segment config "fetchCache" is not compatible with `nextConfig.experimental.dynamicIO`. Please remove it.
|
||||
x Route segment config "fetchCache" is not compatible with `nextConfig.experimental.cacheComponents`. Please remove it.
|
||||
,-[input.js:4:1]
|
||||
3 | export const dynamicParams = false
|
||||
4 | export const fetchCache = 'force-no-store'
|
||||
: ^^^^^^^^^^
|
||||
5 | export const revalidate = 1
|
||||
`----
|
||||
x Route segment config "revalidate" is not compatible with `nextConfig.experimental.dynamicIO`. Please remove it.
|
||||
x Route segment config "revalidate" is not compatible with `nextConfig.experimental.cacheComponents`. Please remove it.
|
||||
,-[input.js:5:1]
|
||||
4 | export const fetchCache = 'force-no-store'
|
||||
5 | export const revalidate = 1
|
||||
@@ -477,7 +477,7 @@ fn react_server_components_typescript(input: PathBuf) {
|
||||
FileName::Real(PathBuf::from("/some-project/src/some-file.js")).into(),
|
||||
Config::WithOptions(Options {
|
||||
is_react_server_layer: true,
|
||||
dynamic_io_enabled: false,
|
||||
cache_components_enabled: false,
|
||||
use_cache_enabled: false,
|
||||
}),
|
||||
tr.comments.as_ref().clone(),
|
||||
@@ -505,7 +505,7 @@ fn react_server_components_fixture(input: PathBuf) {
|
||||
FileName::Real(PathBuf::from("/some-project/src/some-file.js")).into(),
|
||||
Config::WithOptions(Options {
|
||||
is_react_server_layer,
|
||||
dynamic_io_enabled: false,
|
||||
cache_components_enabled: false,
|
||||
use_cache_enabled: false,
|
||||
}),
|
||||
tr.comments.as_ref().clone(),
|
||||
|
||||
@@ -271,7 +271,7 @@ export const getPost = cache(async (id) => {
|
||||
|
||||
## Streaming
|
||||
|
||||
> **Warning:** The content below assumes the [`dynamicIO` config option](/docs/app/api-reference/config/next-config-js/dynamicIO) is enabled in your application. The flag was introduced in Next.js 15 canary.
|
||||
> **Warning:** The content below assumes the [`cacheComponents` config option](/docs/app/api-reference/config/next-config-js/cacheComponents) is enabled in your application. The flag was introduced in Next.js 15 canary.
|
||||
|
||||
When using `async/await` in Server Components, Next.js will opt into [dynamic rendering](/docs/app/getting-started/partial-prerendering#dynamic-rendering). This means the data will be fetched and rendered on the server for every user request. If there are any slow data requests, the whole route will be blocked from rendering.
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ The following features are currently available in canary:
|
||||
- [`"use cache"`](/docs/app/api-reference/directives/use-cache)
|
||||
- [`cacheLife`](/docs/app/api-reference/functions/cacheLife)
|
||||
- [`cacheTag`](/docs/app/api-reference/functions/cacheTag)
|
||||
- [`dynamicIO`](/docs/app/api-reference/config/next-config-js/dynamicIO)
|
||||
- [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents)
|
||||
|
||||
**Authentication**:
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ related:
|
||||
description: View related API references.
|
||||
links:
|
||||
- app/api-reference/config/next-config-js/useCache
|
||||
- app/api-reference/config/next-config-js/dynamicIO
|
||||
- app/api-reference/config/next-config-js/cacheComponents
|
||||
- app/api-reference/config/next-config-js/cacheLife
|
||||
- app/api-reference/functions/cacheTag
|
||||
- app/api-reference/functions/cacheLife
|
||||
@@ -43,7 +43,7 @@ const nextConfig = {
|
||||
module.exports = nextConfig
|
||||
```
|
||||
|
||||
> **Good to know:** `use cache` can also be enabled with the [`dynamicIO`](/docs/app/api-reference/config/next-config-js/dynamicIO) option.
|
||||
> **Good to know:** `use cache` can also be enabled with the [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents) option.
|
||||
|
||||
Then, add `use cache` at the file, component, or function level:
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Route Segment Config
|
||||
description: Learn about how to configure options for Next.js route segments.
|
||||
---
|
||||
|
||||
> The options outlined on this page are disabled if the [`dynamicIO`](/docs/app/api-reference/config/next-config-js/dynamicIO) flag is on, and will eventually be deprecated in the future.
|
||||
> The options outlined on this page are disabled if the [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents) flag is on, and will eventually be deprecated in the future.
|
||||
|
||||
The Route Segment options allows you to configure the behavior of a [Page](/docs/app/api-reference/file-conventions/layout), [Layout](/docs/app/api-reference/file-conventions/layout), or [Route Handler](/docs/app/api-reference/file-conventions/route) by directly exporting the following variables:
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ related:
|
||||
title: Related
|
||||
description: View related API references.
|
||||
links:
|
||||
- app/api-reference/config/next-config-js/dynamicIO
|
||||
- app/api-reference/config/next-config-js/cacheComponents
|
||||
- app/api-reference/directives/use-cache
|
||||
- app/api-reference/functions/revalidateTag
|
||||
- app/api-reference/functions/cacheTag
|
||||
@@ -16,14 +16,14 @@ The `cacheLife` function is used to set the cache lifetime of a function or comp
|
||||
|
||||
## Usage
|
||||
|
||||
To use `cacheLife`, enable the [`dynamicIO` flag](/docs/app/api-reference/config/next-config-js/dynamicIO) in your `next.config.js` file:
|
||||
To use `cacheLife`, enable the [`cacheComponents` flag](/docs/app/api-reference/config/next-config-js/cacheComponents) in your `next.config.js` file:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export default nextConfig
|
||||
```js filename="next.config.js" switcher
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
cacheLife: {
|
||||
biweekly: {
|
||||
stale: 60 * 60 * 24 * 14, // 14 days
|
||||
@@ -128,7 +128,7 @@ module.exports = nextConfig
|
||||
```js filename="next.config.js" switcher
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
cacheLife: {
|
||||
biweekly: {
|
||||
stale: 60 * 60 * 24 * 14, // 14 days
|
||||
@@ -165,7 +165,7 @@ The example below shows how to override the default “days” cache profile:
|
||||
```ts filename="next.config.ts"
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
cacheLife: {
|
||||
days: {
|
||||
stale: 3600, // 1 hour
|
||||
|
||||
@@ -6,7 +6,7 @@ related:
|
||||
title: Related
|
||||
description: View related API references.
|
||||
links:
|
||||
- app/api-reference/config/next-config-js/dynamicIO
|
||||
- app/api-reference/config/next-config-js/cacheComponents
|
||||
- app/api-reference/directives/use-cache
|
||||
- app/api-reference/functions/revalidateTag
|
||||
- app/api-reference/functions/cacheLife
|
||||
@@ -16,14 +16,14 @@ The `cacheTag` function allows you to tag cached data for on-demand invalidation
|
||||
|
||||
## Usage
|
||||
|
||||
To use `cacheTag`, enable the [`dynamicIO` flag](/docs/app/api-reference/config/next-config-js/dynamicIO) in your `next.config.js` file:
|
||||
To use `cacheTag`, enable the [`cacheComponents` flag](/docs/app/api-reference/config/next-config-js/cacheComponents) in your `next.config.js` file:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export default nextConfig
|
||||
```js filename="next.config.js" switcher
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: dynamicIO
|
||||
description: Learn how to enable the dynamicIO flag in Next.js.
|
||||
title: cacheComponents
|
||||
description: Learn how to enable the cacheComponents flag in Next.js.
|
||||
version: canary
|
||||
---
|
||||
|
||||
The `dynamicIO` flag is an experimental feature in Next.js that causes data fetching operations in the App Router to be excluded from pre-renders unless they are explicitly cached. This can be useful for optimizing the performance of dynamic data fetching in Server Components.
|
||||
The `cacheComponents` flag is an experimental feature in Next.js that causes data fetching operations in the App Router to be excluded from pre-renders unless they are explicitly cached. This can be useful for optimizing the performance of dynamic data fetching in Server Components.
|
||||
|
||||
It is useful if your application requires fresh data fetching during runtime rather than serving from a pre-rendered cache.
|
||||
|
||||
@@ -12,21 +12,21 @@ It is expected to be used in conjunction with [`use cache`](/docs/app/api-refere
|
||||
|
||||
## Usage
|
||||
|
||||
To enable the `dynamicIO` flag, set it to `true` in the `experimental` section of your `next.config.ts` file:
|
||||
To enable the `cacheComponents` flag, set it to `true` in the `experimental` section of your `next.config.ts` file:
|
||||
|
||||
```ts filename="next.config.ts"
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
When `dynamicIO` is enabled, you can use the following cache functions and configurations:
|
||||
When `cacheComponents` is enabled, you can use the following cache functions and configurations:
|
||||
|
||||
- The [`use cache` directive](/docs/app/api-reference/directives/use-cache)
|
||||
- The [`cacheLife` function](/docs/app/api-reference/config/next-config-js/cacheLife) with `use cache`
|
||||
@@ -34,4 +34,4 @@ When `dynamicIO` is enabled, you can use the following cache functions and confi
|
||||
|
||||
## Notes
|
||||
|
||||
- While `dynamicIO` can optimize performance by ensuring fresh data fetching during runtime, it may also introduce additional latency compared to serving pre-rendered content.
|
||||
- While `cacheComponents` can optimize performance by ensuring fresh data fetching during runtime, it may also introduce additional latency compared to serving pre-rendered content.
|
||||
@@ -8,14 +8,14 @@ The `cacheLife` option allows you to define **custom cache profiles** when using
|
||||
|
||||
## Usage
|
||||
|
||||
To define a profile, enable the [`dynamicIO` flag](/docs/app/api-reference/config/next-config-js/dynamicIO) and add the cache profile in the `cacheLife` object in the `next.config.js` file. For example, a `blog` profile:
|
||||
To define a profile, enable the [`cacheComponents` flag](/docs/app/api-reference/config/next-config-js/cacheComponents) and add the cache profile in the `cacheLife` object in the `next.config.js` file. For example, a `blog` profile:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
cacheLife: {
|
||||
blog: {
|
||||
stale: 3600, // 1 hour
|
||||
@@ -32,7 +32,7 @@ export default nextConfig
|
||||
```js filename="next.config.js" switcher
|
||||
module.exports = {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
cacheLife: {
|
||||
blog: {
|
||||
stale: 3600, // 1 hour
|
||||
|
||||
@@ -4,7 +4,7 @@ description: Learn how to enable the useCache flag in Next.js.
|
||||
version: canary
|
||||
---
|
||||
|
||||
The `useCache` flag is an experimental feature in Next.js that enables the [`use cache` directive](/docs/app/api-reference/directives/use-cache) to be used independently of [`dynamicIO`](/docs/app/api-reference/config/next-config-js/dynamicIO). When enabled, you can use `use cache` in your application even if `dynamicIO` is turned off.
|
||||
The `useCache` flag is an experimental feature in Next.js that enables the [`use cache` directive](/docs/app/api-reference/directives/use-cache) to be used independently of [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents). When enabled, you can use `use cache` in your application even if `cacheComponents` is turned off.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Cannot access Request information or uncached data in `generateMetadata()
|
||||
|
||||
## Why This Error Occurred
|
||||
|
||||
When `dynamicIO` is enabled, Next.js requires that `generateMetadata()` not depend on uncached data or Request data unless some other part of the page also has similar requirements. The reason for this is that while you normally control your intention for what is allowed to be dynamic by adding or removing Suspense boundaries in your Layout and Page components you are not in control of rendering metadata itself.
|
||||
When `cacheComponents` is enabled, Next.js requires that `generateMetadata()` not depend on uncached data or Request data unless some other part of the page also has similar requirements. The reason for this is that while you normally control your intention for what is allowed to be dynamic by adding or removing Suspense boundaries in your Layout and Page components you are not in control of rendering metadata itself.
|
||||
|
||||
The heuristic Next.js uses to understand your intent with `generateMetadata()` is to look at the data requirements of the rest of the route. If other components depend on Request data or uncached data, then we allow `generateMetadata()` to have similar data requirements. If the rest of your page has no dependence on this type of data, we require that `generateMetadata()` also not have this type of data dependence.
|
||||
|
||||
@@ -141,7 +141,7 @@ export default function Page() {
|
||||
}
|
||||
```
|
||||
|
||||
Note: The reason to structure this `DynamicMarker` as a self-contained Suspense boundary is to avoid blocking the actual content of the page from being prerendered. When Partial Prerendering is enabled alongside `dynamicIO`, the static shell will still contain all of the prerenderable content, and only the metadata will stream in dynamically.
|
||||
Note: The reason to structure this `DynamicMarker` as a self-contained Suspense boundary is to avoid blocking the actual content of the page from being prerendered. When Partial Prerendering is enabled alongside `cacheComponents`, the static shell will still contain all of the prerenderable content, and only the metadata will stream in dynamically.
|
||||
|
||||
## Useful Links
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Cannot access Request information or uncached data in `generateViewport()
|
||||
|
||||
## Why This Error Occurred
|
||||
|
||||
When `dynamicIO` is enabled, Next.js requires that `generateViewport()` not depend on uncached data or Request data unless you explicitly opt into having a fully dynamic page. If you encountered this error, it means that `generateViewport` depends on one of these types of data and you have not specifically indicated that the affected route should be entirely dynamic.
|
||||
When `cacheComponents` is enabled, Next.js requires that `generateViewport()` not depend on uncached data or Request data unless you explicitly opt into having a fully dynamic page. If you encountered this error, it means that `generateViewport` depends on one of these types of data and you have not specifically indicated that the affected route should be entirely dynamic.
|
||||
|
||||
## Possible Ways to Fix It
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Cannot access data, headers, params, searchParams, or a short-lived cache
|
||||
|
||||
## Why This Error Occurred
|
||||
|
||||
When the experimental flag `dynamicIO` is enabled, Next.js expects a parent `Suspense` boundary around any component that awaits data that should be accessed on every user request. The purpose of this requirement is so that Next.js can provide a useful fallback while this data is accessed and rendered.
|
||||
When the experimental flag `cacheComponents` is enabled, Next.js expects a parent `Suspense` boundary around any component that awaits data that should be accessed on every user request. The purpose of this requirement is so that Next.js can provide a useful fallback while this data is accessed and rendered.
|
||||
|
||||
While some data is inherently only available when a user request is being handled, such as request headers, Next.js assumes that by default any asynchronous data is expected to be accessed each time a user request is being handled unless you specifically cache it using `"use cache"`.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ title: Cannot access Request information synchronously with `cookies()`, `header
|
||||
|
||||
In Next.js 15 global functions that provide access to Request Header information such as `cookies()`, `headers()`, and `draftMode()` are each now `async` and return a `Promise` rather than the associated object directly.
|
||||
|
||||
To support migrating to the async APIs Next.js 15 still supports accessing properties of the underlying object directly on the returned Promise. However when `dynamicIO` is enabled it is an error to do so.
|
||||
To support migrating to the async APIs Next.js 15 still supports accessing properties of the underlying object directly on the returned Promise. However when `cacheComponents` is enabled it is an error to do so.
|
||||
|
||||
## Possible Ways to Fix It
|
||||
|
||||
@@ -86,7 +86,7 @@ export default async function Page() {
|
||||
}
|
||||
```
|
||||
|
||||
Note that with `await connection()` and `dynamicIO` it is still required that there is a Suspense boundary somewhere above the component that uses `await connection()`. If you do not have any Suspense boundary parent you will need to add one where is appropriate to describe a fallback UI.
|
||||
Note that with `await connection()` and `cacheComponents` it is still required that there is a Suspense boundary somewhere above the component that uses `await connection()`. If you do not have any Suspense boundary parent you will need to add one where is appropriate to describe a fallback UI.
|
||||
|
||||
## Useful Links
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ title: Cannot access Request information synchronously with Page or Layout or Ro
|
||||
|
||||
In Next.js 15 `params` passed into Page and Layout components and `searchParams` passed into Page components are now Promises.
|
||||
|
||||
To support migrating to the async `params` and `searchParams` Next.js 15 still supports accessing param and searchParam values directly on the Promise object. However when `dynamicIO` is enabled it is an error to do so.
|
||||
To support migrating to the async `params` and `searchParams` Next.js 15 still supports accessing param and searchParam values directly on the Promise object. However when `cacheComponents` is enabled it is an error to do so.
|
||||
|
||||
## Possible Ways to Fix It
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Cannot access Request information synchronously in route.js without await
|
||||
|
||||
## Why This Error Occurred
|
||||
|
||||
When `dynamicIO` is enabled Next.js treats most access to Request information as explicitly dynamic. However routes have a Request object and you can read values like the current pathname from that object synchronously. If you need to access Request information in a route handler you should first call `await connection()` to inform Next.js that everything after this point is explicitly dynamic.
|
||||
When `cacheComponents` is enabled Next.js treats most access to Request information as explicitly dynamic. However routes have a Request object and you can read values like the current pathname from that object synchronously. If you need to access Request information in a route handler you should first call `await connection()` to inform Next.js that everything after this point is explicitly dynamic.
|
||||
|
||||
This is only needed in `GET` handlers because `GET` is the only handler Next.js will attempt to prerender.
|
||||
|
||||
|
||||
@@ -393,7 +393,7 @@
|
||||
"392": "Catch-all must be the last part of the URL.",
|
||||
"393": "\"dynamicParams: true\" cannot be used with \"output: export\". See more info here: https://nextjs.org/docs/app/building-your-application/deploying/static-exports",
|
||||
"394": "%s",
|
||||
"395": "cacheLife() is only available with the experimental.dynamicIO config.",
|
||||
"395": "cacheLife() is only available with the experimental.cacheComponents config.",
|
||||
"396": "Invariant: postponed state should not be present on a resume request",
|
||||
"397": "Image with src \"%s\" is missing required \"height\" property.",
|
||||
"398": "Route %s used \"cookies\" inside \"use cache\". Accessing Dynamic data sources inside a cache scope is not supported. If you need this data inside a cached function use \"cookies\" outside of the cached function and pass the required dynamic data in as an argument. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache",
|
||||
@@ -512,7 +512,7 @@
|
||||
"511": "The default export is not a React Component in page: \"/_document\"",
|
||||
"512": "module not found",
|
||||
"513": "Duration is too long to express as float64: %s",
|
||||
"514": "cacheTag() is only available with the experimental.dynamicIO config.",
|
||||
"514": "cacheTag() is only available with the experimental.cacheComponents config.",
|
||||
"515": "Specified i18n.defaultLocale should be included in i18n.locales.\\nSee more info here: https://nextjs.org/docs/messages/invalid-i18n-config",
|
||||
"516": "unstable_notFound has been renamed to notFound, please update the field to continue. Page: %s",
|
||||
"517": "Invariant: The detected locale does not match the locale in the query. Expected to find '%s' in '%s' but found '%s'}",
|
||||
@@ -605,9 +605,9 @@
|
||||
"604": "%s requires valid options.context",
|
||||
"605": "Failed to load page with getStaticPaths for %s",
|
||||
"606": "Missing manifest for Server Actions.",
|
||||
"607": "experimental.dynamicIO",
|
||||
"607": "experimental.cacheComponents",
|
||||
"608": "Route \"%s\" has a \\`generateMetadata\\` that could not finish rendering before %s was used. Follow the instructions in the error for this expression to resolve.",
|
||||
"609": "Route %s couldn't be rendered statically because it used IO that was not cached. See more info here: https://nextjs.org/docs/messages/dynamic-io",
|
||||
"609": "Route %s couldn't be rendered statically because it used IO that was not cached. See more info here: https://nextjs.org/docs/messages/cache-components",
|
||||
"610": "Could not find a production build in the '%s' directory. Try building your app with 'next build' before starting the static export. https://nextjs.org/docs/messages/next-export-no-build-id",
|
||||
"611": "Route %s with \\`dynamic = \"error\"\\` couldn't be rendered statically because it used \\`request.%s\\`.",
|
||||
"612": "ServerPrerenderStreamResult cannot be consumed as a stream because it is not yet complete. status: %s",
|
||||
@@ -683,9 +683,9 @@
|
||||
"682": "Invariant: missing result",
|
||||
"683": "Invariant: missing result from ",
|
||||
"684": "Invariant: missing result from ino",
|
||||
"685": "CacheSignal cannot be used in the edge runtime, because `dynamicIO` does not support it.",
|
||||
"686": "\\`experimental.ppr\\` can not be \\`%s\\` when \\`experimental.dynamicIO\\` is \\`true\\`. PPR is implicitly enabled when Dynamic IO is enabled.",
|
||||
"687": "Dynamic imports should not be instrumented in the edge runtime, because `dynamicIO` doesn't support it",
|
||||
"685": "CacheSignal cannot be used in the edge runtime, because `cacheComponents` does not support it.",
|
||||
"686": "\\`experimental.ppr\\` can not be \\`%s\\` when \\`experimental.cacheComponents\\` is \\`true\\`. PPR is implicitly enabled when Cache Components is enabled.",
|
||||
"687": "Dynamic imports should not be instrumented in the edge runtime, because `cacheComponents` doesn't support it",
|
||||
"688": "The \\`compiler.define\\` option is configured to replace the \\`%s\\` variable. This variable is either part of a Next.js built-in or is already configured.",
|
||||
"689": "The \\`compiler.defineServer\\` option is configured to replace the \\`%s\\` variable. This variable is either part of a Next.js built-in or is already configured.",
|
||||
"690": "Invariant: loadManifests called for edge runtime",
|
||||
@@ -721,5 +721,12 @@
|
||||
"720": "A client prerender store should not be used for a route handler.",
|
||||
"721": "Render in Browser",
|
||||
"722": "Unable to match pathname to a dynamic route",
|
||||
"723": "NAPI type conversion error in throwTurbopackInternalError"
|
||||
"723": "NAPI type conversion error in throwTurbopackInternalError",
|
||||
"724": "cacheLife() is only available with the experimental.cacheComponents config.",
|
||||
"725": "cacheTag() is only available with the experimental.cacheComponents config.",
|
||||
"726": "experimental.cacheComponents",
|
||||
"727": "Route %s couldn't be rendered statically because it used IO that was not cached. See more info here: https://nextjs.org/docs/messages/cache-components",
|
||||
"728": "CacheSignal cannot be used in the edge runtime, because `cacheComponents` does not support it.",
|
||||
"729": "\\`experimental.ppr\\` can not be \\`%s\\` when \\`experimental.cacheComponents\\` is \\`true\\`. PPR is implicitly enabled when Cache Components is enabled.",
|
||||
"730": "Dynamic imports should not be instrumented in the edge runtime, because `cacheComponents` doesn't support it"
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ export function getDefineEnv({
|
||||
const nextConfigEnv = getNextConfigEnv(config)
|
||||
|
||||
const isPPREnabled = checkIsAppPPREnabled(config.experimental.ppr)
|
||||
const isDynamicIOEnabled = !!config.experimental.dynamicIO
|
||||
const isCacheComponentsEnabled = !!config.experimental.cacheComponents
|
||||
const isUseCacheEnabled = !!config.experimental.useCache
|
||||
|
||||
const defineEnv: DefineEnv = {
|
||||
@@ -162,7 +162,7 @@ export function getDefineEnv({
|
||||
config.experimental.appNavFailHandling
|
||||
),
|
||||
'process.env.__NEXT_PPR': isPPREnabled,
|
||||
'process.env.__NEXT_DYNAMIC_IO': isDynamicIOEnabled,
|
||||
'process.env.__NEXT_CACHE_COMPONENTS': isCacheComponentsEnabled,
|
||||
'process.env.__NEXT_USE_CACHE': isUseCacheEnabled,
|
||||
|
||||
'process.env.NEXT_DEPLOYMENT_ID': config.experimental?.useSkewCookie
|
||||
|
||||
@@ -1355,7 +1355,9 @@ export default async function build(
|
||||
config.basePath ? `${config.basePath}${p}` : p
|
||||
)
|
||||
|
||||
const isAppDynamicIOEnabled = Boolean(config.experimental.dynamicIO)
|
||||
const isAppCacheComponentsEnabled = Boolean(
|
||||
config.experimental.cacheComponents
|
||||
)
|
||||
const isAuthInterruptsEnabled = Boolean(
|
||||
config.experimental.authInterrupts
|
||||
)
|
||||
@@ -1790,7 +1792,7 @@ export default async function build(
|
||||
distDir,
|
||||
configFileName,
|
||||
runtimeEnvConfig,
|
||||
dynamicIO: isAppDynamicIOEnabled,
|
||||
cacheComponents: isAppCacheComponentsEnabled,
|
||||
authInterrupts: isAuthInterruptsEnabled,
|
||||
httpAgentOptions: config.httpAgentOptions,
|
||||
locales: config.i18n?.locales,
|
||||
@@ -2029,7 +2031,7 @@ export default async function build(
|
||||
pageRuntime,
|
||||
edgeInfo,
|
||||
pageType,
|
||||
dynamicIO: isAppDynamicIOEnabled,
|
||||
cacheComponents: isAppCacheComponentsEnabled,
|
||||
authInterrupts: isAuthInterruptsEnabled,
|
||||
cacheHandler: config.cacheHandler,
|
||||
cacheHandlers: config.experimental.cacheHandlers,
|
||||
@@ -2572,8 +2574,8 @@ export default async function build(
|
||||
|
||||
const features: EventBuildFeatureUsage[] = [
|
||||
{
|
||||
featureName: 'experimental/dynamicIO',
|
||||
invocationCount: config.experimental.dynamicIO ? 1 : 0,
|
||||
featureName: 'experimental/cacheComponents',
|
||||
invocationCount: config.experimental.cacheComponents ? 1 : 0,
|
||||
},
|
||||
{
|
||||
featureName: 'experimental/optimizeCss',
|
||||
|
||||
@@ -621,7 +621,7 @@ export async function buildAppStaticPaths({
|
||||
dir,
|
||||
page,
|
||||
distDir,
|
||||
dynamicIO,
|
||||
cacheComponents,
|
||||
authInterrupts,
|
||||
segments,
|
||||
isrFlushToDisk,
|
||||
@@ -639,7 +639,7 @@ export async function buildAppStaticPaths({
|
||||
}: {
|
||||
dir: string
|
||||
page: string
|
||||
dynamicIO: boolean
|
||||
cacheComponents: boolean
|
||||
authInterrupts: boolean
|
||||
segments: AppSegment[]
|
||||
distDir: string
|
||||
@@ -696,7 +696,7 @@ export async function buildAppStaticPaths({
|
||||
supportsDynamicResponse: true,
|
||||
isRevalidate: false,
|
||||
experimental: {
|
||||
dynamicIO,
|
||||
cacheComponents,
|
||||
authInterrupts,
|
||||
},
|
||||
waitUntil: afterRunner.context.waitUntil,
|
||||
@@ -903,7 +903,7 @@ export async function buildAppStaticPaths({
|
||||
: undefined
|
||||
|
||||
// Now we have to set the throwOnEmptyStaticShell for each of the routes.
|
||||
if (prerenderedRoutes && dynamicIO) {
|
||||
if (prerenderedRoutes && cacheComponents) {
|
||||
assignErrorIfEmpty(prerenderedRoutes, routeParamKeys)
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ function getBaseSWCOptions({
|
||||
serverComponents,
|
||||
serverReferenceHashSalt,
|
||||
bundleLayer,
|
||||
isDynamicIo,
|
||||
isCacheComponents,
|
||||
cacheHandlers,
|
||||
useCacheEnabled,
|
||||
trackDynamicImports,
|
||||
@@ -91,7 +91,7 @@ function getBaseSWCOptions({
|
||||
serverComponents?: boolean
|
||||
serverReferenceHashSalt: string
|
||||
bundleLayer?: WebpackLayerName
|
||||
isDynamicIo?: boolean
|
||||
isCacheComponents?: boolean
|
||||
cacheHandlers?: ExperimentalConfig['cacheHandlers']
|
||||
useCacheEnabled?: boolean
|
||||
trackDynamicImports?: boolean
|
||||
@@ -217,7 +217,7 @@ function getBaseSWCOptions({
|
||||
serverComponents && !jest
|
||||
? {
|
||||
isReactServerLayer,
|
||||
dynamicIoEnabled: isDynamicIo,
|
||||
cacheComponentsEnabled: isCacheComponents,
|
||||
useCacheEnabled,
|
||||
}
|
||||
: undefined,
|
||||
@@ -373,7 +373,7 @@ export function getLoaderSWCOptions({
|
||||
pagesDir,
|
||||
appDir,
|
||||
isPageFile,
|
||||
isDynamicIo,
|
||||
isCacheComponents,
|
||||
hasReactRefresh,
|
||||
modularizeImports,
|
||||
optimizeServerReact,
|
||||
@@ -401,7 +401,7 @@ export function getLoaderSWCOptions({
|
||||
hasReactRefresh: boolean
|
||||
optimizeServerReact?: boolean
|
||||
modularizeImports: NextConfig['modularizeImports']
|
||||
isDynamicIo?: boolean
|
||||
isCacheComponents?: boolean
|
||||
optimizePackageImports?: NonNullable<
|
||||
NextConfig['experimental']
|
||||
>['optimizePackageImports']
|
||||
@@ -435,7 +435,7 @@ export function getLoaderSWCOptions({
|
||||
serverComponents,
|
||||
serverReferenceHashSalt,
|
||||
esm: !!esm,
|
||||
isDynamicIo,
|
||||
isCacheComponents,
|
||||
cacheHandlers,
|
||||
useCacheEnabled,
|
||||
trackDynamicImports,
|
||||
|
||||
@@ -350,7 +350,7 @@ export async function handler(
|
||||
// we should seed the resume data cache.
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (
|
||||
nextConfig.experimental.dynamicIO &&
|
||||
nextConfig.experimental.cacheComponents &&
|
||||
!isPrefetchRSCRequest &&
|
||||
!context.renderOpts.isPossibleServerAction
|
||||
) {
|
||||
@@ -497,7 +497,7 @@ export async function handler(
|
||||
isRoutePPREnabled,
|
||||
expireTime: nextConfig.expireTime,
|
||||
staleTimes: nextConfig.experimental.staleTimes,
|
||||
dynamicIO: Boolean(nextConfig.experimental.dynamicIO),
|
||||
cacheComponents: Boolean(nextConfig.experimental.cacheComponents),
|
||||
clientSegmentCache: Boolean(
|
||||
nextConfig.experimental.clientSegmentCache
|
||||
),
|
||||
|
||||
@@ -169,7 +169,7 @@ export async function handler(
|
||||
prerenderManifest,
|
||||
renderOpts: {
|
||||
experimental: {
|
||||
dynamicIO: Boolean(nextConfig.experimental.dynamicIO),
|
||||
cacheComponents: Boolean(nextConfig.experimental.cacheComponents),
|
||||
authInterrupts: Boolean(nextConfig.experimental.authInterrupts),
|
||||
},
|
||||
supportsDynamicResponse,
|
||||
|
||||
@@ -1027,7 +1027,7 @@ export async function isPageStatic({
|
||||
pageRuntime,
|
||||
edgeInfo,
|
||||
pageType,
|
||||
dynamicIO,
|
||||
cacheComponents,
|
||||
authInterrupts,
|
||||
originalAppPath,
|
||||
isrFlushToDisk,
|
||||
@@ -1043,7 +1043,7 @@ export async function isPageStatic({
|
||||
dir: string
|
||||
page: string
|
||||
distDir: string
|
||||
dynamicIO: boolean
|
||||
cacheComponents: boolean
|
||||
authInterrupts: boolean
|
||||
configFileName: string
|
||||
runtimeEnvConfig: any
|
||||
@@ -1191,7 +1191,7 @@ export async function isPageStatic({
|
||||
await buildAppStaticPaths({
|
||||
dir,
|
||||
page,
|
||||
dynamicIO,
|
||||
cacheComponents,
|
||||
authInterrupts,
|
||||
segments,
|
||||
distDir,
|
||||
|
||||
@@ -149,7 +149,7 @@ async function loaderTransform(
|
||||
development:
|
||||
this.mode === 'development' ||
|
||||
!!nextConfig.experimental?.allowDevelopmentBuild,
|
||||
isDynamicIo: nextConfig.experimental?.dynamicIO,
|
||||
isCacheComponents: nextConfig.experimental?.cacheComponents,
|
||||
hasReactRefresh,
|
||||
modularizeImports: nextConfig?.modularizeImports,
|
||||
optimizePackageImports: nextConfig?.experimental?.optimizePackageImports,
|
||||
@@ -217,12 +217,12 @@ async function loaderTransform(
|
||||
}
|
||||
|
||||
function shouldTrackDynamicImports(loaderOptions: SWCLoaderOptions): boolean {
|
||||
// we only need to track `import()` 1. in dynamicIO, 2. on the server (RSC and SSR)
|
||||
// we only need to track `import()` 1. in cacheComponents, 2. on the server (RSC and SSR)
|
||||
// (Note: logic duplicated in crates/next-core/src/next_server/transforms.rs)
|
||||
const { nextConfig, bundleLayer, compilerType } = loaderOptions
|
||||
return (
|
||||
!!nextConfig.experimental?.dynamicIO &&
|
||||
// NOTE: `server` means nodejs. `dynamicIO` is not supported in the edge runtime, so we want to exclude it.
|
||||
!!nextConfig.experimental?.cacheComponents &&
|
||||
// NOTE: `server` means nodejs. `cacheComponents` is not supported in the edge runtime, so we want to exclude it.
|
||||
// (also, the code generated by the dynamic imports transform relies on `CacheSignal`, which uses nodejs-specific APIs)
|
||||
compilerType === COMPILER_NAMES.server &&
|
||||
(bundleLayer === WEBPACK_LAYERS.reactServerComponents ||
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { Params } from '../../server/request/params'
|
||||
*
|
||||
* additionally we may send a promise representing params. We don't ever use this passed
|
||||
* value but it can be necessary for the sender to send a Promise that doesn't resolve in certain situations
|
||||
* such as when dynamicIO is enabled. It is up to the caller to decide if the promises are needed.
|
||||
* such as when cacheComponents is enabled. It is up to the caller to decide if the promises are needed.
|
||||
*/
|
||||
export function ClientSegmentRoot({
|
||||
Component,
|
||||
|
||||
@@ -169,7 +169,7 @@ function describeListOfPropertyNames(properties: Array<string>) {
|
||||
export function createRenderParamsFromClient(
|
||||
clientParams: Params
|
||||
): Promise<Params> {
|
||||
if (process.env.__NEXT_DYNAMIC_IO) {
|
||||
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
||||
return makeDynamicallyTrackedParamsWithDevWarnings(clientParams)
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ function makeUntrackedParams(underlyingParams: Params): Promise<Params> {
|
||||
export function createRenderParamsFromClient(
|
||||
clientParams: Params
|
||||
): Promise<Params> {
|
||||
if (process.env.__NEXT_DYNAMIC_IO) {
|
||||
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
||||
return makeUntrackedParams(clientParams)
|
||||
}
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ function warnForSyncSpread() {
|
||||
export function createRenderSearchParamsFromClient(
|
||||
underlyingSearchParams: SearchParams
|
||||
): Promise<SearchParams> {
|
||||
if (process.env.__NEXT_DYNAMIC_IO) {
|
||||
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
||||
return makeUntrackedSearchParamsWithDevWarnings(underlyingSearchParams)
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ function makeUntrackedSearchParams(
|
||||
export function createRenderSearchParamsFromClient(
|
||||
underlyingSearchParams: SearchParams
|
||||
): Promise<SearchParams> {
|
||||
if (process.env.__NEXT_DYNAMIC_IO) {
|
||||
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
||||
return makeUntrackedSearchParams(underlyingSearchParams)
|
||||
}
|
||||
|
||||
|
||||
@@ -400,7 +400,7 @@ async function exportAppImpl(
|
||||
clientTraceMetadata: nextConfig.experimental.clientTraceMetadata,
|
||||
expireTime: nextConfig.expireTime,
|
||||
staleTimes: nextConfig.experimental.staleTimes,
|
||||
dynamicIO: nextConfig.experimental.dynamicIO ?? false,
|
||||
cacheComponents: nextConfig.experimental.cacheComponents ?? false,
|
||||
clientSegmentCache:
|
||||
nextConfig.experimental.clientSegmentCache === 'client-only'
|
||||
? 'client-only'
|
||||
@@ -628,7 +628,7 @@ async function exportAppImpl(
|
||||
let initialPhaseExportPaths: ExportPathEntry[] = []
|
||||
const finalPhaseExportPaths: ExportPathEntry[] = []
|
||||
|
||||
if (renderOpts.experimental.dynamicIO) {
|
||||
if (renderOpts.experimental.cacheComponents) {
|
||||
for (const exportPath of allExportPaths) {
|
||||
if (exportPath._allowEmptyStaticShell) {
|
||||
finalPhaseExportPaths.push(exportPath)
|
||||
|
||||
@@ -49,7 +49,7 @@ export async function exportAppRoute(
|
||||
htmlFilepath: string,
|
||||
fileWriter: MultiFileWriter,
|
||||
experimental: Required<
|
||||
Pick<ExperimentalConfig, 'dynamicIO' | 'authInterrupts'>
|
||||
Pick<ExperimentalConfig, 'cacheComponents' | 'authInterrupts'>
|
||||
>,
|
||||
buildId: string
|
||||
): Promise<ExportRouteResult> {
|
||||
@@ -108,11 +108,11 @@ export async function exportAppRoute(
|
||||
if (
|
||||
!isStaticGenEnabled(userland) &&
|
||||
!isPageMetadataRoute &&
|
||||
// We don't disable static gen when dynamicIO is enabled because we
|
||||
// We don't disable static gen when cacheComponents is enabled because we
|
||||
// expect that anything dynamic in the GET handler will make it dynamic
|
||||
// and thus avoid the cache surprises that led to us removing static gen
|
||||
// unless specifically opted into
|
||||
experimental.dynamicIO !== true
|
||||
experimental.cacheComponents !== true
|
||||
) {
|
||||
return { cacheControl: { revalidate: 0, expire: undefined } }
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ async function exportPageImpl(
|
||||
_isRoutePPREnabled: isRoutePPREnabled,
|
||||
|
||||
// Configure the rendering of the page to allow that an empty static shell
|
||||
// is generated while rendering using PPR and Dynamic IO.
|
||||
// is generated while rendering using PPR and Cache Components.
|
||||
_allowEmptyStaticShell: allowEmptyStaticShell = false,
|
||||
|
||||
// Pull the original query out.
|
||||
|
||||
@@ -607,7 +607,7 @@ async function generateDynamicFlightRenderResult(
|
||||
)
|
||||
|
||||
const RSCPayload: RSCPayload & {
|
||||
/** Only available during dynamicIO development builds. Used for logging errors. */
|
||||
/** Only available during cacheComponents development builds. Used for logging errors. */
|
||||
_validation?: Promise<React.ReactNode>
|
||||
} = await workUnitAsyncStorage.run(
|
||||
requestStore,
|
||||
@@ -621,8 +621,8 @@ async function generateDynamicFlightRenderResult(
|
||||
renderOpts.dev &&
|
||||
// We only want this behavior when we have React's dev builds available
|
||||
process.env.NODE_ENV === 'development' &&
|
||||
// We only have a Prerender environment for projects opted into dynamicIO
|
||||
renderOpts.experimental.dynamicIO
|
||||
// We only have a Prerender environment for projects opted into cacheComponents
|
||||
renderOpts.experimental.cacheComponents
|
||||
) {
|
||||
const [resolveValidation, validationOutlet] = createValidationOutlet()
|
||||
RSCPayload._validation = validationOutlet
|
||||
@@ -1224,7 +1224,7 @@ async function renderToHTMLOrFlightImpl(
|
||||
// module loading from causing a prerender to abort too early.
|
||||
|
||||
const shouldTrackModuleLoading = () => {
|
||||
if (!renderOpts.experimental.dynamicIO) {
|
||||
if (!renderOpts.experimental.cacheComponents) {
|
||||
return false
|
||||
}
|
||||
if (renderOpts.dev) {
|
||||
@@ -1892,12 +1892,12 @@ async function renderToStream(
|
||||
process.env.NODE_ENV === 'development' &&
|
||||
// Edge routes never prerender so we don't have a Prerender environment for anything in edge runtime
|
||||
process.env.NEXT_RUNTIME !== 'edge' &&
|
||||
// We only have a Prerender environment for projects opted into dynamicIO
|
||||
experimental.dynamicIO
|
||||
// We only have a Prerender environment for projects opted into cacheComponents
|
||||
experimental.cacheComponents
|
||||
) {
|
||||
// This is a dynamic render. We don't do dynamic tracking because we're not prerendering
|
||||
const RSCPayload: InitialRSCPayload & {
|
||||
/** Only available during dynamicIO development builds. Used for logging errors. */
|
||||
/** Only available during cacheComponents development builds. Used for logging errors. */
|
||||
_validation?: Promise<React.ReactNode>
|
||||
} = await workUnitAsyncStorage.run(
|
||||
requestStore,
|
||||
@@ -2276,7 +2276,7 @@ function createValidationOutlet() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is a fork of prerenderToStream dynamicIO branch.
|
||||
* This function is a fork of prerenderToStream cacheComponents branch.
|
||||
* While it doesn't return a stream we want it to have identical
|
||||
* prerender semantics to prerenderToStream and should update it
|
||||
* in conjunction with any changes to that function.
|
||||
@@ -2713,7 +2713,7 @@ async function spawnDynamicValidationInDev(
|
||||
/>
|
||||
)
|
||||
} catch (thrownValue) {
|
||||
// Even if the root errors we still want to report any dynamic IO errors
|
||||
// Even if the root errors we still want to report any cache components errors
|
||||
// that were discovered before the root errored.
|
||||
|
||||
let loggingFunction = throwIfDisallowedDynamic.bind(
|
||||
@@ -2918,9 +2918,9 @@ async function prerenderToStream(
|
||||
let prerenderStore: PrerenderStore | null = null
|
||||
|
||||
try {
|
||||
if (experimental.dynamicIO) {
|
||||
if (experimental.cacheComponents) {
|
||||
/**
|
||||
* dynamicIO with PPR
|
||||
* cacheComponents with PPR
|
||||
*
|
||||
* The general approach is to render the RSC stream first allowing any cache reads to resolve.
|
||||
* Once we have settled all cache reads we restart the render and abort after a single Task.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* This class is used to detect when all cache reads for a given render are settled.
|
||||
* We do this to allow for cache warming the prerender without having to continue rendering
|
||||
* the remainder of the page. This feature is really only useful when the dynamicIO flag is on
|
||||
* the remainder of the page. This feature is really only useful when the cacheComponents flag is on
|
||||
* and should only be used in codepaths gated with this feature.
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,7 @@ export class CacheSignal {
|
||||
if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
// we rely on `process.nextTick`, which is not supported in edge
|
||||
throw new InvariantError(
|
||||
'CacheSignal cannot be used in the edge runtime, because `dynamicIO` does not support it.'
|
||||
'CacheSignal cannot be used in the edge runtime, because `cacheComponents` does not support it.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +391,7 @@ async function isPartialRSCData(
|
||||
signal: abortController.signal,
|
||||
onError() {},
|
||||
onPostpone() {
|
||||
// If something postponed, i.e. when Dynamic IO is not enabled, we can
|
||||
// If something postponed, i.e. when Cache Components is not enabled, we can
|
||||
// infer that the RSC data is partial.
|
||||
isPartial = true
|
||||
},
|
||||
|
||||
@@ -766,7 +766,7 @@ async function createComponentTreeInternal({
|
||||
const UseCachePageComponent: React.ComponentType<UseCachePageComponentProps> =
|
||||
PageComponent
|
||||
|
||||
if (!experimental.dynamicIO) {
|
||||
if (!experimental.cacheComponents) {
|
||||
// The "use cache" wrapper takes care of converting this into an
|
||||
// erroring search params promise when passing it to the original
|
||||
// function.
|
||||
|
||||
@@ -183,7 +183,7 @@ export function markCurrentScopeAsDynamic(
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is meant to be used when prerendering without dynamicIO or PPR.
|
||||
* This function is meant to be used when prerendering without cacheComponents or PPR.
|
||||
* When called during a build it will cause Next.js to consider the route as dynamic.
|
||||
*
|
||||
* @internal
|
||||
@@ -289,12 +289,12 @@ export function trackSynchronousPlatformIOAccessInDev(
|
||||
}
|
||||
|
||||
/**
|
||||
* use this function when prerendering with dynamicIO. If we are doing a
|
||||
* use this function when prerendering with cacheComponents. If we are doing a
|
||||
* prospective prerender we don't actually abort because we want to discover
|
||||
* all caches for the shell. If this is the actual prerender we do abort.
|
||||
*
|
||||
* This function accepts a prerenderStore but the caller should ensure we're
|
||||
* actually running in dynamicIO mode.
|
||||
* actually running in cacheComponents mode.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
@@ -574,7 +574,7 @@ export function useDynamicRouteParams(expression: string) {
|
||||
if (workUnitStore) {
|
||||
switch (workUnitStore.type) {
|
||||
case 'prerender-client':
|
||||
// We are in a prerender with dynamicIO semantics. We are going to
|
||||
// We are in a prerender with cacheComponents semantics. We are going to
|
||||
// hang here and never resolve. This will cause the currently
|
||||
// rendering component to effectively be a dynamic hole.
|
||||
React.use(makeHangingPromise(workUnitStore.renderSignal, expression))
|
||||
|
||||
@@ -3,7 +3,7 @@ import { isThenable } from '../../../shared/lib/is-thenable'
|
||||
import { trackPendingImport } from './track-module-loading.external'
|
||||
|
||||
/**
|
||||
* in DynamicIO, `import(...)` will be transformed into `trackDynamicImport(import(...))`.
|
||||
* in CacheComponents, `import(...)` will be transformed into `trackDynamicImport(import(...))`.
|
||||
* A dynamic import is essentially a cached async function, except it's cached by the module system.
|
||||
*
|
||||
* The promises are tracked globally regardless of if the `import()` happens inside a render or outside of it.
|
||||
@@ -14,7 +14,7 @@ export function trackDynamicImport<TExports extends Record<string, any>>(
|
||||
): Promise<TExports> {
|
||||
if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
throw new InvariantError(
|
||||
"Dynamic imports should not be instrumented in the edge runtime, because `dynamicIO` doesn't support it"
|
||||
"Dynamic imports should not be instrumented in the edge runtime, because `cacheComponents` doesn't support it"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ export interface RenderOptsPartial {
|
||||
expireTime: number | undefined
|
||||
staleTimes: ExperimentalConfig['staleTimes'] | undefined
|
||||
clientTraceMetadata: string[] | undefined
|
||||
dynamicIO: boolean
|
||||
cacheComponents: boolean
|
||||
clientSegmentCache: boolean | 'client-only'
|
||||
dynamicOnHover: boolean
|
||||
inlineCss: boolean
|
||||
|
||||
@@ -106,7 +106,7 @@ export interface WorkStore {
|
||||
>
|
||||
readonly assetPrefix?: string
|
||||
|
||||
dynamicIOEnabled: boolean
|
||||
cacheComponentsEnabled: boolean
|
||||
dev: boolean
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,7 +36,7 @@ export type WorkStoreContext = {
|
||||
pendingWaitUntil?: Promise<any>
|
||||
experimental: Pick<
|
||||
RenderOpts['experimental'],
|
||||
'isRoutePPREnabled' | 'dynamicIO' | 'authInterrupts'
|
||||
'isRoutePPREnabled' | 'cacheComponents' | 'authInterrupts'
|
||||
>
|
||||
|
||||
/**
|
||||
@@ -137,7 +137,7 @@ export function createWorkStore({
|
||||
assetPrefix: renderOpts?.assetPrefix || '',
|
||||
|
||||
afterContext: createAfterContext(renderOpts),
|
||||
dynamicIOEnabled: renderOpts.experimental.dynamicIO,
|
||||
cacheComponentsEnabled: renderOpts.experimental.cacheComponents,
|
||||
dev: renderOpts.dev ?? false,
|
||||
previouslyRevalidatedTags,
|
||||
refreshTagsByCacheKind: createRefreshTagsByCacheKind(),
|
||||
|
||||
@@ -451,7 +451,7 @@ export default abstract class Server<
|
||||
|
||||
/**
|
||||
* This is used to persist cache scopes across
|
||||
* prefetch -> full route requests for dynamic IO
|
||||
* prefetch -> full route requests for cache components
|
||||
* it's only fully used in dev
|
||||
*/
|
||||
|
||||
@@ -600,7 +600,7 @@ export default abstract class Server<
|
||||
expireTime: this.nextConfig.expireTime,
|
||||
staleTimes: this.nextConfig.experimental.staleTimes,
|
||||
clientTraceMetadata: this.nextConfig.experimental.clientTraceMetadata,
|
||||
dynamicIO: this.nextConfig.experimental.dynamicIO ?? false,
|
||||
cacheComponents: this.nextConfig.experimental.cacheComponents ?? false,
|
||||
clientSegmentCache:
|
||||
this.nextConfig.experimental.clientSegmentCache === 'client-only'
|
||||
? 'client-only'
|
||||
@@ -2777,7 +2777,7 @@ export default abstract class Server<
|
||||
// If we're in dev, and this isn't a prefetch or a server action,
|
||||
// we should seed the resume data cache.
|
||||
if (
|
||||
this.nextConfig.experimental.dynamicIO &&
|
||||
this.nextConfig.experimental.cacheComponents &&
|
||||
this.renderOpts.dev &&
|
||||
!isPrefetchRSCRequest &&
|
||||
!isPossibleServerAction
|
||||
|
||||
@@ -362,6 +362,7 @@ export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
|
||||
dynamicOnHover: z.boolean().optional(),
|
||||
disableOptimizedLoading: z.boolean().optional(),
|
||||
disablePostcssPresetEnv: z.boolean().optional(),
|
||||
cacheComponents: z.boolean().optional(),
|
||||
dynamicIO: z.boolean().optional(),
|
||||
inlineCss: z.boolean().optional(),
|
||||
esmExternals: z.union([z.boolean(), z.literal('loose')]).optional(),
|
||||
|
||||
@@ -700,6 +700,11 @@ export interface ExperimentalConfig {
|
||||
* Prerendering feature of Next.js, and it enables `react@experimental` being
|
||||
* used for the `app` directory.
|
||||
*/
|
||||
cacheComponents?: boolean
|
||||
|
||||
/**
|
||||
* @deprecated Use `experimental.cacheComponents` instead.
|
||||
*/
|
||||
dynamicIO?: boolean
|
||||
|
||||
/**
|
||||
@@ -827,7 +832,7 @@ export type ExportPathMap = {
|
||||
/**
|
||||
* When true, the page is prerendered as a fallback shell, while allowing
|
||||
* any dynamic accesses to result in an empty shell. This is the case when
|
||||
* the app has `experimental.ppr` and `experimental.dynamicIO` enabled, and
|
||||
* the app has `experimental.ppr` and `experimental.cacheComponents` enabled, and
|
||||
* there are also routes prerendered with a more complete set of params.
|
||||
* Prerendering those routes would catch any invalid dynamic accesses.
|
||||
*
|
||||
@@ -1388,7 +1393,7 @@ export const defaultConfig = Object.freeze({
|
||||
appNavFailHandling: false,
|
||||
prerenderEarlyExit: true,
|
||||
serverMinification: true,
|
||||
// Will default to dynamicIO value.
|
||||
// Will default to cacheComponents value.
|
||||
enablePrerenderSourceMaps: undefined,
|
||||
serverSourceMaps: false,
|
||||
linkNoTouchStart: false,
|
||||
@@ -1459,7 +1464,7 @@ export const defaultConfig = Object.freeze({
|
||||
serverComponentsHmrCache: true,
|
||||
staticGenerationMaxConcurrency: 8,
|
||||
staticGenerationMinPagesPerWorker: 25,
|
||||
dynamicIO: false,
|
||||
cacheComponents: false,
|
||||
inlineCss: false,
|
||||
useCache: undefined,
|
||||
slowModuleDetection: undefined,
|
||||
|
||||
@@ -108,17 +108,17 @@ describe('loadConfig', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('errors when using dynamicIO if not in canary', async () => {
|
||||
it('errors when using cacheComponents if not in canary', async () => {
|
||||
await expect(
|
||||
loadConfig('', __dirname, {
|
||||
customConfig: {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
).rejects.toThrow(
|
||||
/The experimental feature "experimental.dynamicIO" can only be enabled when using the latest canary version of Next.js./
|
||||
/The experimental feature "experimental.cacheComponents" can only be enabled when using the latest canary version of Next.js./
|
||||
)
|
||||
})
|
||||
|
||||
@@ -146,34 +146,95 @@ describe('loadConfig', () => {
|
||||
delete process.env.__NEXT_VERSION
|
||||
})
|
||||
|
||||
it('errors when dynamicIO is enabled but PPR is disabled', async () => {
|
||||
it('errors when cacheComponents is enabled but PPR is disabled', async () => {
|
||||
await expect(
|
||||
loadConfig('', __dirname, {
|
||||
customConfig: {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
ppr: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
).rejects.toThrow(
|
||||
'`experimental.ppr` can not be `false` when `experimental.dynamicIO` is `true`. PPR is implicitly enabled when Dynamic IO is enabled.'
|
||||
'`experimental.ppr` can not be `false` when `experimental.cacheComponents` is `true`. PPR is implicitly enabled when Cache Components is enabled.'
|
||||
)
|
||||
})
|
||||
|
||||
it('errors when dynamicIO is enabled but PPR set to "incremental"', async () => {
|
||||
it('errors when cacheComponents is enabled but PPR set to "incremental"', async () => {
|
||||
await expect(
|
||||
loadConfig('', __dirname, {
|
||||
customConfig: {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
ppr: 'incremental',
|
||||
},
|
||||
},
|
||||
})
|
||||
).rejects.toThrow(
|
||||
'`experimental.ppr` can not be `"incremental"` when `experimental.dynamicIO` is `true`. PPR is implicitly enabled when Dynamic IO is enabled.'
|
||||
'`experimental.ppr` can not be `"incremental"` when `experimental.cacheComponents` is `true`. PPR is implicitly enabled when Cache Components is enabled.'
|
||||
)
|
||||
})
|
||||
|
||||
it('migrates experimental.dynamicIO to experimental.cacheComponents', async () => {
|
||||
process.env.__NEXT_VERSION = 'canary'
|
||||
|
||||
const result = await loadConfig('', __dirname, {
|
||||
customConfig: {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
},
|
||||
},
|
||||
silent: true,
|
||||
})
|
||||
|
||||
expect(result.experimental.cacheComponents).toBe(true)
|
||||
expect(result.experimental.dynamicIO).toBeUndefined()
|
||||
|
||||
delete process.env.__NEXT_VERSION
|
||||
})
|
||||
|
||||
it('preserves cacheComponents when both dynamicIO and cacheComponents are set', async () => {
|
||||
process.env.__NEXT_VERSION = 'canary'
|
||||
|
||||
const result = await loadConfig('', __dirname, {
|
||||
customConfig: {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: false,
|
||||
},
|
||||
},
|
||||
silent: true,
|
||||
})
|
||||
|
||||
expect(result.experimental.cacheComponents).toBe(false)
|
||||
expect(result.experimental.dynamicIO).toBeUndefined()
|
||||
|
||||
delete process.env.__NEXT_VERSION
|
||||
})
|
||||
|
||||
it('warns when using deprecated experimental.dynamicIO', async () => {
|
||||
process.env.__NEXT_VERSION = 'canary'
|
||||
|
||||
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation()
|
||||
|
||||
await loadConfig('', __dirname, {
|
||||
customConfig: {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
},
|
||||
},
|
||||
silent: false,
|
||||
})
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'`experimental.dynamicIO` has been renamed to `experimental.cacheComponents`'
|
||||
)
|
||||
)
|
||||
|
||||
consoleSpy.mockRestore()
|
||||
delete process.env.__NEXT_VERSION
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -160,6 +160,26 @@ function assignDefaults(
|
||||
delete userConfig.exportTrailingSlash
|
||||
}
|
||||
|
||||
// Handle deprecation of experimental.dynamicIO and migrate to experimental.cacheComponents
|
||||
if (userConfig.experimental?.dynamicIO !== undefined) {
|
||||
warnOptionHasBeenDeprecated(
|
||||
userConfig,
|
||||
'experimental.dynamicIO',
|
||||
`\`experimental.dynamicIO\` has been renamed to \`experimental.cacheComponents\`. Please update your ${configFileName} file accordingly.`,
|
||||
silent
|
||||
)
|
||||
|
||||
// If cacheComponents was not explicitly set by the user (i.e., it's still the default value),
|
||||
// use the dynamicIO value. We check against the user config, not the merged result.
|
||||
if (userConfig.experimental?.cacheComponents === undefined) {
|
||||
userConfig.experimental.cacheComponents =
|
||||
userConfig.experimental.dynamicIO
|
||||
}
|
||||
|
||||
// Remove the deprecated property
|
||||
delete userConfig.experimental.dynamicIO
|
||||
}
|
||||
|
||||
const config = Object.keys(userConfig).reduce<{ [key: string]: any }>(
|
||||
(currentConfig, key) => {
|
||||
const value = userConfig[key]
|
||||
@@ -267,8 +287,8 @@ function assignDefaults(
|
||||
// Prevents usage of certain experimental features outside of canary
|
||||
if (result.experimental?.ppr) {
|
||||
throw new CanaryOnlyError({ feature: 'experimental.ppr' })
|
||||
} else if (result.experimental?.dynamicIO) {
|
||||
throw new CanaryOnlyError({ feature: 'experimental.dynamicIO' })
|
||||
} else if (result.experimental?.cacheComponents) {
|
||||
throw new CanaryOnlyError({ feature: 'experimental.cacheComponents' })
|
||||
} else if (result.experimental?.turbopackPersistentCaching) {
|
||||
throw new CanaryOnlyError({
|
||||
feature: 'experimental.turbopackPersistentCaching',
|
||||
@@ -1089,21 +1109,21 @@ function assignDefaults(
|
||||
result.htmlLimitedBots = HTML_LIMITED_BOT_UA_RE_STRING
|
||||
}
|
||||
|
||||
// "use cache" was originally implicitly enabled with the dynamicIO flag, so
|
||||
// we transfer the value for dynamicIO to the explicit useCache flag to ensure
|
||||
// "use cache" was originally implicitly enabled with the cacheComponents flag, so
|
||||
// we transfer the value for cacheComponents to the explicit useCache flag to ensure
|
||||
// backwards compatibility.
|
||||
if (result.experimental.useCache === undefined) {
|
||||
result.experimental.useCache = result.experimental.dynamicIO
|
||||
result.experimental.useCache = result.experimental.cacheComponents
|
||||
}
|
||||
|
||||
// If dynamicIO is enabled, we also enable PPR.
|
||||
if (result.experimental.dynamicIO) {
|
||||
// If cacheComponents is enabled, we also enable PPR.
|
||||
if (result.experimental.cacheComponents) {
|
||||
if (
|
||||
userConfig.experimental?.ppr === false ||
|
||||
userConfig.experimental?.ppr === 'incremental'
|
||||
) {
|
||||
throw new Error(
|
||||
`\`experimental.ppr\` can not be \`${JSON.stringify(userConfig.experimental?.ppr)}\` when \`experimental.dynamicIO\` is \`true\`. PPR is implicitly enabled when Dynamic IO is enabled.`
|
||||
`\`experimental.ppr\` can not be \`${JSON.stringify(userConfig.experimental?.ppr)}\` when \`experimental.cacheComponents\` is \`true\`. PPR is implicitly enabled when Cache Components is enabled.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1593,15 +1613,15 @@ function enforceExperimentalFeatures(
|
||||
if (
|
||||
process.env.__NEXT_EXPERIMENTAL_CACHE_COMPONENTS === 'true' &&
|
||||
// We do respect an explicit value in the user config.
|
||||
(config.experimental.dynamicIO === undefined ||
|
||||
(isDefaultConfig && !config.experimental.dynamicIO))
|
||||
(config.experimental.cacheComponents === undefined ||
|
||||
(isDefaultConfig && !config.experimental.cacheComponents))
|
||||
) {
|
||||
config.experimental.dynamicIO = true
|
||||
config.experimental.cacheComponents = true
|
||||
|
||||
if (configuredExperimentalFeatures) {
|
||||
addConfiguredExperimentalFeature(
|
||||
configuredExperimentalFeatures,
|
||||
'dynamicIO',
|
||||
'cacheComponents',
|
||||
true,
|
||||
'enabled by `__NEXT_EXPERIMENTAL_CACHE_COMPONENTS`'
|
||||
)
|
||||
@@ -1610,7 +1630,7 @@ function enforceExperimentalFeatures(
|
||||
|
||||
if (
|
||||
config.experimental.enablePrerenderSourceMaps === undefined &&
|
||||
config.experimental.dynamicIO === true
|
||||
config.experimental.cacheComponents === true
|
||||
) {
|
||||
config.experimental.enablePrerenderSourceMaps = true
|
||||
|
||||
@@ -1619,7 +1639,7 @@ function enforceExperimentalFeatures(
|
||||
configuredExperimentalFeatures,
|
||||
'enablePrerenderSourceMaps',
|
||||
true,
|
||||
'enabled by `experimental.dynamicIO`'
|
||||
'enabled by `experimental.cacheComponents`'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ const cache =
|
||||
? React.cache
|
||||
: (fn: (key: unknown) => void) => fn
|
||||
|
||||
// When Dynamic IO is enabled, we record these as errors so that they
|
||||
// When Cache Components is enabled, we record these as errors so that they
|
||||
// are captured by the dev overlay as it's more critical to fix these
|
||||
// when enabled.
|
||||
const logErrorOrWarn = process.env.__NEXT_DYNAMIC_IO
|
||||
const logErrorOrWarn = process.env.__NEXT_CACHE_COMPONENTS
|
||||
? console.error
|
||||
: console.warn
|
||||
|
||||
|
||||
@@ -820,7 +820,9 @@ export default class DevServer extends Server {
|
||||
configFileName,
|
||||
publicRuntimeConfig,
|
||||
serverRuntimeConfig,
|
||||
dynamicIO: Boolean(this.nextConfig.experimental.dynamicIO),
|
||||
cacheComponents: Boolean(
|
||||
this.nextConfig.experimental.cacheComponents
|
||||
),
|
||||
},
|
||||
httpAgentOptions,
|
||||
locales,
|
||||
|
||||
@@ -25,7 +25,7 @@ type RuntimeConfig = {
|
||||
configFileName: string
|
||||
publicRuntimeConfig: { [key: string]: any }
|
||||
serverRuntimeConfig: { [key: string]: any }
|
||||
dynamicIO: boolean
|
||||
cacheComponents: boolean
|
||||
}
|
||||
|
||||
// we call getStaticPaths in a separate process to ensure
|
||||
@@ -118,7 +118,7 @@ export async function loadStaticPaths({
|
||||
return buildAppStaticPaths({
|
||||
dir,
|
||||
page: pathname,
|
||||
dynamicIO: config.dynamicIO,
|
||||
cacheComponents: config.cacheComponents,
|
||||
segments,
|
||||
distDir,
|
||||
requestHeaders,
|
||||
|
||||
@@ -25,7 +25,7 @@ const abortListenersBySignal = new WeakMap<AbortSignal, AbortListeners>()
|
||||
|
||||
/**
|
||||
* This function constructs a promise that will never resolve. This is primarily
|
||||
* useful for dynamicIO where we use promise resolution timing to determine which
|
||||
* useful for cacheComponents where we use promise resolution timing to determine which
|
||||
* parts of a render can be included in a prerender.
|
||||
*
|
||||
* @internal
|
||||
|
||||
@@ -157,7 +157,7 @@ async function createCachedPrerenderResponse(
|
||||
revalidate: number,
|
||||
handleUnlock: () => Promise<void> | void
|
||||
): Promise<Response> {
|
||||
// We are prerendering at build time or revalidate time with dynamicIO so we
|
||||
// We are prerendering at build time or revalidate time with cacheComponents so we
|
||||
// need to buffer the response so we can guarantee it can be read in a
|
||||
// microtask.
|
||||
const bodyBuffer = await res.arrayBuffer()
|
||||
@@ -551,7 +551,7 @@ export function createPatchedFetcher(
|
||||
switch (workUnitStore.type) {
|
||||
case 'prerender':
|
||||
// While we don't want to do caching in the client scope we know the
|
||||
// fetch will be dynamic for dynamicIO so we may as well avoid the
|
||||
// fetch will be dynamic for cacheComponents so we may as well avoid the
|
||||
// call here. (fallthrough)
|
||||
case 'prerender-client':
|
||||
if (cacheSignal) {
|
||||
@@ -909,7 +909,7 @@ export function createPatchedFetcher(
|
||||
// We sometimes use the cache to dedupe fetches that do not
|
||||
// specify a cache configuration. In these cases we want to
|
||||
// make sure we still exclude them from prerenders if
|
||||
// dynamicIO is on so we introduce an artificial task boundary
|
||||
// cacheComponents is on so we introduce an artificial task boundary
|
||||
// here.
|
||||
await waitAtLeastOneReactRenderTask()
|
||||
break
|
||||
@@ -1096,13 +1096,13 @@ export function createPatchedFetcher(
|
||||
}
|
||||
|
||||
// We used to just resolve the Response and clone it however for
|
||||
// static generation with dynamicIO we need the response to be able to
|
||||
// static generation with cacheComponents we need the response to be able to
|
||||
// be resolved in a microtask and cloning the response will never have
|
||||
// a body that can resolve in a microtask in node (as observed through
|
||||
// experimentation) So instead we await the body and then when it is
|
||||
// available we construct manually cloned Response objects with the
|
||||
// body as an ArrayBuffer. This will be resolvable in a microtask
|
||||
// making it compatible with dynamicIO.
|
||||
// making it compatible with cacheComponents.
|
||||
const pendingResponse = doOriginalFetch(true, cacheReasonOverride)
|
||||
// We're cloning the response using this utility because there
|
||||
// exists a bug in the undici library around response cloning.
|
||||
|
||||
@@ -219,7 +219,7 @@ function installProcessErrorHandlers(
|
||||
// Immediately log the error.
|
||||
// TODO: Ideally, if we knew that this error was triggered by application
|
||||
// code, we would suppress it entirely without logging. We can't reliably
|
||||
// detect all of these, but when dynamicIO is enabled, we could suppress
|
||||
// detect all of these, but when cacheComponents is enabled, we could suppress
|
||||
// at least some of them by waiting to log the error until after all in-
|
||||
// progress renders have completed. Then, only log errors for which there
|
||||
// was not a corresponding "rejectionHandled" event.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* We extend `Date` during builds and revalidates to ensure that prerenders don't observe the clock as a source of IO
|
||||
* When dynamicIO is enabled. The current time is a form of IO even though it resolves synchronously. When dyanmicIO is
|
||||
* When cacheComponents is enabled. The current time is a form of IO even though it resolves synchronously. When cacheComponents is
|
||||
* enabled we need to ensure that clock time is excluded from prerenders unless it is cached.
|
||||
*
|
||||
* There is tension here because time is used for both output and introspection. While arbitrary we intend to reserve
|
||||
@@ -52,6 +52,6 @@ try {
|
||||
Date = createDate(Date)
|
||||
} catch {
|
||||
console.error(
|
||||
'Failed to install `Date` class extension. When using `experimental.dynamicIO`, APIs that read the current time will not correctly trigger dynamic behavior.'
|
||||
'Failed to install `Date` class extension. When using `experimental.cacheComponents`, APIs that read the current time will not correctly trigger dynamic behavior.'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* We extend node:crypto APIs during builds and revalidates to ensure that prerenders don't observe random bytes
|
||||
* When dynamicIO is enabled. Random bytes are a form of IO even if they resolve synchronously. When dyanmicIO is
|
||||
* When cacheComponents is enabled. Random bytes are a form of IO even if they resolve synchronously. When cacheComponents is
|
||||
* enabled we need to ensure that random bytes are excluded from prerenders unless they are cached.
|
||||
*
|
||||
*
|
||||
@@ -28,7 +28,7 @@ if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
}
|
||||
} catch {
|
||||
console.error(
|
||||
`Failed to install ${randomUUIDExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
|
||||
`Failed to install ${randomUUIDExpression} extension. When using \`experimental.cacheComponents\` calling this function will not correctly trigger dynamic behavior.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
}
|
||||
} catch {
|
||||
console.error(
|
||||
`Failed to install ${randomBytesExpression} extension. When using \`experimental.dynamicIO\` calling this function without a callback argument will not correctly trigger dynamic behavior.`
|
||||
`Failed to install ${randomBytesExpression} extension. When using \`experimental.cacheComponents\` calling this function without a callback argument will not correctly trigger dynamic behavior.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
}
|
||||
} catch {
|
||||
console.error(
|
||||
`Failed to install ${randomFillSyncExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
|
||||
`Failed to install ${randomFillSyncExpression} extension. When using \`experimental.cacheComponents\` calling this function will not correctly trigger dynamic behavior.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
}
|
||||
} catch {
|
||||
console.error(
|
||||
`Failed to install ${randomBytesExpression} extension. When using \`experimental.dynamicIO\` calling this function without a callback argument will not correctly trigger dynamic behavior.`
|
||||
`Failed to install ${randomBytesExpression} extension. When using \`experimental.cacheComponents\` calling this function without a callback argument will not correctly trigger dynamic behavior.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
}
|
||||
} catch {
|
||||
console.error(
|
||||
`Failed to install ${generatePrimeSyncExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
|
||||
`Failed to install ${generatePrimeSyncExpression} extension. When using \`experimental.cacheComponents\` calling this function will not correctly trigger dynamic behavior.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
}
|
||||
} catch {
|
||||
console.error(
|
||||
`Failed to install ${generateKeyPairSyncExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
|
||||
`Failed to install ${generateKeyPairSyncExpression} extension. When using \`experimental.cacheComponents\` calling this function will not correctly trigger dynamic behavior.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
}
|
||||
} catch {
|
||||
console.error(
|
||||
`Failed to install ${generateKeySyncExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
|
||||
`Failed to install ${generateKeySyncExpression} extension. When using \`experimental.cacheComponents\` calling this function will not correctly trigger dynamic behavior.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* We extend Math.random() during builds and revalidates to ensure that prerenders don't observe randomness
|
||||
* When dynamicIO is enabled. randomness is a form of IO even though it resolves synchronously. When dyanmicIO is
|
||||
* When cacheComponents is enabled. randomness is a form of IO even though it resolves synchronously. When cacheComponents is
|
||||
* enabled we need to ensure that randomness is excluded from prerenders.
|
||||
*
|
||||
* The extensions here never error nor alter the random generation itself and thus should be transparent to callers.
|
||||
@@ -21,6 +21,6 @@ try {
|
||||
Object.defineProperty(Math.random, 'name', { value: 'random' })
|
||||
} catch {
|
||||
console.error(
|
||||
`Failed to install ${expression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
|
||||
`Failed to install ${expression} extension. When using \`experimental.cacheComponents\` calling this function will not correctly trigger dynamic behavior.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* We extend Web Crypto APIs during builds and revalidates to ensure that prerenders don't observe random bytes
|
||||
* When dynamicIO is enabled. Random bytes are a form of IO even if they resolve synchronously. When dyanmicIO is
|
||||
* When cacheComponents is enabled. Random bytes are a form of IO even if they resolve synchronously. When cacheComponents is
|
||||
* enabled we need to ensure that random bytes are excluded from prerenders unless they are cached.
|
||||
*
|
||||
*
|
||||
@@ -31,7 +31,7 @@ try {
|
||||
}
|
||||
} catch {
|
||||
console.error(
|
||||
`Failed to install ${getRandomValuesExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
|
||||
`Failed to install ${getRandomValuesExpression} extension. When using \`experimental.cacheComponents\` calling this function will not correctly trigger dynamic behavior.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,6 +44,6 @@ try {
|
||||
} as typeof _randomUUID
|
||||
} catch {
|
||||
console.error(
|
||||
`Failed to install ${getRandomValuesExpression} extension. When using \`experimental.dynamicIO\` calling this function will not correctly trigger dynamic behavior.`
|
||||
`Failed to install ${getRandomValuesExpression} extension. When using \`experimental.cacheComponents\` calling this function will not correctly trigger dynamic behavior.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ export function cookies(): Promise<ReadonlyRequestCookies> {
|
||||
)
|
||||
case 'prerender-ppr':
|
||||
// We need track dynamic access here eagerly to keep continuity with
|
||||
// how cookies has worked in PPR without dynamicIO.
|
||||
// how cookies has worked in PPR without cacheComponents.
|
||||
return postponeWithTracking(
|
||||
workStore.route,
|
||||
callingExpression,
|
||||
@@ -139,7 +139,7 @@ export function cookies(): Promise<ReadonlyRequestCookies> {
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'development' && !workStore?.isPrefetchRequest) {
|
||||
if (process.env.__NEXT_DYNAMIC_IO) {
|
||||
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
||||
return makeUntrackedCookiesWithDevWarnings(
|
||||
underlyingCookies,
|
||||
workStore?.route
|
||||
|
||||
@@ -102,13 +102,13 @@ function createOrGetCachedDraftMode(
|
||||
if (process.env.NODE_ENV === 'development' && !workStore?.isPrefetchRequest) {
|
||||
const route = workStore?.route
|
||||
|
||||
if (process.env.__NEXT_DYNAMIC_IO) {
|
||||
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
||||
return createDraftModeWithDevWarnings(draftModeProvider, route)
|
||||
}
|
||||
|
||||
promise = createExoticDraftModeWithDevWarnings(draftModeProvider, route)
|
||||
} else {
|
||||
if (process.env.__NEXT_DYNAMIC_IO) {
|
||||
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
||||
return Promise.resolve(new DraftMode(draftModeProvider))
|
||||
}
|
||||
|
||||
|
||||
@@ -116,9 +116,9 @@ export function headers(): Promise<ReadonlyHeaders> {
|
||||
`${exportName} must not be used within a client component. Next.js should be preventing ${exportName} from being included in client components statically, but did not in this case.`
|
||||
)
|
||||
case 'prerender-ppr':
|
||||
// PPR Prerender (no dynamicIO)
|
||||
// PPR Prerender (no cacheComponents)
|
||||
// We are prerendering with PPR. We need track dynamic access here eagerly
|
||||
// to keep continuity with how headers has worked in PPR without dynamicIO.
|
||||
// to keep continuity with how headers has worked in PPR without cacheComponents.
|
||||
// TODO consider switching the semantic to throw on property access instead
|
||||
return postponeWithTracking(
|
||||
workStore.route,
|
||||
@@ -146,7 +146,7 @@ export function headers(): Promise<ReadonlyHeaders> {
|
||||
|
||||
const requestStore = getExpectedRequestStore('headers')
|
||||
if (process.env.NODE_ENV === 'development' && !workStore?.isPrefetchRequest) {
|
||||
if (process.env.__NEXT_DYNAMIC_IO) {
|
||||
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
||||
return makeUntrackedHeadersWithDevWarnings(
|
||||
requestStore.headers,
|
||||
workStore?.route
|
||||
|
||||
@@ -145,7 +145,7 @@ export function createPrerenderParamsForClientSegment(
|
||||
if (fallbackParams.has(key)) {
|
||||
// This params object has one or more fallback params, so we need
|
||||
// to consider the awaiting of this params object "dynamic". Since
|
||||
// we are in dynamicIO mode we encode this as a promise that never
|
||||
// we are in cacheComponents mode we encode this as a promise that never
|
||||
// resolves.
|
||||
return makeHangingPromise(workUnitStore.renderSignal, '`params`')
|
||||
}
|
||||
@@ -188,7 +188,7 @@ function createPrerenderParams(
|
||||
switch (prerenderStore.type) {
|
||||
case 'prerender':
|
||||
case 'prerender-client':
|
||||
// We are in a dynamicIO prerender
|
||||
// We are in a cacheComponents prerender
|
||||
return makeHangingParams(underlyingParams, prerenderStore)
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
@@ -213,7 +213,7 @@ function createRenderParams(
|
||||
workStore: WorkStore
|
||||
): Promise<Params> {
|
||||
if (process.env.NODE_ENV === 'development' && !workStore.isPrefetchRequest) {
|
||||
if (process.env.__NEXT_DYNAMIC_IO) {
|
||||
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
||||
return makeDynamicallyTrackedParamsWithDevWarnings(
|
||||
underlyingParams,
|
||||
workStore
|
||||
@@ -225,7 +225,7 @@ function createRenderParams(
|
||||
workStore
|
||||
)
|
||||
} else {
|
||||
if (process.env.__NEXT_DYNAMIC_IO) {
|
||||
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
||||
return makeUntrackedParams(underlyingParams)
|
||||
}
|
||||
|
||||
@@ -314,10 +314,10 @@ function makeErroringExoticParams(
|
||||
// for params is only dynamic when we're generating a fallback shell
|
||||
// and even when `dynamic = "error"` we still support generating dynamic
|
||||
// fallback shells
|
||||
// TODO remove this comment when dynamicIO is the default since there
|
||||
// TODO remove this comment when cacheComponents is the default since there
|
||||
// will be no `dynamic = "error"`
|
||||
if (prerenderStore.type === 'prerender-ppr') {
|
||||
// PPR Prerender (no dynamicIO)
|
||||
// PPR Prerender (no cacheComponents)
|
||||
postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
@@ -341,10 +341,10 @@ function makeErroringExoticParams(
|
||||
// for params is only dynamic when we're generating a fallback shell
|
||||
// and even when `dynamic = "error"` we still support generating dynamic
|
||||
// fallback shells
|
||||
// TODO remove this comment when dynamicIO is the default since there
|
||||
// TODO remove this comment when cacheComponents is the default since there
|
||||
// will be no `dynamic = "error"`
|
||||
if (prerenderStore.type === 'prerender-ppr') {
|
||||
// PPR Prerender (no dynamicIO)
|
||||
// PPR Prerender (no cacheComponents)
|
||||
postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
|
||||
@@ -93,7 +93,7 @@ function createPrerenderRootParams(
|
||||
// params need to be treated as dynamic because we have at least one fallback param
|
||||
switch (prerenderStore.type) {
|
||||
case 'prerender':
|
||||
// We are in a dynamicIO prerender
|
||||
// We are in a cacheComponents prerender
|
||||
const cachedParams = CachedParams.get(underlyingParams)
|
||||
if (cachedParams) {
|
||||
return cachedParams
|
||||
@@ -108,7 +108,7 @@ function createPrerenderRootParams(
|
||||
return promise
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
// We aren't in a dynamicIO prerender but we do have fallback params at this
|
||||
// We aren't in a cacheComponents prerender but we do have fallback params at this
|
||||
// level so we need to make an erroring params object which will postpone
|
||||
// if you access the fallback params
|
||||
return makeErroringRootParams(
|
||||
@@ -162,10 +162,10 @@ function makeErroringRootParams(
|
||||
// for params is only dynamic when we're generating a fallback shell
|
||||
// and even when `dynamic = "error"` we still support generating dynamic
|
||||
// fallback shells
|
||||
// TODO remove this comment when dynamicIO is the default since there
|
||||
// TODO remove this comment when cacheComponents is the default since there
|
||||
// will be no `dynamic = "error"`
|
||||
if (prerenderStore.type === 'prerender-ppr') {
|
||||
// PPR Prerender (no dynamicIO)
|
||||
// PPR Prerender (no cacheComponents)
|
||||
postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
|
||||
@@ -124,7 +124,7 @@ export function createPrerenderSearchParamsForClientPage(
|
||||
switch (workUnitStore.type) {
|
||||
case 'prerender':
|
||||
case 'prerender-client':
|
||||
// We're prerendering in a mode that aborts (dynamicIO) and should stall
|
||||
// We're prerendering in a mode that aborts (cacheComponents) and should stall
|
||||
// the promise to ensure the RSC side is considered dynamic
|
||||
return makeHangingPromise(workUnitStore.renderSignal, '`searchParams`')
|
||||
case 'prerender-ppr':
|
||||
@@ -156,7 +156,7 @@ function createPrerenderSearchParams(
|
||||
switch (prerenderStore.type) {
|
||||
case 'prerender':
|
||||
case 'prerender-client':
|
||||
// We are in a dynamicIO (PPR or otherwise) prerender
|
||||
// We are in a cacheComponents (PPR or otherwise) prerender
|
||||
return makeHangingSearchParams(prerenderStore)
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
@@ -181,7 +181,7 @@ function createRenderSearchParams(
|
||||
process.env.NODE_ENV === 'development' &&
|
||||
!workStore.isPrefetchRequest
|
||||
) {
|
||||
if (process.env.__NEXT_DYNAMIC_IO) {
|
||||
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
||||
return makeUntrackedSearchParamsWithDevWarnings(
|
||||
underlyingSearchParams,
|
||||
workStore
|
||||
@@ -193,7 +193,7 @@ function createRenderSearchParams(
|
||||
workStore
|
||||
)
|
||||
} else {
|
||||
if (process.env.__NEXT_DYNAMIC_IO) {
|
||||
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
||||
return makeUntrackedSearchParams(underlyingSearchParams)
|
||||
}
|
||||
|
||||
@@ -291,7 +291,7 @@ function makeErroringExoticSearchParams(
|
||||
expression
|
||||
)
|
||||
} else if (prerenderStore.type === 'prerender-ppr') {
|
||||
// PPR Prerender (no dynamicIO)
|
||||
// PPR Prerender (no cacheComponents)
|
||||
postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
@@ -316,7 +316,7 @@ function makeErroringExoticSearchParams(
|
||||
expression
|
||||
)
|
||||
} else if (prerenderStore.type === 'prerender-ppr') {
|
||||
// PPR Prerender (no dynamicIO)
|
||||
// PPR Prerender (no cacheComponents)
|
||||
postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
@@ -344,7 +344,7 @@ function makeErroringExoticSearchParams(
|
||||
expression
|
||||
)
|
||||
} else if (prerenderStore.type === 'prerender-ppr') {
|
||||
// PPR Prerender (no dynamicIO)
|
||||
// PPR Prerender (no cacheComponents)
|
||||
postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
@@ -379,7 +379,7 @@ function makeErroringExoticSearchParams(
|
||||
expression
|
||||
)
|
||||
} else if (prerenderStore.type === 'prerender-ppr') {
|
||||
// PPR Prerender (no dynamicIO)
|
||||
// PPR Prerender (no cacheComponents)
|
||||
postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
@@ -406,7 +406,7 @@ function makeErroringExoticSearchParams(
|
||||
expression
|
||||
)
|
||||
} else if (prerenderStore.type === 'prerender-ppr') {
|
||||
// PPR Prerender (no dynamicIO)
|
||||
// PPR Prerender (no cacheComponents)
|
||||
postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
|
||||
@@ -311,7 +311,8 @@ export class AppRouteRouteModule extends RouteModule<
|
||||
context: AppRouteRouteHandlerContext
|
||||
) {
|
||||
const isStaticGeneration = workStore.isStaticGeneration
|
||||
const dynamicIOEnabled = !!context.renderOpts.experimental?.dynamicIO
|
||||
const cacheComponentsEnabled =
|
||||
!!context.renderOpts.experimental?.cacheComponents
|
||||
|
||||
// Patch the global fetch.
|
||||
patchFetch({
|
||||
@@ -355,10 +356,10 @@ export class AppRouteRouteModule extends RouteModule<
|
||||
? INFINITE_CACHE
|
||||
: userlandRevalidate
|
||||
|
||||
if (dynamicIOEnabled) {
|
||||
if (cacheComponentsEnabled) {
|
||||
/**
|
||||
* When we are attempting to statically prerender the GET handler of a route.ts module
|
||||
* and dynamicIO is on we follow a similar pattern to rendering.
|
||||
* and cacheComponents is on we follow a similar pattern to rendering.
|
||||
*
|
||||
* We first run the handler letting caches fill. If something synchronously dynamic occurs
|
||||
* during this prospective render then we can infer it will happen on every render and we
|
||||
@@ -536,7 +537,7 @@ export class AppRouteRouteModule extends RouteModule<
|
||||
if (!bodyHandled) {
|
||||
bodyHandled = true
|
||||
finalController.abort()
|
||||
reject(createDynamicIOError(workStore.route))
|
||||
reject(createCacheComponentsError(workStore.route))
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
@@ -547,13 +548,13 @@ export class AppRouteRouteModule extends RouteModule<
|
||||
if (!responseHandled) {
|
||||
responseHandled = true
|
||||
finalController.abort()
|
||||
reject(createDynamicIOError(workStore.route))
|
||||
reject(createCacheComponentsError(workStore.route))
|
||||
}
|
||||
})
|
||||
})
|
||||
if (finalController.signal.aborted) {
|
||||
// We aborted from within the execution
|
||||
throw createDynamicIOError(workStore.route)
|
||||
throw createCacheComponentsError(workStore.route)
|
||||
} else {
|
||||
// We didn't abort during the execution. We can abort now as a matter of semantics
|
||||
// though at the moment nothing actually consumes this signal so it won't halt any
|
||||
@@ -1144,9 +1145,9 @@ const requireStaticNextUrlHandlers = {
|
||||
},
|
||||
}
|
||||
|
||||
function createDynamicIOError(route: string) {
|
||||
function createCacheComponentsError(route: string) {
|
||||
return new DynamicServerError(
|
||||
`Route ${route} couldn't be rendered statically because it used IO that was not cached. See more info here: https://nextjs.org/docs/messages/dynamic-io`
|
||||
`Route ${route} couldn't be rendered statically because it used IO that was not cached. See more info here: https://nextjs.org/docs/messages/cache-components`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -733,9 +733,9 @@ export function cache(
|
||||
originalFn.apply(null, [
|
||||
{
|
||||
params: outerParams,
|
||||
searchParams: workStore.dynamicIOEnabled
|
||||
searchParams: workStore.cacheComponentsEnabled
|
||||
? innerSearchParams
|
||||
: // When dynamicIO is not enabled, we can not encode
|
||||
: // When cacheComponents is not enabled, we can not encode
|
||||
// searchParams as a hanging promise. To still avoid unused
|
||||
// search params from making a page dynamic, we define them
|
||||
// in `createComponentTree` as a promise that resolves to an
|
||||
|
||||
@@ -287,7 +287,7 @@ export async function adapter(
|
||||
params.request.nextConfig?.experimental?.cacheLife,
|
||||
experimental: {
|
||||
isRoutePPREnabled: false,
|
||||
dynamicIO: false,
|
||||
cacheComponents: false,
|
||||
authInterrupts:
|
||||
!!params.request.nextConfig?.experimental?.authInterrupts,
|
||||
},
|
||||
|
||||
@@ -108,7 +108,7 @@ export class EdgeRouteModuleWrapper {
|
||||
onClose: closeController.onClose.bind(closeController),
|
||||
onAfterTaskError: undefined,
|
||||
experimental: {
|
||||
dynamicIO: !!process.env.__NEXT_DYNAMIC_IO,
|
||||
cacheComponents: !!process.env.__NEXT_CACHE_COMPONENTS,
|
||||
authInterrupts: !!process.env.__NEXT_EXPERIMENTAL_AUTH_INTERRUPTS,
|
||||
},
|
||||
cacheLifeProfiles: this.nextConfig.experimental.cacheLife,
|
||||
|
||||
@@ -109,6 +109,7 @@ function revalidate(tags: string[], expression: string) {
|
||||
`Route ${store.route} used "${expression}" inside a function cached with "unstable_cache(...)" which is unsupported. To ensure revalidation is performed consistently it must always happen outside of renders and cached functions. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`
|
||||
)
|
||||
case 'prerender':
|
||||
// cacheComponents Prerender
|
||||
const error = new Error(
|
||||
`Route ${store.route} used ${expression} without first calling \`await connection()\`.`
|
||||
)
|
||||
|
||||
@@ -172,7 +172,7 @@ export type EventBuildFeatureUsage = {
|
||||
| 'next/font/google'
|
||||
| 'next/font/local'
|
||||
| 'experimental/nextScriptWorkers'
|
||||
| 'experimental/dynamicIO'
|
||||
| 'experimental/cacheComponents'
|
||||
| 'experimental/optimizeCss'
|
||||
| 'experimental/ppr'
|
||||
| 'swcLoader'
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
"test/development/app-dir/dev-indicator/hide-button.test.ts",
|
||||
"test/development/app-dir/dev-indicator/route-type.test.ts",
|
||||
"test/development/app-dir/dynamic-error-trace/index.test.ts",
|
||||
"test/development/app-dir/dynamic-io-*/**/*",
|
||||
"test/development/app-dir/cache-components-*/**/*",
|
||||
"test/development/app-dir/edge-errors-hmr/index.test.ts",
|
||||
"test/development/app-dir/next-after-app-invalid-usage/index.test.ts",
|
||||
"test/development/app-dir/owner-stack-react-missing-key-prop/owner-stack-react-missing-key-prop.test.ts",
|
||||
@@ -151,8 +151,8 @@
|
||||
"test/e2e/app-dir/dynamic-href/dynamic-href.test.ts",
|
||||
"test/e2e/app-dir/dynamic-in-generate-params/index.test.ts",
|
||||
"test/e2e/app-dir/dynamic-interception-route-revalidate/dynamic-interception-route-revalidate.test.ts",
|
||||
"test/e2e/app-dir/dynamic-io-*/**/*",
|
||||
"test/e2e/app-dir/dynamic-io/**/*",
|
||||
"test/e2e/app-dir/cache-components-*/**/*",
|
||||
"test/e2e/app-dir/cache-components/**/*",
|
||||
"test/e2e/app-dir/dynamic-requests/dynamic-requests.test.ts",
|
||||
"test/e2e/app-dir/dynamic/dynamic.test.ts",
|
||||
"test/e2e/app-dir/edge-route-catchall/edge-route-catchall.test.ts",
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import {
|
||||
retry,
|
||||
} from 'next-test-utils'
|
||||
|
||||
describe('Dynamic IO Dev Errors', () => {
|
||||
describe('Cache Components Dev Errors', () => {
|
||||
const { next } = nextTestSetup({
|
||||
files: __dirname,
|
||||
})
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
},
|
||||
}
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ import { assertNoRedbox, hasErrorToast, retry } from 'next-test-utils'
|
||||
import { createSandbox } from 'development-sandbox'
|
||||
import { outdent } from 'outdent'
|
||||
|
||||
describe('Dynamic IO Dev Errors', () => {
|
||||
describe('Cache Components Dev Errors', () => {
|
||||
const { isTurbopack, next } = nextTestSetup({
|
||||
files: __dirname,
|
||||
})
|
||||
@@ -167,11 +167,11 @@ describe('Dynamic IO Dev Errors', () => {
|
||||
} else {
|
||||
await expect(browser).toDisplayRedbox(`
|
||||
{
|
||||
"description": " x Route segment config "revalidate" is not compatible with \`nextConfig.experimental.dynamicIO\`. Please remove it.",
|
||||
"description": " x Route segment config "revalidate" is not compatible with \`nextConfig.experimental.cacheComponents\`. Please remove it.",
|
||||
"environmentLabel": null,
|
||||
"label": "Build Error",
|
||||
"source": "./app/page.tsx
|
||||
Error: x Route segment config "revalidate" is not compatible with \`nextConfig.experimental.dynamicIO\`. Please remove it.
|
||||
Error: x Route segment config "revalidate" is not compatible with \`nextConfig.experimental.cacheComponents\`. Please remove it.
|
||||
,-[1:1]
|
||||
1 | export const revalidate = 10
|
||||
: ^^^^^^^^^^
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
cacheComponents: true,
|
||||
},
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ export default async function Page() {
|
||||
<main>
|
||||
<h1>Warmup Dev Renders</h1>
|
||||
<p>
|
||||
In Dev when dynamicIO is enabled requests are preceded by a cache
|
||||
In Dev when cacheComponents is enabled requests are preceded by a cache
|
||||
warming prerender. Without PPR this prerender only includes up to the
|
||||
nearest Loading boundary (loading.tsx) and will never include the Page
|
||||
itself. When PPR is enabled it will include everything that is
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user