Skip to content

Commit 7aa6137

Browse files
authored
Emit build error when useCache is enabled and Edge runtime is used (#75279)
The `"use cache"` directive is not compatible with the Edge runtime. When the `experimental.useCache` flag is enabled, we now emit a build error for pages with `export const runtime = 'edge'`. This is analogous to using the `experimental.dynamicIO` flag. The other route segment configs that are forbidden when `dynamicIO` is enabled, are currently still allowed for `useCache`: - `dynamicParams` - `dynamic` - `fetchCache` - `revalidate`
1 parent e431f9e commit 7aa6137

File tree

12 files changed

+256
-10
lines changed

12 files changed

+256
-10
lines changed

crates/next-core/src/next_shared/transforms/next_react_server_components.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ pub async fn get_next_react_server_components_transform_rule(
3535
) -> Result<ModuleRule> {
3636
let enable_mdx_rs = next_config.mdx_rs().await?.is_some();
3737
let dynamic_io_enabled = *next_config.enable_dynamic_io().await?;
38+
let use_cache_enabled = *next_config.enable_use_cache().await?;
3839
Ok(get_ecma_transform_rule(
3940
Box::new(NextJsReactServerComponents::new(
4041
is_react_server_layer,
4142
dynamic_io_enabled,
43+
use_cache_enabled,
4244
app_dir,
4345
)),
4446
enable_mdx_rs,
@@ -50,18 +52,21 @@ pub async fn get_next_react_server_components_transform_rule(
5052
struct NextJsReactServerComponents {
5153
is_react_server_layer: bool,
5254
dynamic_io_enabled: bool,
55+
use_cache_enabled: bool,
5356
app_dir: Option<Vc<FileSystemPath>>,
5457
}
5558

5659
impl NextJsReactServerComponents {
5760
fn new(
5861
is_react_server_layer: bool,
5962
dynamic_io_enabled: bool,
63+
use_cache_enabled: bool,
6064
app_dir: Option<Vc<FileSystemPath>>,
6165
) -> Self {
6266
Self {
6367
is_react_server_layer,
6468
dynamic_io_enabled,
69+
use_cache_enabled,
6570
app_dir,
6671
}
6772
}
@@ -82,6 +87,7 @@ impl CustomTransformer for NextJsReactServerComponents {
8287
Config::WithOptions(Options {
8388
is_react_server_layer: self.is_react_server_layer,
8489
dynamic_io_enabled: self.dynamic_io_enabled,
90+
use_cache_enabled: self.use_cache_enabled,
8591
}),
8692
match self.app_dir {
8793
None => None,

crates/next-custom-transforms/src/transforms/react_server_components.rs

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
use std::{collections::HashMap, path::PathBuf, rc::Rc, sync::Arc};
1+
use std::{
2+
collections::HashMap,
3+
fmt::{self, Display},
4+
path::PathBuf,
5+
rc::Rc,
6+
sync::Arc,
7+
};
28

39
use indexmap::IndexMap;
410
use once_cell::sync::Lazy;
@@ -45,6 +51,7 @@ impl Config {
4551
pub struct Options {
4652
pub is_react_server_layer: bool,
4753
pub dynamic_io_enabled: bool,
54+
pub use_cache_enabled: bool,
4855
}
4956

5057
/// A visitor that transforms given module to use module proxy if it's a React
@@ -54,6 +61,7 @@ pub struct Options {
5461
struct ReactServerComponents<C: Comments> {
5562
is_react_server_layer: bool,
5663
dynamic_io_enabled: bool,
64+
use_cache_enabled: bool,
5765
filepath: String,
5866
app_dir: Option<PathBuf>,
5967
comments: C,
@@ -80,13 +88,28 @@ enum RSCErrorKind {
8088
NextRscErrInvalidApi((String, Span)),
8189
NextRscErrDeprecatedApi((String, String, Span)),
8290
NextSsrDynamicFalseNotAllowed(Span),
83-
NextRscErrIncompatibleDynamicIoSegment(Span, String),
91+
NextRscErrIncompatibleRouteSegmentConfig(Span, String, NextConfigProperty),
92+
}
93+
94+
#[derive(Clone, Debug, Copy)]
95+
enum NextConfigProperty {
96+
DynamicIo,
97+
UseCache,
98+
}
99+
100+
impl Display for NextConfigProperty {
101+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102+
match self {
103+
NextConfigProperty::DynamicIo => write!(f, "experimental.dynamicIO"),
104+
NextConfigProperty::UseCache => write!(f, "experimental.useCache"),
105+
}
106+
}
84107
}
85108

86109
enum InvalidExportKind {
87110
General,
88111
Metadata,
89-
DynamicIoSegment,
112+
RouteSegmentConfig(NextConfigProperty),
90113
}
91114

92115
impl<C: Comments> VisitMut for ReactServerComponents<C> {
@@ -97,6 +120,7 @@ impl<C: Comments> VisitMut for ReactServerComponents<C> {
97120
let mut validator = ReactServerComponentValidator::new(
98121
self.is_react_server_layer,
99122
self.dynamic_io_enabled,
123+
self.use_cache_enabled,
100124
self.filepath.clone(),
101125
self.app_dir.clone(),
102126
);
@@ -318,8 +342,8 @@ fn report_error(app_dir: &Option<PathBuf>, filepath: &str, error_kind: RSCErrorK
318342
.to_string(),
319343
vec![span],
320344
),
321-
RSCErrorKind::NextRscErrIncompatibleDynamicIoSegment(span, segment) => (
322-
format!("Route segment config \"{}\" is not compatible with `nextConfig.experimental.dynamicIO`. Please remove it.", segment),
345+
RSCErrorKind::NextRscErrIncompatibleRouteSegmentConfig(span, segment, property) => (
346+
format!("Route segment config \"{}\" is not compatible with `nextConfig.{}`. Please remove it.", segment, property),
323347
vec![span],
324348
),
325349
};
@@ -516,6 +540,7 @@ fn collect_top_level_directives_and_imports(
516540
struct ReactServerComponentValidator {
517541
is_react_server_layer: bool,
518542
dynamic_io_enabled: bool,
543+
use_cache_enabled: bool,
519544
filepath: String,
520545
app_dir: Option<PathBuf>,
521546
invalid_server_imports: Vec<JsWord>,
@@ -534,12 +559,14 @@ impl ReactServerComponentValidator {
534559
pub fn new(
535560
is_react_server_layer: bool,
536561
dynamic_io_enabled: bool,
562+
use_cache_enabled: bool,
537563
filename: String,
538564
app_dir: Option<PathBuf>,
539565
) -> Self {
540566
Self {
541567
is_react_server_layer,
542568
dynamic_io_enabled,
569+
use_cache_enabled,
543570
filepath: filename,
544571
app_dir,
545572
directive_import_collection: None,
@@ -769,11 +796,39 @@ impl ReactServerComponentValidator {
769796
(InvalidExportKind::Metadata, *span),
770797
);
771798
}
772-
"dynamicParams" | "dynamic" | "fetchCache" | "runtime" | "revalidate" => {
799+
"runtime" => {
773800
if self.dynamic_io_enabled {
774801
possibly_invalid_exports.insert(
775802
export_name.to_string(),
776-
(InvalidExportKind::DynamicIoSegment, *span),
803+
(
804+
InvalidExportKind::RouteSegmentConfig(
805+
NextConfigProperty::DynamicIo,
806+
),
807+
*span,
808+
),
809+
);
810+
} else if self.use_cache_enabled {
811+
possibly_invalid_exports.insert(
812+
export_name.to_string(),
813+
(
814+
InvalidExportKind::RouteSegmentConfig(
815+
NextConfigProperty::UseCache,
816+
),
817+
*span,
818+
),
819+
);
820+
}
821+
}
822+
"dynamicParams" | "dynamic" | "fetchCache" | "revalidate" => {
823+
if self.dynamic_io_enabled {
824+
possibly_invalid_exports.insert(
825+
export_name.to_string(),
826+
(
827+
InvalidExportKind::RouteSegmentConfig(
828+
NextConfigProperty::DynamicIo,
829+
),
830+
*span,
831+
),
777832
);
778833
}
779834
}
@@ -815,13 +870,14 @@ impl ReactServerComponentValidator {
815870

816871
for (export_name, (kind, span)) in &possibly_invalid_exports {
817872
match kind {
818-
InvalidExportKind::DynamicIoSegment => {
873+
InvalidExportKind::RouteSegmentConfig(property) => {
819874
report_error(
820875
&self.app_dir,
821876
&self.filepath,
822-
RSCErrorKind::NextRscErrIncompatibleDynamicIoSegment(
877+
RSCErrorKind::NextRscErrIncompatibleRouteSegmentConfig(
823878
*span,
824879
export_name.clone(),
880+
*property,
825881
),
826882
);
827883
}
@@ -985,11 +1041,21 @@ pub fn server_components_assert(
9851041
Config::WithOptions(x) => x.dynamic_io_enabled,
9861042
_ => false,
9871043
};
1044+
let use_cache_enabled: bool = match &config {
1045+
Config::WithOptions(x) => x.use_cache_enabled,
1046+
_ => false,
1047+
};
9881048
let filename = match filename {
9891049
FileName::Custom(path) => format!("<{path}>"),
9901050
_ => filename.to_string(),
9911051
};
992-
ReactServerComponentValidator::new(is_react_server_layer, dynamic_io_enabled, filename, app_dir)
1052+
ReactServerComponentValidator::new(
1053+
is_react_server_layer,
1054+
dynamic_io_enabled,
1055+
use_cache_enabled,
1056+
filename,
1057+
app_dir,
1058+
)
9931059
}
9941060

9951061
/// Runs react server component transform for the module proxy, as well as
@@ -1008,9 +1074,14 @@ pub fn server_components<C: Comments>(
10081074
Config::WithOptions(x) => x.dynamic_io_enabled,
10091075
_ => false,
10101076
};
1077+
let use_cache_enabled: bool = match &config {
1078+
Config::WithOptions(x) => x.use_cache_enabled,
1079+
_ => false,
1080+
};
10111081
visit_mut_pass(ReactServerComponents {
10121082
is_react_server_layer,
10131083
dynamic_io_enabled,
1084+
use_cache_enabled,
10141085
comments,
10151086
filepath: match &*filename {
10161087
FileName::Custom(path) => format!("<{path}>"),

crates/next-custom-transforms/tests/errors.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ fn react_server_components_errors(input: PathBuf) {
9494
use next_custom_transforms::transforms::react_server_components::{Config, Options};
9595
let is_react_server_layer = input.iter().any(|s| s.to_str() == Some("server-graph"));
9696
let dynamic_io_enabled = input.iter().any(|s| s.to_str() == Some("dynamic-io"));
97+
let use_cache_enabled = input.iter().any(|s| s.to_str() == Some("use-cache"));
9798
let output = input.parent().unwrap().join("output.js");
9899
test_fixture(
99100
syntax(),
@@ -103,6 +104,7 @@ fn react_server_components_errors(input: PathBuf) {
103104
Config::WithOptions(Options {
104105
is_react_server_layer,
105106
dynamic_io_enabled,
107+
use_cache_enabled,
106108
}),
107109
tr.comments.as_ref().clone(),
108110
None,
@@ -154,6 +156,7 @@ fn react_server_actions_errors(input: PathBuf) {
154156
Config::WithOptions(Options {
155157
is_react_server_layer,
156158
dynamic_io_enabled: true,
159+
use_cache_enabled: true,
157160
}),
158161
tr.comments.as_ref().clone(),
159162
None,
@@ -213,6 +216,7 @@ fn use_cache_not_allowed(input: PathBuf) {
213216
Config::WithOptions(Options {
214217
is_react_server_layer: true,
215218
dynamic_io_enabled: false,
219+
use_cache_enabled: false,
216220
}),
217221
tr.comments.as_ref().clone(),
218222
None,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const runtime = 'edge'
2+
// The following route segment configs are currently allowed:
3+
export const dynamic = 'force-dynamic'
4+
export const dynamicParams = false
5+
export const fetchCache = 'force-no-store'
6+
export const revalidate = 1
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const runtime = 'edge';
2+
// The following route segment configs are currently allowed:
3+
export const dynamic = 'force-dynamic';
4+
export const dynamicParams = false;
5+
export const fetchCache = 'force-no-store';
6+
export const revalidate = 1;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
x Route segment config "runtime" is not compatible with `nextConfig.experimental.useCache`. Please remove it.
2+
,-[input.js:1:1]
3+
1 | export const runtime = 'edge'
4+
: ^^^^^^^
5+
2 | // The following route segment configs are currently allowed:
6+
`----

crates/next-custom-transforms/tests/fixture.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ fn react_server_components_typescript(input: PathBuf) {
475475
Config::WithOptions(Options {
476476
is_react_server_layer: true,
477477
dynamic_io_enabled: false,
478+
use_cache_enabled: false,
478479
}),
479480
tr.comments.as_ref().clone(),
480481
None,
@@ -502,6 +503,7 @@ fn react_server_components_fixture(input: PathBuf) {
502503
Config::WithOptions(Options {
503504
is_react_server_layer,
504505
dynamic_io_enabled: false,
506+
use_cache_enabled: false,
505507
}),
506508
tr.comments.as_ref().clone(),
507509
None,

packages/next/src/build/swc/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ function getBaseSWCOptions({
212212
? {
213213
isReactServerLayer,
214214
dynamicIoEnabled: isDynamicIo,
215+
useCacheEnabled,
215216
}
216217
: undefined,
217218
serverActions:
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default function Root({ children }: { children: React.ReactNode }) {
2+
return (
3+
<html>
4+
<body>
5+
<main>{children}</main>
6+
</body>
7+
</html>
8+
)
9+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const runtime = 'edge'
2+
3+
export default function Page() {
4+
return <div>This page uses `export const runtime`.</div>
5+
}

0 commit comments

Comments
 (0)