From 23c4649853aa2013c39f1e3db97e666bdc10795e Mon Sep 17 00:00:00 2001
From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Date: Tue, 27 Jan 2026 20:45:16 +0100
Subject: [PATCH 1/6] [backport] Upgrade to swc 54 (#88207) (#89103)
Backports #88207
For this issue https://github.com/vercel/next.js/issues/88822
Also includes https://github.com/vercel/next.js/pull/89066/changes/df12b894f9e7d9a15b8b159d260793b8f810ed68 to get CI working
---
Cargo.lock | 382 +++++++++++-------
Cargo.toml | 16 +-
.../tests/loader/issue-30389/output.js | 2 +-
packages/next/taskfile.js | 4 +-
... ecmascript source code failed-9f1236.txt} | 10 +-
...nsforms_preset_env_input_index_5aaf1327.js | 2 +-
...rms_preset_env_input_index_5aaf1327.js.map | 2 +-
7 files changed, 257 insertions(+), 161 deletions(-)
rename turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/invalid-export-parse-error/issues/{Parsing ecmascript source code failed-988b73.txt => Parsing ecmascript source code failed-9f1236.txt} (78%)
diff --git a/Cargo.lock b/Cargo.lock
index 4c41141e5fa9f..4b7f6f85be880 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -600,9 +600,9 @@ dependencies = [
[[package]]
name = "binding_macros"
-version = "48.0.0"
+version = "52.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d75f0bad14aa119d142f9a5e7e09bae121a57ff6733e05e419f7fb6b58daae53"
+checksum = "82abc2266f26d1ab0d36c0a24d4f07ef6b129d9b21b4c331698a941af58bb138"
dependencies = [
"anyhow",
"console_error_panic_hook",
@@ -613,7 +613,7 @@ dependencies = [
"swc",
"swc_common",
"swc_ecma_ast",
- "swc_ecma_transforms",
+ "swc_ecma_transforms 44.0.0",
"swc_ecma_visit",
"wasm-bindgen",
"wasm-bindgen-futures",
@@ -3942,7 +3942,7 @@ dependencies = [
[[package]]
name = "mdxjs"
version = "1.0.4"
-source = "git+https://github.com/vercel-labs/mdxjs-rs-turbopack.git?branch=turbopack#61f972c5493ec667227b33f81c3db5cdbaee0c70"
+source = "git+https://github.com/vercel-labs/mdxjs-rs-turbopack.git?branch=turbopack#c91839ba4365c4f41458a35132b81523db12c7ef"
dependencies = [
"markdown",
"rustc-hash 2.1.1",
@@ -4116,9 +4116,9 @@ dependencies = [
[[package]]
name = "modularize_imports"
-version = "0.105.0"
+version = "0.108.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54fae10b4b55eaac16b2b0f137ef17386b59ffa84c3742689a62c023d5671429"
+checksum = "68a92a7624364d1b7eb4766e5a28803d59c3a92b20c625f5c16269ffa20693ad"
dependencies = [
"convert_case",
"handlebars",
@@ -5758,9 +5758,9 @@ dependencies = [
[[package]]
name = "react_remove_properties"
-version = "0.59.0"
+version = "0.62.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c579911352ee65ecf1759e2d0ff8ee9c08ec6cb79dd5368de5621ce7189b30c"
+checksum = "f351d3759cc556a5932f5602993e3d77b860e8a35025e8afdf9234d1aef2c31f"
dependencies = [
"serde",
"swc_atoms",
@@ -5871,9 +5871,9 @@ checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca"
[[package]]
name = "remove_console"
-version = "0.60.0"
+version = "0.63.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f32606f2fe4a09a819b2d100e40904d032239698855cc650ef1a67ffc338073"
+checksum = "a4d9abba71018614c38e8249b443c025ac19a489f522899f9e7703c18d2ac8fd"
dependencies = [
"serde",
"swc_atoms",
@@ -6925,9 +6925,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "styled_components"
-version = "0.133.0"
+version = "0.136.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fad77c7648d966a363195e8997d6d13ce9de45a9f58122e36c54e7089936cf4"
+checksum = "23c6e9e6685a48e8bee2d1b787dc2b84044684173fe96feb711dfe314016ba2f"
dependencies = [
"Inflector",
"once_cell",
@@ -6944,9 +6944,9 @@ dependencies = [
[[package]]
name = "styled_jsx"
-version = "0.108.0"
+version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b49865d7cb6225992b308e46f2e399b387a8156b79a56c8071cb266dec99684d"
+checksum = "6d84ca02fd9b0aa9abc1113115cc675fd9ca7557db4b8f11b61aaf5b7a27b9ac"
dependencies = [
"anyhow",
"lightningcss",
@@ -6965,9 +6965,9 @@ dependencies = [
"swc_css_prefixer",
"swc_css_visit",
"swc_ecma_ast",
- "swc_ecma_minifier",
- "swc_ecma_parser",
- "swc_ecma_transforms_base",
+ "swc_ecma_minifier 41.0.1",
+ "swc_ecma_parser 31.0.0",
+ "swc_ecma_transforms_base 34.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
"swc_plugin_macro",
@@ -6988,9 +6988,9 @@ checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b"
[[package]]
name = "swc"
-version = "48.0.1"
+version = "52.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8dbf2d5a3990c940c41e36a64f041062f53fd54f2e3e88e0e3646edae26d285"
+checksum = "d330c7945664f5fe9594443dbaea3abfa275a1d6513d0888d163cc580ddab408"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -7015,13 +7015,13 @@ dependencies = [
"swc_ecma_codegen",
"swc_ecma_ext_transforms",
"swc_ecma_loader",
- "swc_ecma_minifier",
- "swc_ecma_parser",
+ "swc_ecma_minifier 42.0.0",
+ "swc_ecma_parser 32.0.0",
"swc_ecma_preset_env",
- "swc_ecma_transforms",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms 44.0.0",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_transforms_compat",
- "swc_ecma_transforms_optimization",
+ "swc_ecma_transforms_optimization 37.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
"swc_error_reporters",
@@ -7126,9 +7126,9 @@ dependencies = [
[[package]]
name = "swc_compiler_base"
-version = "41.0.0"
+version = "45.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1583da0d977424f5b637daf962cd8b0a08115c2e5b1e4563c8a159b19f5c3ba"
+checksum = "05764fac0b452c050eec49135de597223166590b9992ce9096628db06fce5ae3"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -7143,8 +7143,8 @@ dependencies = [
"swc_config",
"swc_ecma_ast",
"swc_ecma_codegen",
- "swc_ecma_minifier",
- "swc_ecma_parser",
+ "swc_ecma_minifier 42.0.0",
+ "swc_ecma_parser 32.0.0",
"swc_ecma_visit",
"swc_sourcemap",
"swc_timer",
@@ -7185,9 +7185,9 @@ dependencies = [
[[package]]
name = "swc_core"
-version = "50.2.3"
+version = "54.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76a4addc76896d859a57855961fd351486a900021aade516f7623969ed28958f"
+checksum = "0f361c932ffe029754a10512ee217f550109ee80dda1fe6ec90772440bfc0a68"
dependencies = [
"binding_macros",
"par-core",
@@ -7199,13 +7199,13 @@ dependencies = [
"swc_ecma_codegen",
"swc_ecma_lints",
"swc_ecma_loader",
- "swc_ecma_minifier",
- "swc_ecma_parser",
+ "swc_ecma_minifier 42.0.0",
+ "swc_ecma_parser 32.0.0",
"swc_ecma_preset_env",
"swc_ecma_quote_macros",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_transforms_module",
- "swc_ecma_transforms_optimization",
+ "swc_ecma_transforms_optimization 37.0.0",
"swc_ecma_transforms_proposal",
"swc_ecma_transforms_react",
"swc_ecma_transforms_testing",
@@ -7405,16 +7405,16 @@ dependencies = [
[[package]]
name = "swc_ecma_compat_bugfixes"
-version = "36.0.0"
+version = "39.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffa497fafc0c22684e1a6e8485d2586a838feaae5e3bf8d5ffad4780e6f014a7"
+checksum = "75920017b78c5231b47250c329930b76031f703ebfb14ead7b466f43374d6828"
dependencies = [
"rustc-hash 2.1.1",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_compat_es2015",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
"swc_trace_macro",
@@ -7423,9 +7423,9 @@ dependencies = [
[[package]]
name = "swc_ecma_compat_common"
-version = "27.0.0"
+version = "30.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33c7c4e235291ce0c48c07942eb7c53e82c2a870886def0f5acf4719bc4fa413"
+checksum = "fdb4c2493374ec4d5c456ebdf5de3168d56993147b3888ec47d837334898eb4b"
dependencies = [
"swc_common",
"swc_ecma_ast",
@@ -7435,9 +7435,9 @@ dependencies = [
[[package]]
name = "swc_ecma_compat_es2015"
-version = "36.0.0"
+version = "39.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a7742dc4ac2e86e68cf05fe6af8eaa94921bcc66f7a44b9ef9f6abc2fb77133"
+checksum = "2e654cd8be79bc1aba940135d600d975dcec9047e8935897db9f53fa8bd94c3e"
dependencies = [
"arrayvec 0.7.6",
"indexmap 2.9.0",
@@ -7451,7 +7451,7 @@ dependencies = [
"swc_config",
"swc_ecma_ast",
"swc_ecma_compat_common",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_transforms_classes",
"swc_ecma_transforms_macros",
"swc_ecma_utils",
@@ -7462,72 +7462,72 @@ dependencies = [
[[package]]
name = "swc_ecma_compat_es2016"
-version = "32.0.0"
+version = "35.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09ac6e606ae9e421efc01a885321aab8682311c22771c4dec832f2d1ef677558"
+checksum = "09ed1b54c134f5c972dabe6d153dab0ec26db0e6ca31ad7b9ec1824a46c595a0"
dependencies = [
"swc_ecma_ast",
"swc_ecma_transformer",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"tracing",
]
[[package]]
name = "swc_ecma_compat_es2017"
-version = "32.0.3"
+version = "35.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70c11fbac41835652ab00409b615ba85bc7724089b48dd660a0197a2b407d9a4"
+checksum = "34b18078fcaf7bf8f16d41d644383d97879bf6677aacbd97a2c4426b2a6c85dd"
dependencies = [
"serde",
"swc_common",
"swc_ecma_ast",
"swc_ecma_transformer",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"tracing",
]
[[package]]
name = "swc_ecma_compat_es2018"
-version = "33.0.2"
+version = "36.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4366a2623f4ed06cd2eafda2c67a9cf275ff783a0ec46195878becbb1c08490b"
+checksum = "25c6661887ef18abf36746dfc4aea1c6bb8f006656a1db11fbcaefd340764492"
dependencies = [
"serde",
"swc_ecma_ast",
"swc_ecma_transformer",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"tracing",
]
[[package]]
name = "swc_ecma_compat_es2019"
-version = "32.0.1"
+version = "35.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7e35dca28eeaa885ffc73278da7a74f41c2e73e78ef1374c359bc47ca198329"
+checksum = "0c6d2040d966a2db719193c13739765bb4026d746ed34767323fbc4ecc241dd4"
dependencies = [
"swc_common",
"swc_ecma_ast",
"swc_ecma_transformer",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"tracing",
]
[[package]]
name = "swc_ecma_compat_es2020"
-version = "34.1.0"
+version = "37.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3068c11d622f57b1fdecf12642e664cca08e2c523226a2dbe5475f62f157bc4e"
+checksum = "368df3e1713a0b2cecf51e1f5d2ed2f99367e21e6dbb3b4c1b7603664bc4854f"
dependencies = [
"serde",
"swc_common",
"swc_ecma_ast",
"swc_ecma_compat_es2022",
"swc_ecma_transformer",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
"tracing",
@@ -7535,30 +7535,29 @@ dependencies = [
[[package]]
name = "swc_ecma_compat_es2021"
-version = "32.0.0"
+version = "35.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a9f2bf21c585ec85c88defe56e6c9bb284a8f641e57ad7e9749d01c48f88022"
+checksum = "485ea701061eaa7934461dd4874deda80e6b0f254c672c232fc14a4e3b6e6008"
dependencies = [
"swc_ecma_ast",
- "swc_ecma_compiler",
- "swc_ecma_transforms_base",
+ "swc_ecma_transformer",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"tracing",
]
[[package]]
name = "swc_ecma_compat_es2022"
-version = "34.0.0"
+version = "37.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "844f22af73c628d17391de705f94f50c5fc90c60a95ea17758bd7b191bb42673"
+checksum = "ee87fab5c61b2147798343581ca60b5320362b82a0140faa080757efc6498d1e"
dependencies = [
"rustc-hash 2.1.1",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
- "swc_ecma_compat_common",
- "swc_ecma_compiler",
- "swc_ecma_transforms_base",
+ "swc_ecma_transformer",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_transforms_classes",
"swc_ecma_transforms_macros",
"swc_ecma_utils",
@@ -7581,24 +7580,6 @@ dependencies = [
"tracing",
]
-[[package]]
-name = "swc_ecma_compiler"
-version = "10.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31ac34b3192c252e1bc19b791dd5534d977d9d5eb5e708486e10327913849d74"
-dependencies = [
- "bitflags 2.9.1",
- "rustc-hash 2.1.1",
- "swc_atoms",
- "swc_common",
- "swc_ecma_ast",
- "swc_ecma_transforms_base",
- "swc_ecma_utils",
- "swc_ecma_visit",
- "swc_trace_macro",
- "tracing",
-]
-
[[package]]
name = "swc_ecma_ext_transforms"
version = "25.0.0"
@@ -7669,9 +7650,45 @@ dependencies = [
[[package]]
name = "swc_ecma_minifier"
-version = "38.0.1"
+version = "41.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22b43e723e5b7a2696379a6d888074791dee0e8234ce6ad5bf4c22a9d5a89779"
+dependencies = [
+ "arrayvec 0.7.6",
+ "bitflags 2.9.1",
+ "indexmap 2.9.0",
+ "num-bigint",
+ "num_cpus",
+ "once_cell",
+ "par-core",
+ "par-iter",
+ "parking_lot",
+ "phf",
+ "radix_fmt",
+ "rustc-hash 2.1.1",
+ "ryu-js",
+ "serde",
+ "serde_json",
+ "swc_atoms",
+ "swc_common",
+ "swc_config",
+ "swc_ecma_ast",
+ "swc_ecma_codegen",
+ "swc_ecma_parser 31.0.0",
+ "swc_ecma_transforms_base 34.0.0",
+ "swc_ecma_transforms_optimization 36.0.0",
+ "swc_ecma_usage_analyzer",
+ "swc_ecma_utils",
+ "swc_ecma_visit",
+ "swc_timer",
+ "tracing",
+]
+
+[[package]]
+name = "swc_ecma_minifier"
+version = "42.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5c8d16175fe77d4df22505885ae16770c72b440898a2c7d44126a599bdb8488"
+checksum = "e55ced848cae0fa5e33e465540bbeafd70645f4099b8b9b3ca66e66d95f52300"
dependencies = [
"arrayvec 0.7.6",
"bitflags 2.9.1",
@@ -7693,9 +7710,9 @@ dependencies = [
"swc_config",
"swc_ecma_ast",
"swc_ecma_codegen",
- "swc_ecma_parser",
- "swc_ecma_transforms_base",
- "swc_ecma_transforms_optimization",
+ "swc_ecma_parser 32.0.0",
+ "swc_ecma_transforms_base 35.0.0",
+ "swc_ecma_transforms_optimization 37.0.0",
"swc_ecma_usage_analyzer",
"swc_ecma_utils",
"swc_ecma_visit",
@@ -7705,9 +7722,30 @@ dependencies = [
[[package]]
name = "swc_ecma_parser"
-version = "29.0.2"
+version = "31.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0aea4fbe6610be2875bdaee934e2deefa19fe3edc1f9dca9bf2609bc37078f6"
+dependencies = [
+ "bitflags 2.9.1",
+ "either",
+ "num-bigint",
+ "phf",
+ "rustc-hash 2.1.1",
+ "seq-macro",
+ "serde",
+ "smartstring",
+ "stacker",
+ "swc_atoms",
+ "swc_common",
+ "swc_ecma_ast",
+ "tracing",
+]
+
+[[package]]
+name = "swc_ecma_parser"
+version = "32.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e63984b544fe1d8f66e9ce616e57429bb878572fcf1504851ef9d9f4f5260e2b"
+checksum = "e1d0c36843109fff178bbedc439b4190daa865d78e553134243a4df220329fdd"
dependencies = [
"bitflags 2.9.1",
"either",
@@ -7726,9 +7764,9 @@ dependencies = [
[[package]]
name = "swc_ecma_preset_env"
-version = "42.1.0"
+version = "45.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb20236b41713ff91c943fc9cd32bb107c652ab0469b56aac53aca53060cd5f5"
+checksum = "e4f1f6c815a26faa019a3629890c4ca8b6657b4b3682237fabf948899fa81e91"
dependencies = [
"anyhow",
"foldhash 0.1.5",
@@ -7743,18 +7781,17 @@ dependencies = [
"swc_atoms",
"swc_common",
"swc_ecma_ast",
- "swc_ecma_compiler",
"swc_ecma_transformer",
- "swc_ecma_transforms",
+ "swc_ecma_transforms 44.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
]
[[package]]
name = "swc_ecma_quote_macros"
-version = "29.0.0"
+version = "32.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "596dbb78166e8a0984f422fe3f635ef7f3dd94606af4cd4ad5d5cb0473b48025"
+checksum = "d70a7f12dd07015e00bf30770497de4ab45db2cf34ca9e870aece9e456166032"
dependencies = [
"anyhow",
"proc-macro2",
@@ -7763,7 +7800,7 @@ dependencies = [
"swc_atoms",
"swc_common",
"swc_ecma_ast",
- "swc_ecma_parser",
+ "swc_ecma_parser 32.0.0",
"swc_macros_common",
"syn 2.0.104",
]
@@ -7783,16 +7820,16 @@ dependencies = [
[[package]]
name = "swc_ecma_transformer"
-version = "3.2.1"
+version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f37ffd2b9ca4e37e7e9875a75f398218d8654f533308605b050147c113056d0"
+checksum = "12e2aaef402a685c66f7cfb983b7b218ca42cd3de15c8aaf912f10ea8cd77402"
dependencies = [
"rustc-hash 2.1.1",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_hooks",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
"tracing",
@@ -7800,17 +7837,30 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms"
-version = "41.0.0"
+version = "43.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "998e21c2114dc994aac843b9b91efaba98d2f40904fbed8e7e7539b96c664294"
+dependencies = [
+ "par-core",
+ "swc_common",
+ "swc_ecma_ast",
+ "swc_ecma_transforms_base 34.0.0",
+ "swc_ecma_utils",
+]
+
+[[package]]
+name = "swc_ecma_transforms"
+version = "44.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f849ffa819d540070671568c7d89fc7e9e5f28f7948dc6cc6f0cbcf3750974ca"
+checksum = "a3bba9c21bfc599f9a7aae0e2f9107b0b3156ee86bbdb796fd36ef8c265ff8c0"
dependencies = [
"par-core",
"swc_common",
"swc_ecma_ast",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_transforms_compat",
"swc_ecma_transforms_module",
- "swc_ecma_transforms_optimization",
+ "swc_ecma_transforms_optimization 37.0.0",
"swc_ecma_transforms_proposal",
"swc_ecma_transforms_react",
"swc_ecma_transforms_typescript",
@@ -7819,9 +7869,31 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_base"
-version = "32.0.0"
+version = "34.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3d611dda8b946fbd3854ee04e09539ab556370d329cdf45a6a5c040ac479f70"
+dependencies = [
+ "better_scoped_tls",
+ "indexmap 2.9.0",
+ "once_cell",
+ "par-core",
+ "phf",
+ "rustc-hash 2.1.1",
+ "serde",
+ "swc_atoms",
+ "swc_common",
+ "swc_ecma_ast",
+ "swc_ecma_parser 31.0.0",
+ "swc_ecma_utils",
+ "swc_ecma_visit",
+ "tracing",
+]
+
+[[package]]
+name = "swc_ecma_transforms_base"
+version = "35.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "499486ed875ba49af2f36d0d809f61ce6e42943a3aa1d97f592d96f10fe734cc"
+checksum = "83f9f0dee4466e6eeb7042f2a0fc6c84298dfa914baff5bcfb44beb9f24b215f"
dependencies = [
"better_scoped_tls",
"indexmap 2.9.0",
@@ -7833,7 +7905,7 @@ dependencies = [
"swc_atoms",
"swc_common",
"swc_ecma_ast",
- "swc_ecma_parser",
+ "swc_ecma_parser 32.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
"tracing",
@@ -7841,22 +7913,22 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_classes"
-version = "32.0.0"
+version = "35.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7366550e0cba7454da2ad4f95a02e5bb16d7013ec87fe9b33ad39e001096b1a1"
+checksum = "0dc00cb0ee27cb78fc719482a7bf5697b5052b878dbd5ba6b65249c1ddcf8832"
dependencies = [
"swc_common",
"swc_ecma_ast",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
]
[[package]]
name = "swc_ecma_transforms_compat"
-version = "37.0.0"
+version = "40.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0150fa9abef8caca0e5d8b2d13409f950faee77d5b234b0c4b508482dfc557aa"
+checksum = "91cbf17a99fdb5d8604a2c5109fa6980f59b9b5827e715358ec24249449e74d7"
dependencies = [
"indexmap 2.9.0",
"par-core",
@@ -7875,7 +7947,7 @@ dependencies = [
"swc_ecma_compat_es2021",
"swc_ecma_compat_es2022",
"swc_ecma_compat_es3",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
"tracing",
@@ -7895,9 +7967,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_module"
-version = "35.0.0"
+version = "38.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e08c588370af4860fd39707bc6b8bbfea717cabb8226a127c9b08788b388c95"
+checksum = "0a884810184e70414a38b7572c4ea6ef66fed036cfdcd9f9804ce83e14b1ad76"
dependencies = [
"Inflector",
"anyhow",
@@ -7914,8 +7986,8 @@ dependencies = [
"swc_config",
"swc_ecma_ast",
"swc_ecma_loader",
- "swc_ecma_parser",
- "swc_ecma_transforms_base",
+ "swc_ecma_parser 32.0.0",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
"tracing",
@@ -7923,9 +7995,33 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_optimization"
-version = "34.0.0"
+version = "36.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee215e841db9d94ca1e0d3339de1d57941fc30a4020d067a5648bc59cd3d2fed"
+dependencies = [
+ "bytes-str",
+ "dashmap 5.5.3",
+ "indexmap 2.9.0",
+ "once_cell",
+ "par-core",
+ "petgraph 0.7.1",
+ "rustc-hash 2.1.1",
+ "serde_json",
+ "swc_atoms",
+ "swc_common",
+ "swc_ecma_ast",
+ "swc_ecma_parser 31.0.0",
+ "swc_ecma_transforms_base 34.0.0",
+ "swc_ecma_utils",
+ "swc_ecma_visit",
+ "tracing",
+]
+
+[[package]]
+name = "swc_ecma_transforms_optimization"
+version = "37.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7678b46f04eff0937a86bf98b2db3235818e10cfd63590653482302a905c3911"
+checksum = "3bc5e01b1e5c696b4784a133a5ee28ca8b309800d858ab63509069df8fe30181"
dependencies = [
"bytes-str",
"dashmap 5.5.3",
@@ -7938,8 +8034,8 @@ dependencies = [
"swc_atoms",
"swc_common",
"swc_ecma_ast",
- "swc_ecma_parser",
- "swc_ecma_transforms_base",
+ "swc_ecma_parser 32.0.0",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
"tracing",
@@ -7947,9 +8043,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_proposal"
-version = "32.0.0"
+version = "35.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a457eb2cb09d3461f2ed8d7263f9980b021f10f638d92ef63e0beebfd9e5e9d"
+checksum = "8b8f121d69b5ec24359e611be11075734f0fd535b0ce2f8f2887b0cafb0166ef"
dependencies = [
"either",
"rustc-hash 2.1.1",
@@ -7957,7 +8053,7 @@ dependencies = [
"swc_atoms",
"swc_common",
"swc_ecma_ast",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_transforms_classes",
"swc_ecma_utils",
"swc_ecma_visit",
@@ -7965,9 +8061,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_react"
-version = "35.0.0"
+version = "38.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35d917bf232b72a3fee91b6eb281930264bab6ae1c20b311f5d27f9c918bb8ea"
+checksum = "652b77238937631359f964e53d53ca2b92f9fa29c6a28c9d3b054c44e18a17b9"
dependencies = [
"base64 0.22.1",
"bytes-str",
@@ -7981,17 +8077,17 @@ dependencies = [
"swc_common",
"swc_config",
"swc_ecma_ast",
- "swc_ecma_parser",
- "swc_ecma_transforms_base",
+ "swc_ecma_parser 32.0.0",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
]
[[package]]
name = "swc_ecma_transforms_testing"
-version = "35.0.0"
+version = "38.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29ec7851260522b2864983caa3fc7004ebb080842f49ee49a5461b779277378d"
+checksum = "0df902ecb7e522c8df33f11db02fbed22abd4809663490100e3ea66c103130dd"
dependencies = [
"ansi_term",
"anyhow",
@@ -8003,9 +8099,9 @@ dependencies = [
"swc_common",
"swc_ecma_ast",
"swc_ecma_codegen",
- "swc_ecma_parser",
+ "swc_ecma_parser 32.0.0",
"swc_ecma_testing",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
"swc_sourcemap",
@@ -8015,9 +8111,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_typescript"
-version = "35.0.0"
+version = "38.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1a8624ca5f7d96659ac43eb9d85b4afd8af00e4c17eba1685b557b1ad6285a3"
+checksum = "4c32bbafb42b8f0f2013e94a4b4981d643a645b1a7f9c20931cbca5bf78c1538"
dependencies = [
"bytes-str",
"rustc-hash 2.1.1",
@@ -8025,7 +8121,7 @@ dependencies = [
"swc_atoms",
"swc_common",
"swc_ecma_ast",
- "swc_ecma_transforms_base",
+ "swc_ecma_transforms_base 35.0.0",
"swc_ecma_transforms_react",
"swc_ecma_utils",
"swc_ecma_visit",
@@ -8033,9 +8129,9 @@ dependencies = [
[[package]]
name = "swc_ecma_usage_analyzer"
-version = "26.0.0"
+version = "27.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3eb7998f23f765b1fad1e8e015e9f58a0cdff31bfec783f708aa416e44bd5fd2"
+checksum = "2e5215c48b139422dcf54958d746b5e9a7b31bab8fc126f28e4049c4985505d5"
dependencies = [
"bitflags 2.9.1",
"indexmap 2.9.0",
@@ -8086,9 +8182,9 @@ dependencies = [
[[package]]
name = "swc_emotion"
-version = "0.109.0"
+version = "0.112.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d643c05e50c486f0ea35e34429fafbff05e8614a3be94546cebd7d1109d3b90a"
+checksum = "393f34d168e7e8db6ad2e24444877852ae547dde472fd5fd06486aa42e188528"
dependencies = [
"base64 0.22.1",
"byteorder",
@@ -8102,7 +8198,7 @@ dependencies = [
"swc_common",
"swc_ecma_ast",
"swc_ecma_codegen",
- "swc_ecma_transforms",
+ "swc_ecma_transforms 43.0.0",
"swc_ecma_utils",
"swc_ecma_visit",
"swc_sourcemap",
@@ -8222,9 +8318,9 @@ dependencies = [
[[package]]
name = "swc_relay"
-version = "0.79.0"
+version = "0.82.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af3e9c6539f28c45ec2f0e9623a98d9d76960521cebc68d6ed785e1164835a2e"
+checksum = "89143814dd6e13622d5135409fcf9c93a5e94588e6f065783ee2a76d2dd841a9"
dependencies = [
"once_cell",
"regex",
diff --git a/Cargo.toml b/Cargo.toml
index 586cae7195c6b..d30c55f6eefec 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -339,7 +339,7 @@ turbopack-trace-utils = { path = "turbopack/crates/turbopack-trace-utils" }
turbopack-wasm = { path = "turbopack/crates/turbopack-wasm" }
# SWC crates
-swc_core = { version = "50.2.3", features = [
+swc_core = { version = "54.0.0", features = [
"ecma_loader_lru",
"ecma_loader_parking_lot",
"parallel_rayon",
@@ -350,13 +350,13 @@ testing = "19.0.0"
# Keep consistent with preset_env_base through swc_core
browserslist-rs = "0.19.0"
mdxjs = "1.0.3"
-modularize_imports = "0.105.0"
-styled_components = "0.133.0"
-styled_jsx = "0.108.0"
-swc_emotion = "0.109.0"
-swc_relay = "0.79.0"
-react_remove_properties = "0.59.0"
-remove_console = "0.60.0"
+modularize_imports = "0.108.0"
+styled_components = "0.136.0"
+styled_jsx = "0.111.0"
+swc_emotion = "0.112.0"
+swc_relay = "0.82.0"
+react_remove_properties = "0.62.0"
+remove_console = "0.63.0"
preset_env_base = "6.0.0"
diff --git a/crates/next-custom-transforms/tests/loader/issue-30389/output.js b/crates/next-custom-transforms/tests/loader/issue-30389/output.js
index b16a885f5d2e8..0d9f43a4c0a42 100644
--- a/crates/next-custom-transforms/tests/loader/issue-30389/output.js
+++ b/crates/next-custom-transforms/tests/loader/issue-30389/output.js
@@ -1,3 +1,3 @@
-var _my_array;
var _ref;
+var _my_array;
var test = (_ref = (_my_array = my_array) === null || _my_array === void 0 ? void 0 : _my_array[0]) !== null && _ref !== void 0 ? _ref : [];
diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js
index 225fb3b1f69ae..a7ea43c6fab5a 100644
--- a/packages/next/taskfile.js
+++ b/packages/next/taskfile.js
@@ -2201,7 +2201,7 @@ export async function copy_ncced(task) {
export async function ncc(task, opts) {
await task
- .clear('src/compiled')
+ // .clear('src/compiled') // TODO: Re-enabled once npm issues are resolved.
.parallel(
[
'ncc_safe_stable_stringify',
@@ -2332,7 +2332,7 @@ export async function ncc(task, opts) {
'copy_babel_runtime',
'copy_vercel_og',
'copy_constants_browserify',
- 'copy_vendor_react',
+ // 'copy_vendor_react', // TODO: Re-enable once npm issues are resolved.
'ncc_sass_loader',
'ncc_jest_worker',
'ncc_edge_runtime_cookies',
diff --git a/turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/invalid-export-parse-error/issues/Parsing ecmascript source code failed-988b73.txt b/turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/invalid-export-parse-error/issues/Parsing ecmascript source code failed-9f1236.txt
similarity index 78%
rename from turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/invalid-export-parse-error/issues/Parsing ecmascript source code failed-988b73.txt
rename to turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/invalid-export-parse-error/issues/Parsing ecmascript source code failed-9f1236.txt
index 686ccaaacd603..002b7c7e6d893 100644
--- a/turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/invalid-export-parse-error/issues/Parsing ecmascript source code failed-988b73.txt
+++ b/turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/invalid-export-parse-error/issues/Parsing ecmascript source code failed-9f1236.txt
@@ -1,9 +1,9 @@
-error - [analysis] /turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/invalid-export-parse-error/input/invalid-export/broken.js:2:16 Parsing ecmascript source code failed
+error - [analysis] /turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/invalid-export-parse-error/input/invalid-export/broken.js:3:0 Parsing ecmascript source code failed
1 | export const a = 42
- + v
- 2 + export const b =
- + ^
- 3 |
+ 2 | export const b =
+ + v
+ 3 +
+ + ^
Expression expected
diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/5c1d0_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_5aaf1327.js b/turbopack/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/5c1d0_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_5aaf1327.js
index 630b9e1689826..2bbb26d2fabca 100644
--- a/turbopack/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/5c1d0_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_5aaf1327.js
+++ b/turbopack/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/5c1d0_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_5aaf1327.js
@@ -1168,8 +1168,8 @@ function loadChunkPath(sourceType, sourceData, chunkPath) {
/**
* Returns an absolute url to an asset.
*/ function resolvePathFromModule(moduleId) {
- var exported = this.r(moduleId);
var _ref;
+ var exported = this.r(moduleId);
return (_ref = exported === null || exported === void 0 ? void 0 : exported.default) !== null && _ref !== void 0 ? _ref : exported;
}
browserContextPrototype.R = resolvePathFromModule;
diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/780ce_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_5aaf1327.js.map b/turbopack/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/780ce_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_5aaf1327.js.map
index a7c93dcf80ca5..dacb7793cf909 100644
--- a/turbopack/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/780ce_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_5aaf1327.js.map
+++ b/turbopack/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/780ce_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_5aaf1327.js.map
@@ -3,7 +3,7 @@
"sources": [],
"sections": [
{"offset": {"line": 13, "column": 0}, "map": {"version":3,"sources":["turbopack:///[turbopack]/shared/runtime-utils.ts"],"sourcesContent":["/**\n * This file contains runtime types and functions that are shared between all\n * TurboPack ECMAScript runtimes.\n *\n * It will be prepended to the runtime code of each runtime.\n */\n\n/* eslint-disable @typescript-eslint/no-unused-vars */\n\n/// \n\ntype EsmNamespaceObject = Record\n\n// @ts-ignore Defined in `dev-base.ts`\ndeclare function getOrInstantiateModuleFromParent(\n id: ModuleId,\n sourceModule: M\n): M\n\nconst REEXPORTED_OBJECTS = new WeakMap()\n\n/**\n * Constructs the `__turbopack_context__` object for a module.\n */\nfunction Context(\n this: TurbopackBaseContext,\n module: Module,\n exports: Exports\n) {\n this.m = module\n // We need to store this here instead of accessing it from the module object to:\n // 1. Make it available to factories directly, since we rewrite `this` to\n // `__turbopack_context__.e` in CJS modules.\n // 2. Support async modules which rewrite `module.exports` to a promise, so we\n // can still access the original exports object from functions like\n // `esmExport`\n // Ideally we could find a new approach for async modules and drop this property altogether.\n this.e = exports\n}\nconst contextPrototype = Context.prototype as TurbopackBaseContext\n\ntype ModuleContextMap = Record\n\ninterface ModuleContextEntry {\n id: () => ModuleId\n module: () => any\n}\n\ninterface ModuleContext {\n // require call\n (moduleId: string): Exports | EsmNamespaceObject\n\n // async import call\n import(moduleId: string): Promise\n\n keys(): ModuleId[]\n\n resolve(moduleId: string): ModuleId\n}\n\ntype GetOrInstantiateModuleFromParent = (\n moduleId: M['id'],\n parentModule: M\n) => M\n\ndeclare function getOrInstantiateRuntimeModule(\n chunkPath: ChunkPath,\n moduleId: ModuleId\n): Module\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty\nconst toStringTag = typeof Symbol !== 'undefined' && Symbol.toStringTag\n\nfunction defineProp(\n obj: any,\n name: PropertyKey,\n options: PropertyDescriptor & ThisType\n) {\n if (!hasOwnProperty.call(obj, name)) Object.defineProperty(obj, name, options)\n}\n\nfunction getOverwrittenModule(\n moduleCache: ModuleCache,\n id: ModuleId\n): Module {\n let module = moduleCache[id]\n if (!module) {\n // This is invoked when a module is merged into another module, thus it wasn't invoked via\n // instantiateModule and the cache entry wasn't created yet.\n module = createModuleObject(id)\n moduleCache[id] = module\n }\n return module\n}\n\n/**\n * Creates the module object. Only done here to ensure all module objects have the same shape.\n */\nfunction createModuleObject(id: ModuleId): Module {\n return {\n exports: {},\n error: undefined,\n id,\n namespaceObject: undefined,\n }\n}\n\ntype BindingTag = 0\nconst BindingTag_Value = 0 as BindingTag\n\n// an arbitrary sequence of bindings as\n// - a prop name\n// - BindingTag_Value, a value to be bound directly, or\n// - 1 or 2 functions to bind as getters and sdetters\ntype EsmBindings = Array<\n string | BindingTag | (() => unknown) | ((v: unknown) => void) | unknown\n>\n\n/**\n * Adds the getters to the exports object.\n */\nfunction esm(exports: Exports, bindings: EsmBindings) {\n defineProp(exports, '__esModule', { value: true })\n if (toStringTag) defineProp(exports, toStringTag, { value: 'Module' })\n let i = 0\n while (i < bindings.length) {\n const propName = bindings[i++] as string\n const tagOrFunction = bindings[i++]\n if (typeof tagOrFunction === 'number') {\n if (tagOrFunction === BindingTag_Value) {\n defineProp(exports, propName, {\n value: bindings[i++],\n enumerable: true,\n writable: false,\n })\n } else {\n throw new Error(`unexpected tag: ${tagOrFunction}`)\n }\n } else {\n const getterFn = tagOrFunction as () => unknown\n if (typeof bindings[i] === 'function') {\n const setterFn = bindings[i++] as (v: unknown) => void\n defineProp(exports, propName, {\n get: getterFn,\n set: setterFn,\n enumerable: true,\n })\n } else {\n defineProp(exports, propName, {\n get: getterFn,\n enumerable: true,\n })\n }\n }\n }\n Object.seal(exports)\n}\n\n/**\n * Makes the module an ESM with exports\n */\nfunction esmExport(\n this: TurbopackBaseContext,\n bindings: EsmBindings,\n id: ModuleId | undefined\n) {\n let module: Module\n let exports: Module['exports']\n if (id != null) {\n module = getOverwrittenModule(this.c, id)\n exports = module.exports\n } else {\n module = this.m\n exports = this.e\n }\n module.namespaceObject = exports\n esm(exports, bindings)\n}\ncontextPrototype.s = esmExport\n\ntype ReexportedObjects = Record[]\nfunction ensureDynamicExports(\n module: Module,\n exports: Exports\n): ReexportedObjects {\n let reexportedObjects: ReexportedObjects | undefined =\n REEXPORTED_OBJECTS.get(module)\n\n if (!reexportedObjects) {\n REEXPORTED_OBJECTS.set(module, (reexportedObjects = []))\n module.exports = module.namespaceObject = new Proxy(exports, {\n get(target, prop) {\n if (\n hasOwnProperty.call(target, prop) ||\n prop === 'default' ||\n prop === '__esModule'\n ) {\n return Reflect.get(target, prop)\n }\n for (const obj of reexportedObjects!) {\n const value = Reflect.get(obj, prop)\n if (value !== undefined) return value\n }\n return undefined\n },\n ownKeys(target) {\n const keys = Reflect.ownKeys(target)\n for (const obj of reexportedObjects!) {\n for (const key of Reflect.ownKeys(obj)) {\n if (key !== 'default' && !keys.includes(key)) keys.push(key)\n }\n }\n return keys\n },\n })\n }\n return reexportedObjects\n}\n\n/**\n * Dynamically exports properties from an object\n */\nfunction dynamicExport(\n this: TurbopackBaseContext,\n object: Record,\n id: ModuleId | undefined\n) {\n let module: Module\n let exports: Exports\n if (id != null) {\n module = getOverwrittenModule(this.c, id)\n exports = module.exports\n } else {\n module = this.m\n exports = this.e\n }\n const reexportedObjects = ensureDynamicExports(module, exports)\n\n if (typeof object === 'object' && object !== null) {\n reexportedObjects.push(object)\n }\n}\ncontextPrototype.j = dynamicExport\n\nfunction exportValue(\n this: TurbopackBaseContext,\n value: any,\n id: ModuleId | undefined\n) {\n let module: Module\n if (id != null) {\n module = getOverwrittenModule(this.c, id)\n } else {\n module = this.m\n }\n module.exports = value\n}\ncontextPrototype.v = exportValue\n\nfunction exportNamespace(\n this: TurbopackBaseContext,\n namespace: any,\n id: ModuleId | undefined\n) {\n let module: Module\n if (id != null) {\n module = getOverwrittenModule(this.c, id)\n } else {\n module = this.m\n }\n module.exports = module.namespaceObject = namespace\n}\ncontextPrototype.n = exportNamespace\n\nfunction createGetter(obj: Record, key: string | symbol) {\n return () => obj[key]\n}\n\n/**\n * @returns prototype of the object\n */\nconst getProto: (obj: any) => any = Object.getPrototypeOf\n ? (obj) => Object.getPrototypeOf(obj)\n : (obj) => obj.__proto__\n\n/** Prototypes that are not expanded for exports */\nconst LEAF_PROTOTYPES = [null, getProto({}), getProto([]), getProto(getProto)]\n\n/**\n * @param raw\n * @param ns\n * @param allowExportDefault\n * * `false`: will have the raw module as default export\n * * `true`: will have the default property as default export\n */\nfunction interopEsm(\n raw: Exports,\n ns: EsmNamespaceObject,\n allowExportDefault?: boolean\n) {\n const bindings: EsmBindings = []\n let defaultLocation = -1\n for (\n let current = raw;\n (typeof current === 'object' || typeof current === 'function') &&\n !LEAF_PROTOTYPES.includes(current);\n current = getProto(current)\n ) {\n for (const key of Object.getOwnPropertyNames(current)) {\n bindings.push(key, createGetter(raw, key))\n if (defaultLocation === -1 && key === 'default') {\n defaultLocation = bindings.length - 1\n }\n }\n }\n\n // this is not really correct\n // we should set the `default` getter if the imported module is a `.cjs file`\n if (!(allowExportDefault && defaultLocation >= 0)) {\n // Replace the binding with one for the namespace itself in order to preserve iteration order.\n if (defaultLocation >= 0) {\n // Replace the getter with the value\n bindings.splice(defaultLocation, 1, BindingTag_Value, raw)\n } else {\n bindings.push('default', BindingTag_Value, raw)\n }\n }\n\n esm(ns, bindings)\n return ns\n}\n\nfunction createNS(raw: Module['exports']): EsmNamespaceObject {\n if (typeof raw === 'function') {\n return function (this: any, ...args: any[]) {\n return raw.apply(this, args)\n }\n } else {\n return Object.create(null)\n }\n}\n\nfunction esmImport(\n this: TurbopackBaseContext,\n id: ModuleId\n): Exclude {\n const module = getOrInstantiateModuleFromParent(id, this.m)\n\n // any ES module has to have `module.namespaceObject` defined.\n if (module.namespaceObject) return module.namespaceObject\n\n // only ESM can be an async module, so we don't need to worry about exports being a promise here.\n const raw = module.exports\n return (module.namespaceObject = interopEsm(\n raw,\n createNS(raw),\n raw && (raw as any).__esModule\n ))\n}\ncontextPrototype.i = esmImport\n\nfunction asyncLoader(\n this: TurbopackBaseContext,\n moduleId: ModuleId\n): Promise {\n const loader = this.r(moduleId) as (\n importFunction: EsmImport\n ) => Promise\n return loader(esmImport.bind(this))\n}\ncontextPrototype.A = asyncLoader\n\n// Add a simple runtime require so that environments without one can still pass\n// `typeof require` CommonJS checks so that exports are correctly registered.\nconst runtimeRequire =\n // @ts-ignore\n typeof require === 'function'\n ? // @ts-ignore\n require\n : function require() {\n throw new Error('Unexpected use of runtime require')\n }\ncontextPrototype.t = runtimeRequire\n\nfunction commonJsRequire(\n this: TurbopackBaseContext,\n id: ModuleId\n): Exports {\n return getOrInstantiateModuleFromParent(id, this.m).exports\n}\ncontextPrototype.r = commonJsRequire\n\n/**\n * Remove fragments and query parameters since they are never part of the context map keys\n *\n * This matches how we parse patterns at resolving time. Arguably we should only do this for\n * strings passed to `import` but the resolve does it for `import` and `require` and so we do\n * here as well.\n */\nfunction parseRequest(request: string): string {\n // Per the URI spec fragments can contain `?` characters, so we should trim it off first\n // https://datatracker.ietf.org/doc/html/rfc3986#section-3.5\n const hashIndex = request.indexOf('#')\n if (hashIndex !== -1) {\n request = request.substring(0, hashIndex)\n }\n\n const queryIndex = request.indexOf('?')\n if (queryIndex !== -1) {\n request = request.substring(0, queryIndex)\n }\n\n return request\n}\n/**\n * `require.context` and require/import expression runtime.\n */\nfunction moduleContext(map: ModuleContextMap): ModuleContext {\n function moduleContext(id: string): Exports {\n id = parseRequest(id)\n if (hasOwnProperty.call(map, id)) {\n return map[id].module()\n }\n\n const e = new Error(`Cannot find module '${id}'`)\n ;(e as any).code = 'MODULE_NOT_FOUND'\n throw e\n }\n\n moduleContext.keys = (): string[] => {\n return Object.keys(map)\n }\n\n moduleContext.resolve = (id: string): ModuleId => {\n id = parseRequest(id)\n if (hasOwnProperty.call(map, id)) {\n return map[id].id()\n }\n\n const e = new Error(`Cannot find module '${id}'`)\n ;(e as any).code = 'MODULE_NOT_FOUND'\n throw e\n }\n\n moduleContext.import = async (id: string) => {\n return await (moduleContext(id) as Promise)\n }\n\n return moduleContext\n}\ncontextPrototype.f = moduleContext\n\n/**\n * Returns the path of a chunk defined by its data.\n */\nfunction getChunkPath(chunkData: ChunkData): ChunkPath {\n return typeof chunkData === 'string' ? chunkData : chunkData.path\n}\n\nfunction isPromise(maybePromise: any): maybePromise is Promise {\n return (\n maybePromise != null &&\n typeof maybePromise === 'object' &&\n 'then' in maybePromise &&\n typeof maybePromise.then === 'function'\n )\n}\n\nfunction isAsyncModuleExt(obj: T): obj is AsyncModuleExt & T {\n return turbopackQueues in obj\n}\n\nfunction createPromise() {\n let resolve: (value: T | PromiseLike) => void\n let reject: (reason?: any) => void\n\n const promise = new Promise((res, rej) => {\n reject = rej\n resolve = res\n })\n\n return {\n promise,\n resolve: resolve!,\n reject: reject!,\n }\n}\n\n// Load the CompressedmoduleFactories of a chunk into the `moduleFactories` Map.\n// The CompressedModuleFactories format is\n// - 1 or more module ids\n// - a module factory function\n// So walking this is a little complex but the flat structure is also fast to\n// traverse, we can use `typeof` operators to distinguish the two cases.\nfunction installCompressedModuleFactories(\n chunkModules: CompressedModuleFactories,\n offset: number,\n moduleFactories: ModuleFactories,\n newModuleId?: (id: ModuleId) => void\n) {\n let i = offset\n while (i < chunkModules.length) {\n let moduleId = chunkModules[i] as ModuleId\n let end = i + 1\n // Find our factory function\n while (\n end < chunkModules.length &&\n typeof chunkModules[end] !== 'function'\n ) {\n end++\n }\n if (end === chunkModules.length) {\n throw new Error('malformed chunk format, expected a factory function')\n }\n // Each chunk item has a 'primary id' and optional additional ids. If the primary id is already\n // present we know all the additional ids are also present, so we don't need to check.\n if (!moduleFactories.has(moduleId)) {\n const moduleFactoryFn = chunkModules[end] as Function\n applyModuleFactoryName(moduleFactoryFn)\n newModuleId?.(moduleId)\n for (; i < end; i++) {\n moduleId = chunkModules[i] as ModuleId\n moduleFactories.set(moduleId, moduleFactoryFn)\n }\n }\n i = end + 1 // end is pointing at the last factory advance to the next id or the end of the array.\n }\n}\n\n// everything below is adapted from webpack\n// https://github.com/webpack/webpack/blob/6be4065ade1e252c1d8dcba4af0f43e32af1bdc1/lib/runtime/AsyncModuleRuntimeModule.js#L13\n\nconst turbopackQueues = Symbol('turbopack queues')\nconst turbopackExports = Symbol('turbopack exports')\nconst turbopackError = Symbol('turbopack error')\n\nconst enum QueueStatus {\n Unknown = -1,\n Unresolved = 0,\n Resolved = 1,\n}\n\ntype AsyncQueueFn = (() => void) & { queueCount: number }\ntype AsyncQueue = AsyncQueueFn[] & {\n status: QueueStatus\n}\n\nfunction resolveQueue(queue?: AsyncQueue) {\n if (queue && queue.status !== QueueStatus.Resolved) {\n queue.status = QueueStatus.Resolved\n queue.forEach((fn) => fn.queueCount--)\n queue.forEach((fn) => (fn.queueCount-- ? fn.queueCount++ : fn()))\n }\n}\n\ntype Dep = Exports | AsyncModulePromise | Promise\n\ntype AsyncModuleExt = {\n [turbopackQueues]: (fn: (queue: AsyncQueue) => void) => void\n [turbopackExports]: Exports\n [turbopackError]?: any\n}\n\ntype AsyncModulePromise = Promise & AsyncModuleExt\n\nfunction wrapDeps(deps: Dep[]): AsyncModuleExt[] {\n return deps.map((dep): AsyncModuleExt => {\n if (dep !== null && typeof dep === 'object') {\n if (isAsyncModuleExt(dep)) return dep\n if (isPromise(dep)) {\n const queue: AsyncQueue = Object.assign([], {\n status: QueueStatus.Unresolved,\n })\n\n const obj: AsyncModuleExt = {\n [turbopackExports]: {},\n [turbopackQueues]: (fn: (queue: AsyncQueue) => void) => fn(queue),\n }\n\n dep.then(\n (res) => {\n obj[turbopackExports] = res\n resolveQueue(queue)\n },\n (err) => {\n obj[turbopackError] = err\n resolveQueue(queue)\n }\n )\n\n return obj\n }\n }\n\n return {\n [turbopackExports]: dep,\n [turbopackQueues]: () => {},\n }\n })\n}\n\nfunction asyncModule(\n this: TurbopackBaseContext,\n body: (\n handleAsyncDependencies: (\n deps: Dep[]\n ) => Exports[] | Promise<() => Exports[]>,\n asyncResult: (err?: any) => void\n ) => void,\n hasAwait: boolean\n) {\n const module = this.m\n const queue: AsyncQueue | undefined = hasAwait\n ? Object.assign([], { status: QueueStatus.Unknown })\n : undefined\n\n const depQueues: Set = new Set()\n\n const { resolve, reject, promise: rawPromise } = createPromise()\n\n const promise: AsyncModulePromise = Object.assign(rawPromise, {\n [turbopackExports]: module.exports,\n [turbopackQueues]: (fn) => {\n queue && fn(queue)\n depQueues.forEach(fn)\n promise['catch'](() => {})\n },\n } satisfies AsyncModuleExt)\n\n const attributes: PropertyDescriptor = {\n get(): any {\n return promise\n },\n set(v: any) {\n // Calling `esmExport` leads to this.\n if (v !== promise) {\n promise[turbopackExports] = v\n }\n },\n }\n\n Object.defineProperty(module, 'exports', attributes)\n Object.defineProperty(module, 'namespaceObject', attributes)\n\n function handleAsyncDependencies(deps: Dep[]) {\n const currentDeps = wrapDeps(deps)\n\n const getResult = () =>\n currentDeps.map((d) => {\n if (d[turbopackError]) throw d[turbopackError]\n return d[turbopackExports]\n })\n\n const { promise, resolve } = createPromise<() => Exports[]>()\n\n const fn: AsyncQueueFn = Object.assign(() => resolve(getResult), {\n queueCount: 0,\n })\n\n function fnQueue(q: AsyncQueue) {\n if (q !== queue && !depQueues.has(q)) {\n depQueues.add(q)\n if (q && q.status === QueueStatus.Unresolved) {\n fn.queueCount++\n q.push(fn)\n }\n }\n }\n\n currentDeps.map((dep) => dep[turbopackQueues](fnQueue))\n\n return fn.queueCount ? promise : getResult()\n }\n\n function asyncResult(err?: any) {\n if (err) {\n reject((promise[turbopackError] = err))\n } else {\n resolve(promise[turbopackExports])\n }\n\n resolveQueue(queue)\n }\n\n body(handleAsyncDependencies, asyncResult)\n\n if (queue && queue.status === QueueStatus.Unknown) {\n queue.status = QueueStatus.Unresolved\n }\n}\ncontextPrototype.a = asyncModule\n\n/**\n * A pseudo \"fake\" URL object to resolve to its relative path.\n *\n * When UrlRewriteBehavior is set to relative, calls to the `new URL()` will construct url without base using this\n * runtime function to generate context-agnostic urls between different rendering context, i.e ssr / client to avoid\n * hydration mismatch.\n *\n * This is based on webpack's existing implementation:\n * https://github.com/webpack/webpack/blob/87660921808566ef3b8796f8df61bd79fc026108/lib/runtime/RelativeUrlRuntimeModule.js\n */\nconst relativeURL = function relativeURL(this: any, inputUrl: string) {\n const realUrl = new URL(inputUrl, 'x:/')\n const values: Record = {}\n for (const key in realUrl) values[key] = (realUrl as any)[key]\n values.href = inputUrl\n values.pathname = inputUrl.replace(/[?#].*/, '')\n values.origin = values.protocol = ''\n values.toString = values.toJSON = (..._args: Array) => inputUrl\n for (const key in values)\n Object.defineProperty(this, key, {\n enumerable: true,\n configurable: true,\n value: values[key],\n })\n}\nrelativeURL.prototype = URL.prototype\ncontextPrototype.U = relativeURL\n\n/**\n * Utility function to ensure all variants of an enum are handled.\n */\nfunction invariant(never: never, computeMessage: (arg: any) => string): never {\n throw new Error(`Invariant: ${computeMessage(never)}`)\n}\n\n/**\n * A stub function to make `require` available but non-functional in ESM.\n */\nfunction requireStub(_moduleId: ModuleId): never {\n throw new Error('dynamic usage of require is not supported')\n}\ncontextPrototype.z = requireStub\n\n// Make `globalThis` available to the module in a way that cannot be shadowed by a local variable.\ncontextPrototype.g = globalThis\n\ntype ContextConstructor = {\n new (module: Module, exports: Exports): TurbopackBaseContext\n}\n\nfunction applyModuleFactoryName(factory: Function) {\n // Give the module factory a nice name to improve stack traces.\n Object.defineProperty(factory, 'name', {\n value: 'module evaluation',\n })\n}\n"],"names":[],"mappings":"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","ignoreList":[0]}},
- {"offset": {"line": 762, "column": 0}, "map": {"version":3,"sources":["turbopack:///[turbopack]/browser/runtime/base/runtime-base.ts"],"sourcesContent":["/**\n * This file contains runtime types and functions that are shared between all\n * Turbopack *development* ECMAScript runtimes.\n *\n * It will be appended to the runtime code of each runtime right after the\n * shared runtime utils.\n */\n\n/* eslint-disable @typescript-eslint/no-unused-vars */\n\n/// \n/// \n\n// Used in WebWorkers to tell the runtime about the chunk base path\ndeclare var TURBOPACK_WORKER_LOCATION: string\n// Used in WebWorkers to tell the runtime about the chunk suffix\ndeclare var TURBOPACK_CHUNK_SUFFIX: string\n// Used in WebWorkers to tell the runtime about the current chunk url since it can't be detected via document.currentScript\n// Note it's stored in reversed order to use push and pop\ndeclare var TURBOPACK_NEXT_CHUNK_URLS: ChunkUrl[] | undefined\n\n// Injected by rust code\ndeclare var CHUNK_BASE_PATH: string\ndeclare var CHUNK_SUFFIX: string\n\ninterface TurbopackBrowserBaseContext extends TurbopackBaseContext {\n R: ResolvePathFromModule\n}\n\nconst browserContextPrototype =\n Context.prototype as TurbopackBrowserBaseContext\n\n// Provided by build or dev base\ndeclare function instantiateModule(\n id: ModuleId,\n sourceType: SourceType,\n sourceData: SourceData\n): Module\n\ntype RuntimeParams = {\n otherChunks: ChunkData[]\n runtimeModuleIds: ModuleId[]\n}\n\ntype ChunkRegistration = [\n chunkPath: ChunkScript,\n ...([RuntimeParams] | CompressedModuleFactories),\n]\n\ntype ChunkList = {\n script: ChunkListScript\n chunks: ChunkData[]\n source: 'entry' | 'dynamic'\n}\n\nenum SourceType {\n /**\n * The module was instantiated because it was included in an evaluated chunk's\n * runtime.\n * SourceData is a ChunkPath.\n */\n Runtime = 0,\n /**\n * The module was instantiated because a parent module imported it.\n * SourceData is a ModuleId.\n */\n Parent = 1,\n /**\n * The module was instantiated because it was included in a chunk's hot module\n * update.\n * SourceData is an array of ModuleIds or undefined.\n */\n Update = 2,\n}\n\ntype SourceData = ChunkPath | ModuleId | ModuleId[] | undefined\ninterface RuntimeBackend {\n registerChunk: (chunkPath: ChunkPath, params?: RuntimeParams) => void\n /**\n * Returns the same Promise for the same chunk URL.\n */\n loadChunkCached: (sourceType: SourceType, chunkUrl: ChunkUrl) => Promise\n loadWebAssembly: (\n sourceType: SourceType,\n sourceData: SourceData,\n wasmChunkPath: ChunkPath,\n edgeModule: () => WebAssembly.Module,\n importsObj: WebAssembly.Imports\n ) => Promise\n loadWebAssemblyModule: (\n sourceType: SourceType,\n sourceData: SourceData,\n wasmChunkPath: ChunkPath,\n edgeModule: () => WebAssembly.Module\n ) => Promise\n}\n\ninterface DevRuntimeBackend {\n reloadChunk?: (chunkUrl: ChunkUrl) => Promise\n unloadChunk?: (chunkUrl: ChunkUrl) => void\n restart: () => void\n}\n\nconst moduleFactories: ModuleFactories = new Map()\ncontextPrototype.M = moduleFactories\n\nconst availableModules: Map | true> = new Map()\n\nconst availableModuleChunks: Map | true> = new Map()\n\nfunction factoryNotAvailableMessage(\n moduleId: ModuleId,\n sourceType: SourceType,\n sourceData: SourceData\n): string {\n let instantiationReason\n switch (sourceType) {\n case SourceType.Runtime:\n instantiationReason = `as a runtime entry of chunk ${sourceData}`\n break\n case SourceType.Parent:\n instantiationReason = `because it was required from module ${sourceData}`\n break\n case SourceType.Update:\n instantiationReason = 'because of an HMR update'\n break\n default:\n invariant(\n sourceType,\n (sourceType) => `Unknown source type: ${sourceType}`\n )\n }\n return `Module ${moduleId} was instantiated ${instantiationReason}, but the module factory is not available.`\n}\n\nfunction loadChunk(\n this: TurbopackBrowserBaseContext,\n chunkData: ChunkData\n): Promise {\n return loadChunkInternal(SourceType.Parent, this.m.id, chunkData)\n}\nbrowserContextPrototype.l = loadChunk\n\nfunction loadInitialChunk(chunkPath: ChunkPath, chunkData: ChunkData) {\n return loadChunkInternal(SourceType.Runtime, chunkPath, chunkData)\n}\n\nasync function loadChunkInternal(\n sourceType: SourceType,\n sourceData: SourceData,\n chunkData: ChunkData\n): Promise {\n if (typeof chunkData === 'string') {\n return loadChunkPath(sourceType, sourceData, chunkData)\n }\n\n const includedList = chunkData.included || []\n const modulesPromises = includedList.map((included) => {\n if (moduleFactories.has(included)) return true\n return availableModules.get(included)\n })\n if (modulesPromises.length > 0 && modulesPromises.every((p) => p)) {\n // When all included items are already loaded or loading, we can skip loading ourselves\n await Promise.all(modulesPromises)\n return\n }\n\n const includedModuleChunksList = chunkData.moduleChunks || []\n const moduleChunksPromises = includedModuleChunksList\n .map((included) => {\n // TODO(alexkirsz) Do we need this check?\n // if (moduleFactories[included]) return true;\n return availableModuleChunks.get(included)\n })\n .filter((p) => p)\n\n let promise: Promise\n if (moduleChunksPromises.length > 0) {\n // Some module chunks are already loaded or loading.\n\n if (moduleChunksPromises.length === includedModuleChunksList.length) {\n // When all included module chunks are already loaded or loading, we can skip loading ourselves\n await Promise.all(moduleChunksPromises)\n return\n }\n\n const moduleChunksToLoad: Set = new Set()\n for (const moduleChunk of includedModuleChunksList) {\n if (!availableModuleChunks.has(moduleChunk)) {\n moduleChunksToLoad.add(moduleChunk)\n }\n }\n\n for (const moduleChunkToLoad of moduleChunksToLoad) {\n const promise = loadChunkPath(sourceType, sourceData, moduleChunkToLoad)\n\n availableModuleChunks.set(moduleChunkToLoad, promise)\n\n moduleChunksPromises.push(promise)\n }\n\n promise = Promise.all(moduleChunksPromises)\n } else {\n promise = loadChunkPath(sourceType, sourceData, chunkData.path)\n\n // Mark all included module chunks as loading if they are not already loaded or loading.\n for (const includedModuleChunk of includedModuleChunksList) {\n if (!availableModuleChunks.has(includedModuleChunk)) {\n availableModuleChunks.set(includedModuleChunk, promise)\n }\n }\n }\n\n for (const included of includedList) {\n if (!availableModules.has(included)) {\n // It might be better to race old and new promises, but it's rare that the new promise will be faster than a request started earlier.\n // In production it's even more rare, because the chunk optimization tries to deduplicate modules anyway.\n availableModules.set(included, promise)\n }\n }\n\n await promise\n}\n\nconst loadedChunk = Promise.resolve(undefined)\nconst instrumentedBackendLoadChunks = new WeakMap<\n Promise,\n Promise | typeof loadedChunk\n>()\n// Do not make this async. React relies on referential equality of the returned Promise.\nfunction loadChunkByUrl(\n this: TurbopackBrowserBaseContext,\n chunkUrl: ChunkUrl\n) {\n return loadChunkByUrlInternal(SourceType.Parent, this.m.id, chunkUrl)\n}\nbrowserContextPrototype.L = loadChunkByUrl\n\n// Do not make this async. React relies on referential equality of the returned Promise.\nfunction loadChunkByUrlInternal(\n sourceType: SourceType,\n sourceData: SourceData,\n chunkUrl: ChunkUrl\n): Promise {\n const thenable = BACKEND.loadChunkCached(sourceType, chunkUrl)\n let entry = instrumentedBackendLoadChunks.get(thenable)\n if (entry === undefined) {\n const resolve = instrumentedBackendLoadChunks.set.bind(\n instrumentedBackendLoadChunks,\n thenable,\n loadedChunk\n )\n entry = thenable.then(resolve).catch((cause) => {\n let loadReason: string\n switch (sourceType) {\n case SourceType.Runtime:\n loadReason = `as a runtime dependency of chunk ${sourceData}`\n break\n case SourceType.Parent:\n loadReason = `from module ${sourceData}`\n break\n case SourceType.Update:\n loadReason = 'from an HMR update'\n break\n default:\n invariant(\n sourceType,\n (sourceType) => `Unknown source type: ${sourceType}`\n )\n }\n let error = new Error(\n `Failed to load chunk ${chunkUrl} ${loadReason}${\n cause ? `: ${cause}` : ''\n }`,\n cause ? { cause } : undefined\n )\n error.name = 'ChunkLoadError'\n throw error\n })\n instrumentedBackendLoadChunks.set(thenable, entry)\n }\n\n return entry\n}\n\n// Do not make this async. React relies on referential equality of the returned Promise.\nfunction loadChunkPath(\n sourceType: SourceType,\n sourceData: SourceData,\n chunkPath: ChunkPath\n): Promise {\n const url = getChunkRelativeUrl(chunkPath)\n return loadChunkByUrlInternal(sourceType, sourceData, url)\n}\n\n/**\n * Returns an absolute url to an asset.\n */\nfunction resolvePathFromModule(\n this: TurbopackBaseContext,\n moduleId: string\n): string {\n const exported = this.r(moduleId)\n return exported?.default ?? exported\n}\nbrowserContextPrototype.R = resolvePathFromModule\n\n/**\n * no-op for browser\n * @param modulePath\n */\nfunction resolveAbsolutePath(modulePath?: string): string {\n return `/ROOT/${modulePath ?? ''}`\n}\nbrowserContextPrototype.P = resolveAbsolutePath\n\n/**\n * Returns a blob URL for the worker.\n * @param chunks list of chunks to load\n */\nfunction getWorkerBlobURL(chunks: ChunkPath[]): string {\n // It is important to reverse the array so when bootstrapping we can infer what chunk is being\n // evaluated by poping urls off of this array. See `getPathFromScript`\n let bootstrap = `self.TURBOPACK_WORKER_LOCATION = ${JSON.stringify(location.origin)};\nself.TURBOPACK_CHUNK_SUFFIX = ${JSON.stringify(CHUNK_SUFFIX)};\nself.TURBOPACK_NEXT_CHUNK_URLS = ${JSON.stringify(chunks.reverse().map(getChunkRelativeUrl), null, 2)};\nimportScripts(...self.TURBOPACK_NEXT_CHUNK_URLS.map(c => self.TURBOPACK_WORKER_LOCATION + c).reverse());`\n let blob = new Blob([bootstrap], { type: 'text/javascript' })\n return URL.createObjectURL(blob)\n}\nbrowserContextPrototype.b = getWorkerBlobURL\n\n/**\n * Instantiates a runtime module.\n */\nfunction instantiateRuntimeModule(\n moduleId: ModuleId,\n chunkPath: ChunkPath\n): Module {\n return instantiateModule(moduleId, SourceType.Runtime, chunkPath)\n}\n/**\n * Returns the URL relative to the origin where a chunk can be fetched from.\n */\nfunction getChunkRelativeUrl(chunkPath: ChunkPath | ChunkListPath): ChunkUrl {\n return `${CHUNK_BASE_PATH}${chunkPath\n .split('/')\n .map((p) => encodeURIComponent(p))\n .join('/')}${CHUNK_SUFFIX}` as ChunkUrl\n}\n\n/**\n * Return the ChunkPath from a ChunkScript.\n */\nfunction getPathFromScript(chunkScript: ChunkPath | ChunkScript): ChunkPath\nfunction getPathFromScript(\n chunkScript: ChunkListPath | ChunkListScript\n): ChunkListPath\nfunction getPathFromScript(\n chunkScript: ChunkPath | ChunkListPath | ChunkScript | ChunkListScript\n): ChunkPath | ChunkListPath {\n if (typeof chunkScript === 'string') {\n return chunkScript as ChunkPath | ChunkListPath\n }\n const chunkUrl =\n typeof TURBOPACK_NEXT_CHUNK_URLS !== 'undefined'\n ? TURBOPACK_NEXT_CHUNK_URLS.pop()!\n : chunkScript.getAttribute('src')!\n const src = decodeURIComponent(chunkUrl.replace(/[?#].*$/, ''))\n const path = src.startsWith(CHUNK_BASE_PATH)\n ? src.slice(CHUNK_BASE_PATH.length)\n : src\n return path as ChunkPath | ChunkListPath\n}\n\nconst regexJsUrl = /\\.js(?:\\?[^#]*)?(?:#.*)?$/\n/**\n * Checks if a given path/URL ends with .js, optionally followed by ?query or #fragment.\n */\nfunction isJs(chunkUrlOrPath: ChunkUrl | ChunkPath): boolean {\n return regexJsUrl.test(chunkUrlOrPath)\n}\n\nconst regexCssUrl = /\\.css(?:\\?[^#]*)?(?:#.*)?$/\n/**\n * Checks if a given path/URL ends with .css, optionally followed by ?query or #fragment.\n */\nfunction isCss(chunkUrl: ChunkUrl): boolean {\n return regexCssUrl.test(chunkUrl)\n}\n\nfunction loadWebAssembly(\n this: TurbopackBaseContext,\n chunkPath: ChunkPath,\n edgeModule: () => WebAssembly.Module,\n importsObj: WebAssembly.Imports\n): Promise {\n return BACKEND.loadWebAssembly(\n SourceType.Parent,\n this.m.id,\n chunkPath,\n edgeModule,\n importsObj\n )\n}\ncontextPrototype.w = loadWebAssembly\n\nfunction loadWebAssemblyModule(\n this: TurbopackBaseContext,\n chunkPath: ChunkPath,\n edgeModule: () => WebAssembly.Module\n): Promise {\n return BACKEND.loadWebAssemblyModule(\n SourceType.Parent,\n this.m.id,\n chunkPath,\n edgeModule\n )\n}\ncontextPrototype.u = loadWebAssemblyModule\n"],"names":[],"mappings":"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","ignoreList":[0]}},
+ {"offset": {"line": 762, "column": 0}, "map": {"version":3,"sources":["turbopack:///[turbopack]/browser/runtime/base/runtime-base.ts"],"sourcesContent":["/**\n * This file contains runtime types and functions that are shared between all\n * Turbopack *development* ECMAScript runtimes.\n *\n * It will be appended to the runtime code of each runtime right after the\n * shared runtime utils.\n */\n\n/* eslint-disable @typescript-eslint/no-unused-vars */\n\n/// \n/// \n\n// Used in WebWorkers to tell the runtime about the chunk base path\ndeclare var TURBOPACK_WORKER_LOCATION: string\n// Used in WebWorkers to tell the runtime about the chunk suffix\ndeclare var TURBOPACK_CHUNK_SUFFIX: string\n// Used in WebWorkers to tell the runtime about the current chunk url since it can't be detected via document.currentScript\n// Note it's stored in reversed order to use push and pop\ndeclare var TURBOPACK_NEXT_CHUNK_URLS: ChunkUrl[] | undefined\n\n// Injected by rust code\ndeclare var CHUNK_BASE_PATH: string\ndeclare var CHUNK_SUFFIX: string\n\ninterface TurbopackBrowserBaseContext extends TurbopackBaseContext {\n R: ResolvePathFromModule\n}\n\nconst browserContextPrototype =\n Context.prototype as TurbopackBrowserBaseContext\n\n// Provided by build or dev base\ndeclare function instantiateModule(\n id: ModuleId,\n sourceType: SourceType,\n sourceData: SourceData\n): Module\n\ntype RuntimeParams = {\n otherChunks: ChunkData[]\n runtimeModuleIds: ModuleId[]\n}\n\ntype ChunkRegistration = [\n chunkPath: ChunkScript,\n ...([RuntimeParams] | CompressedModuleFactories),\n]\n\ntype ChunkList = {\n script: ChunkListScript\n chunks: ChunkData[]\n source: 'entry' | 'dynamic'\n}\n\nenum SourceType {\n /**\n * The module was instantiated because it was included in an evaluated chunk's\n * runtime.\n * SourceData is a ChunkPath.\n */\n Runtime = 0,\n /**\n * The module was instantiated because a parent module imported it.\n * SourceData is a ModuleId.\n */\n Parent = 1,\n /**\n * The module was instantiated because it was included in a chunk's hot module\n * update.\n * SourceData is an array of ModuleIds or undefined.\n */\n Update = 2,\n}\n\ntype SourceData = ChunkPath | ModuleId | ModuleId[] | undefined\ninterface RuntimeBackend {\n registerChunk: (chunkPath: ChunkPath, params?: RuntimeParams) => void\n /**\n * Returns the same Promise for the same chunk URL.\n */\n loadChunkCached: (sourceType: SourceType, chunkUrl: ChunkUrl) => Promise\n loadWebAssembly: (\n sourceType: SourceType,\n sourceData: SourceData,\n wasmChunkPath: ChunkPath,\n edgeModule: () => WebAssembly.Module,\n importsObj: WebAssembly.Imports\n ) => Promise\n loadWebAssemblyModule: (\n sourceType: SourceType,\n sourceData: SourceData,\n wasmChunkPath: ChunkPath,\n edgeModule: () => WebAssembly.Module\n ) => Promise\n}\n\ninterface DevRuntimeBackend {\n reloadChunk?: (chunkUrl: ChunkUrl) => Promise\n unloadChunk?: (chunkUrl: ChunkUrl) => void\n restart: () => void\n}\n\nconst moduleFactories: ModuleFactories = new Map()\ncontextPrototype.M = moduleFactories\n\nconst availableModules: Map | true> = new Map()\n\nconst availableModuleChunks: Map | true> = new Map()\n\nfunction factoryNotAvailableMessage(\n moduleId: ModuleId,\n sourceType: SourceType,\n sourceData: SourceData\n): string {\n let instantiationReason\n switch (sourceType) {\n case SourceType.Runtime:\n instantiationReason = `as a runtime entry of chunk ${sourceData}`\n break\n case SourceType.Parent:\n instantiationReason = `because it was required from module ${sourceData}`\n break\n case SourceType.Update:\n instantiationReason = 'because of an HMR update'\n break\n default:\n invariant(\n sourceType,\n (sourceType) => `Unknown source type: ${sourceType}`\n )\n }\n return `Module ${moduleId} was instantiated ${instantiationReason}, but the module factory is not available.`\n}\n\nfunction loadChunk(\n this: TurbopackBrowserBaseContext,\n chunkData: ChunkData\n): Promise {\n return loadChunkInternal(SourceType.Parent, this.m.id, chunkData)\n}\nbrowserContextPrototype.l = loadChunk\n\nfunction loadInitialChunk(chunkPath: ChunkPath, chunkData: ChunkData) {\n return loadChunkInternal(SourceType.Runtime, chunkPath, chunkData)\n}\n\nasync function loadChunkInternal(\n sourceType: SourceType,\n sourceData: SourceData,\n chunkData: ChunkData\n): Promise {\n if (typeof chunkData === 'string') {\n return loadChunkPath(sourceType, sourceData, chunkData)\n }\n\n const includedList = chunkData.included || []\n const modulesPromises = includedList.map((included) => {\n if (moduleFactories.has(included)) return true\n return availableModules.get(included)\n })\n if (modulesPromises.length > 0 && modulesPromises.every((p) => p)) {\n // When all included items are already loaded or loading, we can skip loading ourselves\n await Promise.all(modulesPromises)\n return\n }\n\n const includedModuleChunksList = chunkData.moduleChunks || []\n const moduleChunksPromises = includedModuleChunksList\n .map((included) => {\n // TODO(alexkirsz) Do we need this check?\n // if (moduleFactories[included]) return true;\n return availableModuleChunks.get(included)\n })\n .filter((p) => p)\n\n let promise: Promise\n if (moduleChunksPromises.length > 0) {\n // Some module chunks are already loaded or loading.\n\n if (moduleChunksPromises.length === includedModuleChunksList.length) {\n // When all included module chunks are already loaded or loading, we can skip loading ourselves\n await Promise.all(moduleChunksPromises)\n return\n }\n\n const moduleChunksToLoad: Set = new Set()\n for (const moduleChunk of includedModuleChunksList) {\n if (!availableModuleChunks.has(moduleChunk)) {\n moduleChunksToLoad.add(moduleChunk)\n }\n }\n\n for (const moduleChunkToLoad of moduleChunksToLoad) {\n const promise = loadChunkPath(sourceType, sourceData, moduleChunkToLoad)\n\n availableModuleChunks.set(moduleChunkToLoad, promise)\n\n moduleChunksPromises.push(promise)\n }\n\n promise = Promise.all(moduleChunksPromises)\n } else {\n promise = loadChunkPath(sourceType, sourceData, chunkData.path)\n\n // Mark all included module chunks as loading if they are not already loaded or loading.\n for (const includedModuleChunk of includedModuleChunksList) {\n if (!availableModuleChunks.has(includedModuleChunk)) {\n availableModuleChunks.set(includedModuleChunk, promise)\n }\n }\n }\n\n for (const included of includedList) {\n if (!availableModules.has(included)) {\n // It might be better to race old and new promises, but it's rare that the new promise will be faster than a request started earlier.\n // In production it's even more rare, because the chunk optimization tries to deduplicate modules anyway.\n availableModules.set(included, promise)\n }\n }\n\n await promise\n}\n\nconst loadedChunk = Promise.resolve(undefined)\nconst instrumentedBackendLoadChunks = new WeakMap<\n Promise,\n Promise | typeof loadedChunk\n>()\n// Do not make this async. React relies on referential equality of the returned Promise.\nfunction loadChunkByUrl(\n this: TurbopackBrowserBaseContext,\n chunkUrl: ChunkUrl\n) {\n return loadChunkByUrlInternal(SourceType.Parent, this.m.id, chunkUrl)\n}\nbrowserContextPrototype.L = loadChunkByUrl\n\n// Do not make this async. React relies on referential equality of the returned Promise.\nfunction loadChunkByUrlInternal(\n sourceType: SourceType,\n sourceData: SourceData,\n chunkUrl: ChunkUrl\n): Promise {\n const thenable = BACKEND.loadChunkCached(sourceType, chunkUrl)\n let entry = instrumentedBackendLoadChunks.get(thenable)\n if (entry === undefined) {\n const resolve = instrumentedBackendLoadChunks.set.bind(\n instrumentedBackendLoadChunks,\n thenable,\n loadedChunk\n )\n entry = thenable.then(resolve).catch((cause) => {\n let loadReason: string\n switch (sourceType) {\n case SourceType.Runtime:\n loadReason = `as a runtime dependency of chunk ${sourceData}`\n break\n case SourceType.Parent:\n loadReason = `from module ${sourceData}`\n break\n case SourceType.Update:\n loadReason = 'from an HMR update'\n break\n default:\n invariant(\n sourceType,\n (sourceType) => `Unknown source type: ${sourceType}`\n )\n }\n let error = new Error(\n `Failed to load chunk ${chunkUrl} ${loadReason}${\n cause ? `: ${cause}` : ''\n }`,\n cause ? { cause } : undefined\n )\n error.name = 'ChunkLoadError'\n throw error\n })\n instrumentedBackendLoadChunks.set(thenable, entry)\n }\n\n return entry\n}\n\n// Do not make this async. React relies on referential equality of the returned Promise.\nfunction loadChunkPath(\n sourceType: SourceType,\n sourceData: SourceData,\n chunkPath: ChunkPath\n): Promise {\n const url = getChunkRelativeUrl(chunkPath)\n return loadChunkByUrlInternal(sourceType, sourceData, url)\n}\n\n/**\n * Returns an absolute url to an asset.\n */\nfunction resolvePathFromModule(\n this: TurbopackBaseContext,\n moduleId: string\n): string {\n const exported = this.r(moduleId)\n return exported?.default ?? exported\n}\nbrowserContextPrototype.R = resolvePathFromModule\n\n/**\n * no-op for browser\n * @param modulePath\n */\nfunction resolveAbsolutePath(modulePath?: string): string {\n return `/ROOT/${modulePath ?? ''}`\n}\nbrowserContextPrototype.P = resolveAbsolutePath\n\n/**\n * Returns a blob URL for the worker.\n * @param chunks list of chunks to load\n */\nfunction getWorkerBlobURL(chunks: ChunkPath[]): string {\n // It is important to reverse the array so when bootstrapping we can infer what chunk is being\n // evaluated by poping urls off of this array. See `getPathFromScript`\n let bootstrap = `self.TURBOPACK_WORKER_LOCATION = ${JSON.stringify(location.origin)};\nself.TURBOPACK_CHUNK_SUFFIX = ${JSON.stringify(CHUNK_SUFFIX)};\nself.TURBOPACK_NEXT_CHUNK_URLS = ${JSON.stringify(chunks.reverse().map(getChunkRelativeUrl), null, 2)};\nimportScripts(...self.TURBOPACK_NEXT_CHUNK_URLS.map(c => self.TURBOPACK_WORKER_LOCATION + c).reverse());`\n let blob = new Blob([bootstrap], { type: 'text/javascript' })\n return URL.createObjectURL(blob)\n}\nbrowserContextPrototype.b = getWorkerBlobURL\n\n/**\n * Instantiates a runtime module.\n */\nfunction instantiateRuntimeModule(\n moduleId: ModuleId,\n chunkPath: ChunkPath\n): Module {\n return instantiateModule(moduleId, SourceType.Runtime, chunkPath)\n}\n/**\n * Returns the URL relative to the origin where a chunk can be fetched from.\n */\nfunction getChunkRelativeUrl(chunkPath: ChunkPath | ChunkListPath): ChunkUrl {\n return `${CHUNK_BASE_PATH}${chunkPath\n .split('/')\n .map((p) => encodeURIComponent(p))\n .join('/')}${CHUNK_SUFFIX}` as ChunkUrl\n}\n\n/**\n * Return the ChunkPath from a ChunkScript.\n */\nfunction getPathFromScript(chunkScript: ChunkPath | ChunkScript): ChunkPath\nfunction getPathFromScript(\n chunkScript: ChunkListPath | ChunkListScript\n): ChunkListPath\nfunction getPathFromScript(\n chunkScript: ChunkPath | ChunkListPath | ChunkScript | ChunkListScript\n): ChunkPath | ChunkListPath {\n if (typeof chunkScript === 'string') {\n return chunkScript as ChunkPath | ChunkListPath\n }\n const chunkUrl =\n typeof TURBOPACK_NEXT_CHUNK_URLS !== 'undefined'\n ? TURBOPACK_NEXT_CHUNK_URLS.pop()!\n : chunkScript.getAttribute('src')!\n const src = decodeURIComponent(chunkUrl.replace(/[?#].*$/, ''))\n const path = src.startsWith(CHUNK_BASE_PATH)\n ? src.slice(CHUNK_BASE_PATH.length)\n : src\n return path as ChunkPath | ChunkListPath\n}\n\nconst regexJsUrl = /\\.js(?:\\?[^#]*)?(?:#.*)?$/\n/**\n * Checks if a given path/URL ends with .js, optionally followed by ?query or #fragment.\n */\nfunction isJs(chunkUrlOrPath: ChunkUrl | ChunkPath): boolean {\n return regexJsUrl.test(chunkUrlOrPath)\n}\n\nconst regexCssUrl = /\\.css(?:\\?[^#]*)?(?:#.*)?$/\n/**\n * Checks if a given path/URL ends with .css, optionally followed by ?query or #fragment.\n */\nfunction isCss(chunkUrl: ChunkUrl): boolean {\n return regexCssUrl.test(chunkUrl)\n}\n\nfunction loadWebAssembly(\n this: TurbopackBaseContext,\n chunkPath: ChunkPath,\n edgeModule: () => WebAssembly.Module,\n importsObj: WebAssembly.Imports\n): Promise {\n return BACKEND.loadWebAssembly(\n SourceType.Parent,\n this.m.id,\n chunkPath,\n edgeModule,\n importsObj\n )\n}\ncontextPrototype.w = loadWebAssembly\n\nfunction loadWebAssemblyModule(\n this: TurbopackBaseContext,\n chunkPath: ChunkPath,\n edgeModule: () => WebAssembly.Module\n): Promise {\n return BACKEND.loadWebAssemblyModule(\n SourceType.Parent,\n this.m.id,\n chunkPath,\n edgeModule\n )\n}\ncontextPrototype.u = loadWebAssemblyModule\n"],"names":[],"mappings":"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","ignoreList":[0]}},
{"offset": {"line": 1241, "column": 0}, "map": {"version":3,"sources":["turbopack:///[turbopack]/browser/runtime/base/build-base.ts"],"sourcesContent":["/// \n/// \n\nconst moduleCache: ModuleCache = {}\ncontextPrototype.c = moduleCache\n\n/**\n * Gets or instantiates a runtime module.\n */\n// @ts-ignore\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction getOrInstantiateRuntimeModule(\n chunkPath: ChunkPath,\n moduleId: ModuleId\n): Module {\n const module = moduleCache[moduleId]\n if (module) {\n if (module.error) {\n throw module.error\n }\n return module\n }\n\n return instantiateModule(moduleId, SourceType.Runtime, chunkPath)\n}\n\n/**\n * Retrieves a module from the cache, or instantiate it if it is not cached.\n */\n// Used by the backend\n// @ts-ignore\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst getOrInstantiateModuleFromParent: GetOrInstantiateModuleFromParent<\n Module\n> = (id, sourceModule) => {\n const module = moduleCache[id]\n\n if (module) {\n if (module.error) {\n throw module.error\n }\n return module\n }\n\n return instantiateModule(id, SourceType.Parent, sourceModule.id)\n}\n\nfunction instantiateModule(\n id: ModuleId,\n sourceType: SourceType,\n sourceData: SourceData\n): Module {\n const moduleFactory = moduleFactories.get(id)\n if (typeof moduleFactory !== 'function') {\n // This can happen if modules incorrectly handle HMR disposes/updates,\n // e.g. when they keep a `setTimeout` around which still executes old code\n // and contains e.g. a `require(\"something\")` call.\n throw new Error(factoryNotAvailableMessage(id, sourceType, sourceData))\n }\n\n const module: Module = createModuleObject(id)\n const exports = module.exports\n\n moduleCache[id] = module\n\n // NOTE(alexkirsz) This can fail when the module encounters a runtime error.\n const context = new (Context as any as ContextConstructor)(\n module,\n exports\n )\n try {\n moduleFactory(context, module, exports)\n } catch (error) {\n module.error = error as any\n throw error\n }\n\n if (module.namespaceObject && module.exports !== module.namespaceObject) {\n // in case of a circular dependency: cjs1 -> esm2 -> cjs1\n interopEsm(module.exports, module.namespaceObject)\n }\n\n return module\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction registerChunk(registration: ChunkRegistration) {\n const chunkPath = getPathFromScript(registration[0])\n let runtimeParams: RuntimeParams | undefined\n // When bootstrapping we are passed a single runtimeParams object so we can distinguish purely based on length\n if (registration.length === 2) {\n runtimeParams = registration[1] as RuntimeParams\n } else {\n runtimeParams = undefined\n installCompressedModuleFactories(\n registration as CompressedModuleFactories,\n /* offset= */ 1,\n moduleFactories\n )\n }\n\n return BACKEND.registerChunk(chunkPath, runtimeParams)\n}\n"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,mCAAmC;AAEnC,IAAM,cAAmC,CAAC;AAC1C,iBAAiB,CAAC,GAAG;AAErB;;CAEC,GACD,aAAa;AACb,6DAA6D;AAC7D,SAAS,8BACP,SAAoB,EACpB,QAAkB;IAElB,IAAM,SAAS,WAAW,CAAC,SAAS;IACpC,IAAI,QAAQ;QACV,IAAI,OAAO,KAAK,EAAE;YAChB,MAAM,OAAO,KAAK;QACpB;QACA,OAAO;IACT;IAEA,OAAO,kBAAkB,UAAU,WAAW,OAAO,EAAE;AACzD;AAEA;;CAEC,GACD,sBAAsB;AACtB,aAAa;AACb,6DAA6D;AAC7D,IAAM,mCAEF,SAAC,IAAI;IACP,IAAM,SAAS,WAAW,CAAC,GAAG;IAE9B,IAAI,QAAQ;QACV,IAAI,OAAO,KAAK,EAAE;YAChB,MAAM,OAAO,KAAK;QACpB;QACA,OAAO;IACT;IAEA,OAAO,kBAAkB,IAAI,WAAW,MAAM,EAAE,aAAa,EAAE;AACjE;AAEA,SAAS,kBACP,EAAY,EACZ,UAAsB,EACtB,UAAsB;IAEtB,IAAM,gBAAgB,gBAAgB,GAAG,CAAC;IAC1C,IAAI,OAAO,kBAAkB,YAAY;QACvC,sEAAsE;QACtE,0EAA0E;QAC1E,mDAAmD;QACnD,MAAM,IAAI,MAAM,2BAA2B,IAAI,YAAY;IAC7D;IAEA,IAAM,SAAiB,mBAAmB;IAC1C,IAAM,UAAU,OAAO,OAAO;IAE9B,WAAW,CAAC,GAAG,GAAG;IAElB,4EAA4E;IAC5E,IAAM,UAAU,IAAK,QACnB,QACA;IAEF,IAAI;QACF,cAAc,SAAS,QAAQ;IACjC,EAAE,OAAO,OAAO;QACd,OAAO,KAAK,GAAG;QACf,MAAM;IACR;IAEA,IAAI,OAAO,eAAe,IAAI,OAAO,OAAO,KAAK,OAAO,eAAe,EAAE;QACvE,yDAAyD;QACzD,WAAW,OAAO,OAAO,EAAE,OAAO,eAAe;IACnD;IAEA,OAAO;AACT;AAEA,6DAA6D;AAC7D,SAAS,cAAc,YAA+B;IACpD,IAAM,YAAY,kBAAkB,YAAY,CAAC,EAAE;IACnD,IAAI;IACJ,8GAA8G;IAC9G,IAAI,aAAa,MAAM,KAAK,GAAG;QAC7B,gBAAgB,YAAY,CAAC,EAAE;IACjC,OAAO;QACL,gBAAgB;QAChB,iCACE,cACA,WAAW,GAAG,GACd;IAEJ;IAEA,OAAO,QAAQ,aAAa,CAAC,WAAW;AAC1C","ignoreList":[0]}},
{"offset": {"line": 1312, "column": 0}, "map": {"version":3,"sources":["turbopack:///[turbopack]/browser/runtime/dom/runtime-backend-dom.ts"],"sourcesContent":["/**\n * This file contains the runtime code specific to the Turbopack development\n * ECMAScript DOM runtime.\n *\n * It will be appended to the base development runtime code.\n */\n\n/* eslint-disable @typescript-eslint/no-unused-vars */\n\n/// \n/// \n\ntype ChunkResolver = {\n resolved: boolean\n loadingStarted: boolean\n resolve: () => void\n reject: (error?: Error) => void\n promise: Promise\n}\n\nlet BACKEND: RuntimeBackend\n\n/**\n * Maps chunk paths to the corresponding resolver.\n */\nconst chunkResolvers: Map = new Map()\n\n;(() => {\n BACKEND = {\n async registerChunk(chunkPath, params) {\n const chunkUrl = getChunkRelativeUrl(chunkPath)\n\n const resolver = getOrCreateResolver(chunkUrl)\n resolver.resolve()\n\n if (params == null) {\n return\n }\n\n for (const otherChunkData of params.otherChunks) {\n const otherChunkPath = getChunkPath(otherChunkData)\n const otherChunkUrl = getChunkRelativeUrl(otherChunkPath)\n\n // Chunk might have started loading, so we want to avoid triggering another load.\n getOrCreateResolver(otherChunkUrl)\n }\n\n // This waits for chunks to be loaded, but also marks included items as available.\n await Promise.all(\n params.otherChunks.map((otherChunkData) =>\n loadInitialChunk(chunkPath, otherChunkData)\n )\n )\n\n if (params.runtimeModuleIds.length > 0) {\n for (const moduleId of params.runtimeModuleIds) {\n getOrInstantiateRuntimeModule(chunkPath, moduleId)\n }\n }\n },\n\n /**\n * Loads the given chunk, and returns a promise that resolves once the chunk\n * has been loaded.\n */\n loadChunkCached(sourceType: SourceType, chunkUrl: ChunkUrl) {\n return doLoadChunk(sourceType, chunkUrl)\n },\n\n async loadWebAssembly(\n _sourceType: SourceType,\n _sourceData: SourceData,\n wasmChunkPath: ChunkPath,\n _edgeModule: () => WebAssembly.Module,\n importsObj: WebAssembly.Imports\n ): Promise {\n const req = fetchWebAssembly(wasmChunkPath)\n\n const { instance } = await WebAssembly.instantiateStreaming(\n req,\n importsObj\n )\n\n return instance.exports\n },\n\n async loadWebAssemblyModule(\n _sourceType: SourceType,\n _sourceData: SourceData,\n wasmChunkPath: ChunkPath,\n _edgeModule: () => WebAssembly.Module\n ): Promise {\n const req = fetchWebAssembly(wasmChunkPath)\n\n return await WebAssembly.compileStreaming(req)\n },\n }\n\n function getOrCreateResolver(chunkUrl: ChunkUrl): ChunkResolver {\n let resolver = chunkResolvers.get(chunkUrl)\n if (!resolver) {\n let resolve: () => void\n let reject: (error?: Error) => void\n const promise = new Promise((innerResolve, innerReject) => {\n resolve = innerResolve\n reject = innerReject\n })\n resolver = {\n resolved: false,\n loadingStarted: false,\n promise,\n resolve: () => {\n resolver!.resolved = true\n resolve()\n },\n reject: reject!,\n }\n chunkResolvers.set(chunkUrl, resolver)\n }\n return resolver\n }\n\n /**\n * Loads the given chunk, and returns a promise that resolves once the chunk\n * has been loaded.\n */\n function doLoadChunk(sourceType: SourceType, chunkUrl: ChunkUrl) {\n const resolver = getOrCreateResolver(chunkUrl)\n if (resolver.loadingStarted) {\n return resolver.promise\n }\n\n if (sourceType === SourceType.Runtime) {\n // We don't need to load chunks references from runtime code, as they're already\n // present in the DOM.\n resolver.loadingStarted = true\n\n if (isCss(chunkUrl)) {\n // CSS chunks do not register themselves, and as such must be marked as\n // loaded instantly.\n resolver.resolve()\n }\n\n // We need to wait for JS chunks to register themselves within `registerChunk`\n // before we can start instantiating runtime modules, hence the absence of\n // `resolver.resolve()` in this branch.\n\n return resolver.promise\n }\n\n if (typeof importScripts === 'function') {\n // We're in a web worker\n if (isCss(chunkUrl)) {\n // ignore\n } else if (isJs(chunkUrl)) {\n self.TURBOPACK_NEXT_CHUNK_URLS!.push(chunkUrl)\n importScripts(TURBOPACK_WORKER_LOCATION + chunkUrl)\n } else {\n throw new Error(\n `can't infer type of chunk from URL ${chunkUrl} in worker`\n )\n }\n } else {\n // TODO(PACK-2140): remove this once all filenames are guaranteed to be escaped.\n const decodedChunkUrl = decodeURI(chunkUrl)\n\n if (isCss(chunkUrl)) {\n const previousLinks = document.querySelectorAll(\n `link[rel=stylesheet][href=\"${chunkUrl}\"],link[rel=stylesheet][href^=\"${chunkUrl}?\"],link[rel=stylesheet][href=\"${decodedChunkUrl}\"],link[rel=stylesheet][href^=\"${decodedChunkUrl}?\"]`\n )\n if (previousLinks.length > 0) {\n // CSS chunks do not register themselves, and as such must be marked as\n // loaded instantly.\n resolver.resolve()\n } else {\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = chunkUrl\n link.onerror = () => {\n resolver.reject()\n }\n link.onload = () => {\n // CSS chunks do not register themselves, and as such must be marked as\n // loaded instantly.\n resolver.resolve()\n }\n // Append to the `head` for webpack compatibility.\n document.head.appendChild(link)\n }\n } else if (isJs(chunkUrl)) {\n const previousScripts = document.querySelectorAll(\n `script[src=\"${chunkUrl}\"],script[src^=\"${chunkUrl}?\"],script[src=\"${decodedChunkUrl}\"],script[src^=\"${decodedChunkUrl}?\"]`\n )\n if (previousScripts.length > 0) {\n // There is this edge where the script already failed loading, but we\n // can't detect that. The Promise will never resolve in this case.\n for (const script of Array.from(previousScripts)) {\n script.addEventListener('error', () => {\n resolver.reject()\n })\n }\n } else {\n const script = document.createElement('script')\n script.src = chunkUrl\n // We'll only mark the chunk as loaded once the script has been executed,\n // which happens in `registerChunk`. Hence the absence of `resolve()` in\n // this branch.\n script.onerror = () => {\n resolver.reject()\n }\n // Append to the `head` for webpack compatibility.\n document.head.appendChild(script)\n }\n } else {\n throw new Error(`can't infer type of chunk from URL ${chunkUrl}`)\n }\n }\n\n resolver.loadingStarted = true\n return resolver.promise\n }\n\n function fetchWebAssembly(wasmChunkPath: ChunkPath) {\n return fetch(getChunkRelativeUrl(wasmChunkPath))\n }\n})()\n"],"names":[],"mappings":"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","ignoreList":[0]}}]
}
\ No newline at end of file
From 4324698881971ef61ec2855419a3e82776e8e342 Mon Sep 17 00:00:00 2001
From: Zack Tanner <1939140+ztanner@users.noreply.github.com>
Date: Tue, 27 Jan 2026 12:00:10 -0800
Subject: [PATCH 2/6] backport: implement LRU cache with invocation ID scoping
for minimal mode response cache (#89122)
Backports:
- #88509
Co-authored-by: Wyatt Johnson
---
.../next/src/server/lib/lru-cache.test.ts | 71 ++
packages/next/src/server/lib/lru-cache.ts | 13 +-
.../next/src/server/response-cache/index.ts | 225 ++++++-
.../src/server/route-modules/route-module.ts | 3 +
test/lib/next-test-utils.ts | 32 +
.../test/index.test.ts | 251 ++++---
.../required-server-files-app.test.ts | 245 +++++--
.../required-server-files-i18n.test.ts | 418 ++++++++----
.../required-server-files-ppr.test.ts | 261 +++++---
.../required-server-files.test.ts | 619 +++++++++++-------
.../response-cache/index.test.ts | 4 +-
11 files changed, 1507 insertions(+), 635 deletions(-)
diff --git a/packages/next/src/server/lib/lru-cache.test.ts b/packages/next/src/server/lib/lru-cache.test.ts
index 3be3357369250..d184cdb69c853 100644
--- a/packages/next/src/server/lib/lru-cache.test.ts
+++ b/packages/next/src/server/lib/lru-cache.test.ts
@@ -226,4 +226,75 @@ describe('LRUCache', () => {
expect(cache.has('key149')).toBe(true) // recent keys retained
})
})
+
+ describe('onEvict Callback', () => {
+ it('should call onEvict when an entry is evicted', () => {
+ const evicted: Array<{ key: string; value: string }> = []
+ const cache = new LRUCache(2, undefined, (key, value) => {
+ evicted.push({ key, value })
+ })
+
+ cache.set('a', 'value-a')
+ cache.set('b', 'value-b')
+ expect(evicted.length).toBe(0)
+
+ cache.set('c', 'value-c') // should evict 'a'
+ expect(evicted.length).toBe(1)
+ expect(evicted[0]).toEqual({ key: 'a', value: 'value-a' })
+ })
+
+ it('should not call onEvict when updating existing entry', () => {
+ const evicted: string[] = []
+ const cache = new LRUCache(2, undefined, (key) => {
+ evicted.push(key)
+ })
+
+ cache.set('a', 'value-a')
+ cache.set('a', 'new-value-a')
+ expect(evicted.length).toBe(0)
+ })
+
+ it('should call onEvict for each evicted entry when multiple are evicted', () => {
+ const evicted: string[] = []
+ const cache = new LRUCache(
+ 10,
+ (value) => value.length,
+ (key) => {
+ evicted.push(key)
+ }
+ )
+
+ cache.set('key1', 'ab') // size 2
+ cache.set('key2', 'cd') // size 2
+ cache.set('key3', 'ef') // size 2, total = 6
+ cache.set('key4', 'ghijklmno') // size 9, should evict key1, key2, key3
+
+ expect(evicted).toEqual(['key1', 'key2', 'key3'])
+ })
+
+ it('should work without onEvict callback', () => {
+ const cache = new LRUCache(2)
+ cache.set('a', 'value-a')
+ cache.set('b', 'value-b')
+ cache.set('c', 'value-c') // should evict without error
+ expect(cache.has('a')).toBe(false)
+ })
+
+ it('should pass the evicted value to the callback', () => {
+ const evicted: Array<{ id: number }> = []
+ const cache = new LRUCache<{ id: number }>(
+ 1,
+ undefined,
+ (_key, value) => {
+ evicted.push(value)
+ }
+ )
+
+ cache.set('obj1', { id: 1 })
+ cache.set('obj2', { id: 2 }) // should evict obj1
+
+ expect(evicted.length).toBe(1)
+ expect(evicted[0]).toEqual({ id: 1 })
+ })
+ })
})
diff --git a/packages/next/src/server/lib/lru-cache.ts b/packages/next/src/server/lib/lru-cache.ts
index 663bc71ab4264..e8fc56809f216 100644
--- a/packages/next/src/server/lib/lru-cache.ts
+++ b/packages/next/src/server/lib/lru-cache.ts
@@ -50,10 +50,16 @@ export class LRUCache {
private totalSize: number = 0
private readonly maxSize: number
private readonly calculateSize: ((value: T) => number) | undefined
+ private readonly onEvict: ((key: string, value: T) => void) | undefined
- constructor(maxSize: number, calculateSize?: (value: T) => number) {
+ constructor(
+ maxSize: number,
+ calculateSize?: (value: T) => number,
+ onEvict?: (key: string, value: T) => void
+ ) {
this.maxSize = maxSize
this.calculateSize = calculateSize
+ this.onEvict = onEvict
// Create sentinel nodes to simplify doubly-linked list operations
// HEAD <-> TAIL (empty list)
@@ -144,6 +150,7 @@ export class LRUCache {
const tail = this.removeTail()
this.cache.delete(tail.key)
this.totalSize -= tail.size
+ this.onEvict?.(tail.key, tail.data)
}
}
@@ -191,6 +198,10 @@ export class LRUCache {
* Removes a specific key from the cache.
* Updates both the hash map and doubly-linked list.
*
+ * Note: This is an explicit removal and does NOT trigger the `onEvict`
+ * callback. Use this for intentional deletions where eviction tracking
+ * is not needed.
+ *
* Time Complexity: O(1)
*/
public remove(key: string): void {
diff --git a/packages/next/src/server/response-cache/index.ts b/packages/next/src/server/response-cache/index.ts
index ab0057e231097..e40ef2f00596a 100644
--- a/packages/next/src/server/response-cache/index.ts
+++ b/packages/next/src/server/response-cache/index.ts
@@ -7,6 +7,8 @@ import type {
} from './types'
import { Batcher } from '../../lib/batcher'
+import { LRUCache } from '../lib/lru-cache'
+import { warnOnce } from '../../build/output/log'
import { scheduleOnNextTick } from '../../lib/scheduler'
import {
fromResponseCacheEntry,
@@ -15,6 +17,92 @@ import {
} from './utils'
import type { RouteKind } from '../route-kind'
+/**
+ * Parses an environment variable as a positive integer, returning the fallback
+ * if the value is missing, not a number, or not positive.
+ */
+function parsePositiveInt(
+ envValue: string | undefined,
+ fallback: number
+): number {
+ if (!envValue) return fallback
+ const parsed = parseInt(envValue, 10)
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback
+}
+
+/**
+ * Default TTL (in milliseconds) for minimal mode response cache entries.
+ * Used for cache hit validation as a fallback for providers that don't
+ * send the x-invocation-id header yet.
+ *
+ * 10 seconds chosen because:
+ * - Long enough to dedupe rapid successive requests (e.g., page + data)
+ * - Short enough to not serve stale data across unrelated requests
+ *
+ * Can be configured via `NEXT_PRIVATE_RESPONSE_CACHE_TTL` environment variable.
+ */
+const DEFAULT_TTL_MS = parsePositiveInt(
+ process.env.NEXT_PRIVATE_RESPONSE_CACHE_TTL,
+ 10_000
+)
+
+/**
+ * Default maximum number of entries in the response cache.
+ * Can be configured via `NEXT_PRIVATE_RESPONSE_CACHE_MAX_SIZE` environment variable.
+ */
+const DEFAULT_MAX_SIZE = parsePositiveInt(
+ process.env.NEXT_PRIVATE_RESPONSE_CACHE_MAX_SIZE,
+ 150
+)
+
+/**
+ * Separator used in compound cache keys to join pathname and invocationID.
+ * Using null byte (\0) since it cannot appear in valid URL paths or UUIDs.
+ */
+const KEY_SEPARATOR = '\0'
+
+/**
+ * Sentinel value used for TTL-based cache entries (when invocationID is undefined).
+ * Uses KEY_SEPARATOR prefix to guarantee uniqueness since null bytes cannot appear
+ * in HTTP headers (RFC 7230), making collision with real invocation IDs impossible.
+ */
+const TTL_SENTINEL = `${KEY_SEPARATOR}ttl`
+
+/**
+ * Entry stored in the LRU cache.
+ */
+type CacheEntry = {
+ entry: IncrementalResponseCacheEntry | null
+ /**
+ * TTL expiration timestamp in milliseconds. Used as a fallback for
+ * cache hit validation when providers don't send x-invocation-id.
+ * Memory pressure is managed by LRU eviction rather than timers.
+ */
+ expiresAt: number
+}
+
+/**
+ * Creates a compound cache key from pathname and invocationID.
+ */
+function createCacheKey(
+ pathname: string,
+ invocationID: string | undefined
+): string {
+ return `${pathname}${KEY_SEPARATOR}${invocationID ?? TTL_SENTINEL}`
+}
+
+/**
+ * Extracts the invocationID from a compound cache key.
+ * Returns undefined if the key used TTL_SENTINEL.
+ */
+function extractInvocationID(compoundKey: string): string | undefined {
+ const separatorIndex = compoundKey.lastIndexOf(KEY_SEPARATOR)
+ if (separatorIndex === -1) return undefined
+
+ const invocationID = compoundKey.slice(separatorIndex + 1)
+ return invocationID === TTL_SENTINEL ? undefined : invocationID
+}
+
export * from './types'
export default class ResponseCache implements ResponseCacheBase {
@@ -43,19 +131,63 @@ export default class ResponseCache implements ResponseCacheBase {
schedulerFn: scheduleOnNextTick,
})
- private previousCacheItem?: {
- key: string
- entry: IncrementalResponseCacheEntry | null
- expiresAt: number
- }
+ /**
+ * LRU cache for minimal mode using compound keys (pathname + invocationID).
+ * This allows multiple invocations to cache the same pathname without
+ * overwriting each other's entries.
+ */
+ private readonly cache: LRUCache
+
+ /**
+ * Set of invocation IDs that have had cache entries evicted.
+ * Used to detect when the cache size may be too small.
+ * Bounded to prevent memory growth.
+ */
+ private readonly evictedInvocationIDs: Set = new Set()
+
+ /**
+ * The configured max size, stored for logging.
+ */
+ private readonly maxSize: number
+
+ /**
+ * The configured TTL for cache entries in milliseconds.
+ */
+ private readonly ttl: number
// we don't use minimal_mode name here as this.minimal_mode is
// statically replace for server runtimes but we need it to
// be dynamic here
private minimal_mode?: boolean
- constructor(minimal_mode: boolean) {
+ constructor(
+ minimal_mode: boolean,
+ maxSize: number = DEFAULT_MAX_SIZE,
+ ttl: number = DEFAULT_TTL_MS
+ ) {
this.minimal_mode = minimal_mode
+ this.maxSize = maxSize
+ this.ttl = ttl
+
+ // Create the LRU cache with eviction tracking
+ this.cache = new LRUCache(maxSize, undefined, (compoundKey) => {
+ const invocationID = extractInvocationID(compoundKey)
+ if (invocationID) {
+ // Bound to 100 entries to prevent unbounded memory growth.
+ // FIFO eviction is acceptable here because:
+ // 1. Invocations are short-lived (single request lifecycle), so older
+ // invocations are unlikely to still be active after 100 newer ones
+ // 2. This warning mechanism is best-effort for developer guidance—
+ // missing occasional eviction warnings doesn't affect correctness
+ // 3. If a long-running invocation is somehow evicted and then has
+ // another cache entry evicted, it will simply be re-added
+ if (this.evictedInvocationIDs.size >= 100) {
+ const first = this.evictedInvocationIDs.values().next().value
+ if (first) this.evictedInvocationIDs.delete(first)
+ }
+ this.evictedInvocationIDs.add(invocationID)
+ }
+ })
}
/**
@@ -77,6 +209,12 @@ export default class ResponseCache implements ResponseCacheBase {
isRoutePPREnabled?: boolean
isFallback?: boolean
waitUntil?: (prom: Promise) => void
+
+ /**
+ * The invocation ID from the infrastructure. Used to scope the
+ * in-memory cache to a single revalidation request in minimal mode.
+ */
+ invocationID?: string
}
): Promise {
// If there is no key for the cache, we can't possibly look this up in the
@@ -88,13 +226,38 @@ export default class ResponseCache implements ResponseCacheBase {
})
}
- // Check minimal mode cache before doing any other work
- if (
- this.minimal_mode &&
- this.previousCacheItem?.key === key &&
- this.previousCacheItem.expiresAt > Date.now()
- ) {
- return toResponseCacheEntry(this.previousCacheItem.entry)
+ // Check minimal mode cache before doing any other work.
+ if (this.minimal_mode) {
+ const cacheKey = createCacheKey(key, context.invocationID)
+ const cachedItem = this.cache.get(cacheKey)
+
+ if (cachedItem) {
+ // With invocationID: exact match found - always a hit
+ // With TTL mode: must check expiration
+ if (context.invocationID !== undefined) {
+ return toResponseCacheEntry(cachedItem.entry)
+ }
+
+ // TTL mode: check expiration
+ const now = Date.now()
+ if (cachedItem.expiresAt > now) {
+ return toResponseCacheEntry(cachedItem.entry)
+ }
+
+ // TTL expired - clean up
+ this.cache.remove(cacheKey)
+ }
+
+ // Warn if this invocation had entries evicted - indicates cache may be too small.
+ if (
+ context.invocationID &&
+ this.evictedInvocationIDs.has(context.invocationID)
+ ) {
+ warnOnce(
+ `Response cache entry was evicted for invocation ${context.invocationID}. ` +
+ `Consider increasing NEXT_PRIVATE_RESPONSE_CACHE_MAX_SIZE (current: ${this.maxSize}).`
+ )
+ }
}
const {
@@ -105,6 +268,7 @@ export default class ResponseCache implements ResponseCacheBase {
isPrefetch = false,
waitUntil,
routeKind,
+ invocationID,
} = context
const response = await this.getBatcher.batch(
@@ -120,6 +284,7 @@ export default class ResponseCache implements ResponseCacheBase {
isRoutePPREnabled,
isPrefetch,
routeKind,
+ invocationID,
},
resolve
)
@@ -153,6 +318,7 @@ export default class ResponseCache implements ResponseCacheBase {
isRoutePPREnabled: boolean
isPrefetch: boolean
routeKind: RouteKind
+ invocationID: string | undefined
},
resolve: (value: IncrementalResponseCacheEntry | null) => void
): Promise {
@@ -188,13 +354,18 @@ export default class ResponseCache implements ResponseCacheBase {
context.isFallback,
responseGenerator,
previousIncrementalCacheEntry,
- previousIncrementalCacheEntry !== null && !context.isOnDemandRevalidate
+ previousIncrementalCacheEntry !== null && !context.isOnDemandRevalidate,
+ undefined,
+ context.invocationID
)
// Handle null response
if (!incrementalResponseCacheEntry) {
- // Unset the previous cache item if it was set so we don't use it again.
- if (this.minimal_mode) this.previousCacheItem = undefined
+ // Remove the cache item if it was set so we don't use it again.
+ if (this.minimal_mode) {
+ const cacheKey = createCacheKey(key, context.invocationID)
+ this.cache.remove(cacheKey)
+ }
return null
}
@@ -226,6 +397,8 @@ export default class ResponseCache implements ResponseCacheBase {
* @param responseGenerator - The response generator to use to generate the response cache entry.
* @param previousIncrementalCacheEntry - The previous cache entry to use to revalidate the cache entry.
* @param hasResolved - Whether the response has been resolved.
+ * @param waitUntil - Optional function to register background work.
+ * @param invocationID - The invocation ID for cache key scoping.
* @returns The revalidated cache entry.
*/
public async revalidate(
@@ -236,7 +409,8 @@ export default class ResponseCache implements ResponseCacheBase {
responseGenerator: ResponseGenerator,
previousIncrementalCacheEntry: IncrementalResponseCacheEntry | null,
hasResolved: boolean,
- waitUntil?: (prom: Promise) => void
+ waitUntil?: (prom: Promise) => void,
+ invocationID?: string
) {
return this.revalidateBatcher.batch(key, () => {
const promise = this.handleRevalidate(
@@ -246,7 +420,8 @@ export default class ResponseCache implements ResponseCacheBase {
isFallback,
responseGenerator,
previousIncrementalCacheEntry,
- hasResolved
+ hasResolved,
+ invocationID
)
// We need to ensure background revalidates are passed to waitUntil.
@@ -263,7 +438,8 @@ export default class ResponseCache implements ResponseCacheBase {
isFallback: boolean,
responseGenerator: ResponseGenerator,
previousIncrementalCacheEntry: IncrementalResponseCacheEntry | null,
- hasResolved: boolean
+ hasResolved: boolean,
+ invocationID: string | undefined
) {
try {
// Generate the response cache entry using the response generator.
@@ -286,11 +462,14 @@ export default class ResponseCache implements ResponseCacheBase {
// defined.
if (incrementalResponseCacheEntry.cacheControl) {
if (this.minimal_mode) {
- this.previousCacheItem = {
- key,
+ // Set TTL expiration for cache hit validation. Entries are validated
+ // by invocationID when available, with TTL as a fallback for providers
+ // that don't send x-invocation-id. Memory is managed by LRU eviction.
+ const cacheKey = createCacheKey(key, invocationID)
+ this.cache.set(cacheKey, {
entry: incrementalResponseCacheEntry,
- expiresAt: Date.now() + 1000,
- }
+ expiresAt: Date.now() + this.ttl,
+ })
} else {
await incrementalCache.set(key, incrementalResponseCacheEntry.value, {
cacheControl: incrementalResponseCacheEntry.cacheControl,
diff --git a/packages/next/src/server/route-modules/route-module.ts b/packages/next/src/server/route-modules/route-module.ts
index 1b26f967466a7..491dc1daadcff 100644
--- a/packages/next/src/server/route-modules/route-module.ts
+++ b/packages/next/src/server/route-modules/route-module.ts
@@ -1008,6 +1008,9 @@ export abstract class RouteModule<
isRoutePPREnabled,
isOnDemandRevalidate,
isPrefetch: req.headers.purpose === 'prefetch',
+ // Use x-invocation-id header to scope the in-memory cache to a single
+ // revalidation request in minimal mode.
+ invocationID: req.headers['x-invocation-id'] as string | undefined,
incrementalCache: await this.getIncrementalCache(
req,
nextConfig,
diff --git a/test/lib/next-test-utils.ts b/test/lib/next-test-utils.ts
index 47617dab304b8..7bbd99e986c6a 100644
--- a/test/lib/next-test-utils.ts
+++ b/test/lib/next-test-utils.ts
@@ -16,6 +16,7 @@ import { writeFile } from 'fs-extra'
import getPort from 'get-port'
import { getRandomPort } from 'get-port-please'
import fetch from 'node-fetch'
+import { nanoid } from 'nanoid'
import qs from 'querystring'
import treeKill from 'tree-kill'
import { once } from 'events'
@@ -197,6 +198,37 @@ export function fetchViaHTTP(
return fetch(getFullUrl(appPort, url), opts)
}
+/**
+ * Creates request options with a unique x-invocation-id header for testing
+ * cache deduplication in minimal mode. Use this when you need to ensure each
+ * request is treated as independent, or when multiple requests need to share
+ * the same invocation ID.
+ *
+ * @example
+ * // Independent requests (each gets its own invocation ID)
+ * const res1 = await fetchViaHTTP(appPort, '/page', undefined, withInvocationId())
+ * const res2 = await fetchViaHTTP(appPort, '/page', undefined, withInvocationId())
+ *
+ * @example
+ * // Grouped requests (share the same invocation ID for cache testing)
+ * const sharedOpts = withInvocationId()
+ * const res1 = await fetchViaHTTP(appPort, '/page', undefined, sharedOpts)
+ * const res2 = await fetchViaHTTP(appPort, '/_next/data/.../page.json', undefined, sharedOpts)
+ *
+ * @param opts - Optional existing RequestInit to merge with
+ * @returns RequestInit with x-invocation-id header added
+ */
+export function withInvocationId(opts?: RequestInit): RequestInit {
+ const invocationId = `test:${nanoid()}`
+ return {
+ ...opts,
+ headers: {
+ ...opts?.headers,
+ 'x-invocation-id': invocationId,
+ },
+ }
+}
+
export function renderViaHTTP(
appPort: string | number,
pathname: string,
diff --git a/test/production/required-server-files-ssr-404/test/index.test.ts b/test/production/required-server-files-ssr-404/test/index.test.ts
index 56be852c7420c..54cc4c0e7b7bb 100644
--- a/test/production/required-server-files-ssr-404/test/index.test.ts
+++ b/test/production/required-server-files-ssr-404/test/index.test.ts
@@ -3,8 +3,14 @@
import fs from 'fs-extra'
import { join } from 'path'
import cheerio from 'cheerio'
-import { nextServer, startApp, waitFor } from 'next-test-utils'
-import { fetchViaHTTP, renderViaHTTP } from 'next-test-utils'
+import {
+ fetchViaHTTP,
+ nextServer,
+ renderViaHTTP,
+ startApp,
+ waitFor,
+ withInvocationId,
+} from 'next-test-utils'
import { nextTestSetup } from 'e2e-utils'
describe('Required Server Files', () => {
@@ -90,14 +96,24 @@ describe('Required Server Files', () => {
})
it('should render SSR page correctly', async () => {
- const html = await renderViaHTTP(appPort, '/')
+ const html = await renderViaHTTP(
+ appPort,
+ '/',
+ undefined,
+ withInvocationId()
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
expect($('#index').text()).toBe('index page')
expect(data.hello).toBe('world')
- const html2 = await renderViaHTTP(appPort, '/')
+ const html2 = await renderViaHTTP(
+ appPort,
+ '/',
+ undefined,
+ withInvocationId()
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -107,7 +123,12 @@ describe('Required Server Files', () => {
})
it('should render dynamic SSR page correctly', async () => {
- const html = await renderViaHTTP(appPort, '/dynamic/first')
+ const html = await renderViaHTTP(
+ appPort,
+ '/dynamic/first',
+ undefined,
+ withInvocationId()
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -115,7 +136,12 @@ describe('Required Server Files', () => {
expect($('#slug').text()).toBe('first')
expect(data.hello).toBe('world')
- const html2 = await renderViaHTTP(appPort, '/dynamic/second')
+ const html2 = await renderViaHTTP(
+ appPort,
+ '/dynamic/second',
+ undefined,
+ withInvocationId()
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -126,7 +152,12 @@ describe('Required Server Files', () => {
})
it('should render fallback page correctly', async () => {
- const html = await renderViaHTTP(appPort, '/fallback/first')
+ const html = await renderViaHTTP(
+ appPort,
+ '/fallback/first',
+ undefined,
+ withInvocationId()
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -135,7 +166,12 @@ describe('Required Server Files', () => {
expect(data.hello).toBe('world')
await waitFor(2000)
- const html2 = await renderViaHTTP(appPort, '/fallback/first')
+ const html2 = await renderViaHTTP(
+ appPort,
+ '/fallback/first',
+ undefined,
+ withInvocationId()
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -144,7 +180,12 @@ describe('Required Server Files', () => {
expect(isNaN(data2.random)).toBe(false)
expect(data2.random).not.toBe(data.random)
- const html3 = await renderViaHTTP(appPort, '/fallback/second')
+ const html3 = await renderViaHTTP(
+ appPort,
+ '/fallback/second',
+ undefined,
+ withInvocationId()
+ )
const $3 = cheerio.load(html3)
const data3 = JSON.parse($3('#props').text())
@@ -155,7 +196,9 @@ describe('Required Server Files', () => {
const { pageProps: data4 } = JSON.parse(
await renderViaHTTP(
appPort,
- `/_next/data/${buildId}/fallback/third.json`
+ `/_next/data/${buildId}/fallback/third.json`,
+ undefined,
+ withInvocationId()
)
)
expect(data4.hello).toBe('world')
@@ -167,11 +210,11 @@ describe('Required Server Files', () => {
appPort,
'/some-other-path',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/',
},
- }
+ })
)
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -183,11 +226,11 @@ describe('Required Server Files', () => {
appPort,
'/some-other-path',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/',
},
- }
+ })
)
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -202,11 +245,11 @@ describe('Required Server Files', () => {
appPort,
'/some-other-path?nxtPslug=first',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/dynamic/[slug]',
},
- }
+ })
)
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -219,11 +262,11 @@ describe('Required Server Files', () => {
appPort,
'/some-other-path?slug=second',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/dynamic/[slug]',
},
- }
+ })
)
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -239,12 +282,12 @@ describe('Required Server Files', () => {
appPort,
'/fallback/first',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/fallback/first',
'x-now-route-matches': 'nxtPslug=first',
},
- }
+ })
)
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -257,12 +300,12 @@ describe('Required Server Files', () => {
appPort,
`/fallback/[slug]`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/fallback/[slug]',
'x-now-route-matches': 'nxtPslug=second',
},
- }
+ })
)
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -278,11 +321,11 @@ describe('Required Server Files', () => {
appPort,
`/_next/data/${buildId}/dynamic/first.json?nxtPslug=first`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/dynamic/[slug]',
},
- }
+ })
)
const { pageProps: data } = await res.json()
@@ -294,12 +337,12 @@ describe('Required Server Files', () => {
appPort,
`/_next/data/${buildId}/fallback/[slug].json`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': `/_next/data/${buildId}/fallback/[slug].json`,
'x-now-route-matches': 'nxtPslug=second',
},
- }
+ })
)
const { pageProps: data2 } = await res2.json()
@@ -313,12 +356,12 @@ describe('Required Server Files', () => {
appPort,
'/catch-all/[[...rest]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
'x-now-route-matches': '',
},
- }
+ })
)
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -331,12 +374,12 @@ describe('Required Server Files', () => {
appPort,
'/catch-all/[[...rest]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
'x-now-route-matches': 'nxtPrest=hello',
},
- }
+ })
)
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -350,13 +393,13 @@ describe('Required Server Files', () => {
appPort,
'/catch-all/[[...rest]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
'x-now-route-matches':
'nxtPrest=hello/world&catchAll=hello/world',
},
- }
+ })
)
const $3 = cheerio.load(html3)
const data3 = JSON.parse($3('#props').text())
@@ -370,11 +413,11 @@ describe('Required Server Files', () => {
appPort,
'/catch-all/[[...rest]]',
{ nxtPrest: 'frank' },
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
},
- }
+ })
)
const $4 = cheerio.load(html4)
const data4 = JSON.parse($4('#props').text())
@@ -388,11 +431,11 @@ describe('Required Server Files', () => {
appPort,
'/catch-all/[[...rest]]',
{},
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
},
- }
+ })
)
const $5 = cheerio.load(html5)
const data5 = JSON.parse($5('#props').text())
@@ -406,11 +449,11 @@ describe('Required Server Files', () => {
appPort,
'/catch-all/[[...rest]]',
{ nxtPrest: 'frank' },
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
},
- }
+ })
)
const $6 = cheerio.load(html6)
const data6 = JSON.parse($6('#props').text())
@@ -443,11 +486,11 @@ describe('Required Server Files', () => {
appPort,
'/partial-catch-all/[domain]/[[...rest]]',
query,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/partial-catch-all/[domain]/[[...rest]]',
},
- }
+ })
)
const $ = cheerio.load(html)
@@ -464,11 +507,11 @@ describe('Required Server Files', () => {
appPort,
`/_next/data/${buildId}/catch-all.json`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
},
- }
+ })
)
const { pageProps: data } = await res.json()
@@ -480,12 +523,12 @@ describe('Required Server Files', () => {
appPort,
`/_next/data/${buildId}/catch-all/[[...rest]].json`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': `/_next/data/${buildId}/catch-all/[[...rest]].json`,
'x-now-route-matches': 'nxtPrest=hello&rest=hello',
},
- }
+ })
)
const { pageProps: data2 } = await res2.json()
@@ -497,12 +540,12 @@ describe('Required Server Files', () => {
appPort,
`/_next/data/${buildId}/catch-all/[[...rest]].json`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': `/_next/data/${buildId}/catch-all/[[...rest]].json`,
'x-now-route-matches': 'nxtPrest=hello/world&rest=hello/world',
},
- }
+ })
)
const { pageProps: data3 } = await res3.json()
@@ -521,9 +564,14 @@ describe('Required Server Files', () => {
'/fallback/another/',
'/fallback/another',
]) {
- const res = await fetchViaHTTP(appPort, path, undefined, {
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ path,
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res.status).toBe(200)
}
@@ -536,11 +584,11 @@ describe('Required Server Files', () => {
{
path: 'hello/world',
},
- {
+ withInvocationId({
headers: {
'x-matched-path': '/',
},
- }
+ })
)
const $ = cheerio.load(html)
expect(JSON.parse($('#router').text()).query).toEqual({
@@ -549,19 +597,34 @@ describe('Required Server Files', () => {
})
it('should bubble error correctly for gip page', async () => {
- const res = await fetchViaHTTP(appPort, '/errors/gip', { crash: '1' })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/errors/gip',
+ { crash: '1' },
+ withInvocationId()
+ )
expect(res.status).toBe(500)
expect(await res.text()).toBe('Internal Server Error')
})
it('should bubble error correctly for gssp page', async () => {
- const res = await fetchViaHTTP(appPort, '/errors/gssp', { crash: '1' })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/errors/gssp',
+ { crash: '1' },
+ withInvocationId()
+ )
expect(res.status).toBe(500)
expect(await res.text()).toBe('Internal Server Error')
})
it('should bubble error correctly for gsp page', async () => {
- const res = await fetchViaHTTP(appPort, '/errors/gsp/crash')
+ const res = await fetchViaHTTP(
+ appPort,
+ '/errors/gsp/crash',
+ undefined,
+ withInvocationId()
+ )
expect(res.status).toBe(500)
expect(await res.text()).toBe('Internal Server Error')
})
@@ -571,11 +634,11 @@ describe('Required Server Files', () => {
appPort,
'/optional-ssp',
{ nxtPrest: '', another: 'value' },
- {
+ withInvocationId({
headers: {
'x-matched-path': '/optional-ssp/[[...rest]]',
},
- }
+ })
)
const html = await res.text()
@@ -590,11 +653,11 @@ describe('Required Server Files', () => {
appPort,
'/optional-ssg',
{ rest: '', another: 'value' },
- {
+ withInvocationId({
headers: {
'x-matched-path': '/optional-ssg/[[...rest]]',
},
- }
+ })
)
const html = await res.text()
@@ -608,11 +671,11 @@ describe('Required Server Files', () => {
appPort,
'/api/optional',
{ nxtPrest: '', another: 'value' },
- {
+ withInvocationId({
headers: {
'x-matched-path': '/api/optional/[[...rest]]',
},
- }
+ })
)
const json = await res.json()
@@ -621,12 +684,17 @@ describe('Required Server Files', () => {
})
it('should match the index page correctly', async () => {
- const res = await fetchViaHTTP(appPort, '/', undefined, {
- headers: {
- 'x-matched-path': '/index',
- },
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/index',
+ },
+ redirect: 'manual',
+ })
+ )
const html = await res.text()
const $ = cheerio.load(html)
@@ -634,12 +702,17 @@ describe('Required Server Files', () => {
})
it('should match the root dynamic page correctly', async () => {
- const res = await fetchViaHTTP(appPort, '/slug-1', undefined, {
- headers: {
- 'x-matched-path': '/[slug]',
- },
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/slug-1',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/[slug]',
+ },
+ redirect: 'manual',
+ })
+ )
const html = await res.text()
const $ = cheerio.load(html)
@@ -652,12 +725,17 @@ describe('Required Server Files', () => {
'/non-existent',
'/404',
]) {
- const res = await fetchViaHTTP(appPort, pathname, undefined, {
- headers: {
- 'x-matched-path': '/404',
- redirect: 'manual',
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ pathname,
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/404',
+ redirect: 'manual',
+ },
+ })
+ )
expect(res.status).toBe(404)
expect(await res.text()).toContain('custom 404')
}
@@ -666,12 +744,17 @@ describe('Required Server Files', () => {
'/_next/static/chunks/pages/index-abc123.js',
'/_next/static/some-file.js',
]) {
- const res = await fetchViaHTTP(appPort, pathname, undefined, {
- headers: {
- 'x-matched-path': '/404',
- redirect: 'manual',
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ pathname,
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/404',
+ redirect: 'manual',
+ },
+ })
+ )
expect(res.status).toBe(404)
expect(res.headers.get('content-type')).toBe(
'text/plain; charset=utf-8'
diff --git a/test/production/standalone-mode/required-server-files/required-server-files-app.test.ts b/test/production/standalone-mode/required-server-files/required-server-files-app.test.ts
index 19a0aad43a173..02f10d104feb3 100644
--- a/test/production/standalone-mode/required-server-files/required-server-files-app.test.ts
+++ b/test/production/standalone-mode/required-server-files/required-server-files-app.test.ts
@@ -10,6 +10,7 @@ import {
findPort,
initNextServerScript,
killApp,
+ withInvocationId,
} from 'next-test-utils'
import { ChildProcess } from 'child_process'
@@ -103,14 +104,19 @@ describe('required server files app router', () => {
})
it('should send the right cache headers for an app route', async () => {
- const res = await fetchViaHTTP(appPort, '/api/test/123', undefined, {
- headers: {
- 'x-matched-path': '/api/test/[slug]',
- 'x-now-route-matches': createNowRouteMatches({
- slug: '123',
- }).toString(),
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/api/test/123',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/api/test/[slug]',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: '123',
+ }).toString(),
+ },
+ })
+ )
expect(res.status).toBe(200)
expect(res.headers.get('cache-control')).toBe('s-maxage=31536000')
})
@@ -120,7 +126,7 @@ describe('required server files app router', () => {
appPort,
'/optional-catchall/[lang]/[flags]/[[...slug]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/optional-catchall/[lang]/[flags]/[[...slug]]',
'x-now-route-matches': createNowRouteMatches({
@@ -129,7 +135,7 @@ describe('required server files app router', () => {
slug: 'slug',
}).toString(),
},
- }
+ })
)
expect(res.status).toBe(200)
@@ -142,7 +148,7 @@ describe('required server files app router', () => {
appPort,
'/optional-catchall/[lang]/[flags]/[[...slug]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/optional-catchall/[lang]/[flags]/[[...slug]]',
'x-now-route-matches': createNowRouteMatches({
@@ -150,7 +156,7 @@ describe('required server files app router', () => {
flags: 'flags',
}).toString(),
},
- }
+ })
)
expect(res.status).toBe(200)
@@ -162,14 +168,19 @@ describe('required server files app router', () => {
})
it('should send the right cache headers for an app page', async () => {
- const res = await fetchViaHTTP(appPort, '/test/123', undefined, {
- headers: {
- 'x-matched-path': '/test/[slug]',
- 'x-now-route-matches': createNowRouteMatches({
- slug: '123',
- }).toString(),
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/test/123',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/test/[slug]',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: '123',
+ }).toString(),
+ },
+ })
+ )
expect(res.status).toBe(200)
expect(res.headers.get('cache-control')).toBe(
's-maxage=3600, stale-while-revalidate=31532400'
@@ -181,13 +192,18 @@ describe('required server files app router', () => {
})
it('should properly handle prerender for bot request', async () => {
- const res = await fetchViaHTTP(appPort, '/isr/first', undefined, {
- headers: {
- 'user-agent':
- 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
- 'x-matched-path': '/isr/first',
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/isr/first',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'user-agent':
+ 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
+ 'x-matched-path': '/isr/first',
+ },
+ })
+ )
expect(res.status).toBe(200)
const html = await res.text()
@@ -195,28 +211,38 @@ describe('required server files app router', () => {
expect($('#page').text()).toBe('/isr/[slug]')
- const rscRes = await fetchViaHTTP(appPort, '/isr/first.rsc', undefined, {
- headers: {
- 'user-agent':
- 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
- 'x-matched-path': '/isr/first',
- },
- })
+ const rscRes = await fetchViaHTTP(
+ appPort,
+ '/isr/first.rsc',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'user-agent':
+ 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
+ 'x-matched-path': '/isr/first',
+ },
+ })
+ )
expect(rscRes.status).toBe(200)
})
it('should properly handle fallback for bot request', async () => {
- const res = await fetchViaHTTP(appPort, '/isr/[slug]', undefined, {
- headers: {
- 'user-agent':
- 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'new',
- }).toString(),
- 'x-matched-path': '/isr/[slug]',
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/isr/[slug]',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'user-agent':
+ 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'new',
+ }).toString(),
+ 'x-matched-path': '/isr/[slug]',
+ },
+ })
+ )
expect(res.status).toBe(200)
const html = await res.text()
@@ -224,16 +250,21 @@ describe('required server files app router', () => {
expect($('#page').text()).toBe('/isr/[slug]')
- const rscRes = await fetchViaHTTP(appPort, '/isr/[slug].rsc', undefined, {
- headers: {
- 'user-agent':
- 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'new',
- }).toString(),
- 'x-matched-path': '/isr/[slug]',
- },
- })
+ const rscRes = await fetchViaHTTP(
+ appPort,
+ '/isr/[slug].rsc',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'user-agent':
+ 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'new',
+ }).toString(),
+ 'x-matched-path': '/isr/[slug]',
+ },
+ })
+ )
expect(rscRes.status).toBe(200)
})
@@ -258,9 +289,14 @@ describe('required server files app router', () => {
],
]) {
require('console').error('checking', { path, tags })
- const res = await fetchViaHTTP(appPort, path, undefined, {
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ path,
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res.status).toBe(200)
expect(res.headers.get('x-next-cache-tags')).toBe(tags)
}
@@ -273,9 +309,14 @@ describe('required server files app router', () => {
'/api/ssr/first',
'/api/ssr/second',
]) {
- const res = await fetchViaHTTP(appPort, path, undefined, {
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ path,
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res.status).toBe(200)
expect(res.headers.get('x-next-cache-tags')).toBeFalsy()
}
@@ -292,9 +333,9 @@ describe('required server files app router', () => {
appPort,
path,
{ hello: 'world' },
- {
+ withInvocationId({
redirect: 'manual',
- }
+ })
)
expect(res.status).toBe(200)
expect(res.headers.get('x-next-cache-tags')).toBeFalsy()
@@ -306,11 +347,11 @@ describe('required server files app router', () => {
appPort,
'/search/[key]',
{ key: 'searchParams', nxtPkey: 'params' },
- {
+ withInvocationId({
headers: {
'x-matched-path': '/search/[key]',
},
- }
+ })
)
const html = await res.text()
@@ -318,4 +359,80 @@ describe('required server files app router', () => {
expect($('dd[data-params]').text()).toBe('params')
expect($('dd[data-searchParams]').text()).toBe('searchParams')
})
+
+ it('should de-dupe HTML/RSC requests for ISR pages', async () => {
+ // Create a shared invocation ID for HTML and RSC requests to test de-duplication
+ const sharedOpts = withInvocationId()
+
+ // First request: HTML for ISR page
+ const htmlRes = await fetchViaHTTP(appPort, '/isr/[slug]', undefined, {
+ ...sharedOpts,
+ headers: {
+ ...sharedOpts.headers,
+ 'x-matched-path': '/isr/[slug]',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'first',
+ }).toString(),
+ },
+ })
+ expect(htmlRes.status).toBe(200)
+ const html = await htmlRes.text()
+ const $ = cheerio.load(html)
+ const timestamp1 = $('#now').text()
+
+ // Second request: RSC for same page with same x-invocation-id
+ const rscRes = await fetchViaHTTP(appPort, '/isr/[slug].rsc', undefined, {
+ ...sharedOpts,
+ headers: {
+ ...sharedOpts.headers,
+ 'x-matched-path': '/isr/[slug]',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'first',
+ }).toString(),
+ },
+ })
+ expect(rscRes.status).toBe(200)
+ const rscText = await rscRes.text()
+
+ // Both should have the same timestamp (same cached render)
+ expect(rscText).toContain(timestamp1)
+ })
+
+ it('should isolate cache between different ISR request groups', async () => {
+ // First group makes a request with its own invocation ID
+ const group1Opts = withInvocationId()
+ const res1 = await fetchViaHTTP(appPort, '/isr/[slug]', undefined, {
+ ...group1Opts,
+ headers: {
+ ...group1Opts.headers,
+ 'x-matched-path': '/isr/[slug]',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'first',
+ }).toString(),
+ },
+ })
+ expect(res1.status).toBe(200)
+ const $1 = cheerio.load(await res1.text())
+ const data1 = $1('#data').text()
+
+ // Second group with different x-invocation-id
+ const group2Opts = withInvocationId()
+ const res2 = await fetchViaHTTP(appPort, '/isr/[slug]', undefined, {
+ ...group2Opts,
+ headers: {
+ ...group2Opts.headers,
+ 'x-matched-path': '/isr/[slug]',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'first',
+ }).toString(),
+ },
+ })
+ expect(res2.status).toBe(200)
+ const $2 = cheerio.load(await res2.text())
+ const data2 = $2('#data').text()
+
+ // Each group should get its own render with different random data
+ // since ISR pages fetch fresh data on each render
+ expect(data1).not.toBe(data2)
+ })
})
diff --git a/test/production/standalone-mode/required-server-files/required-server-files-i18n.test.ts b/test/production/standalone-mode/required-server-files/required-server-files-i18n.test.ts
index b6ba636e8a769..50ce6820835dd 100644
--- a/test/production/standalone-mode/required-server-files/required-server-files-i18n.test.ts
+++ b/test/production/standalone-mode/required-server-files/required-server-files-i18n.test.ts
@@ -13,6 +13,7 @@ import {
killApp,
renderViaHTTP,
waitFor,
+ withInvocationId,
} from 'next-test-utils'
import nodeFetch from 'node-fetch'
import { ChildProcess } from 'child_process'
@@ -136,22 +137,32 @@ describe('required server files i18n', () => {
})
it('should not apply locale redirect in minimal mode', async () => {
- const res = await fetchViaHTTP(appPort, '/', undefined, {
- redirect: 'manual',
- headers: {
- 'accept-language': 'fr',
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/',
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ headers: {
+ 'accept-language': 'fr',
+ },
+ })
+ )
expect(res.status).toBe(200)
expect(await res.text()).toContain('index page')
- const resCookie = await fetchViaHTTP(appPort, '/', undefined, {
- redirect: 'manual',
- headers: {
- 'accept-language': 'en',
- cookie: 'NEXT_LOCALE=fr',
- },
- })
+ const resCookie = await fetchViaHTTP(
+ appPort,
+ '/',
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ headers: {
+ 'accept-language': 'en',
+ cookie: 'NEXT_LOCALE=fr',
+ },
+ })
+ )
expect(resCookie.status).toBe(200)
expect(await resCookie.text()).toContain('index page')
})
@@ -170,9 +181,14 @@ describe('required server files i18n', () => {
it('should set correct SWR headers with notFound gsp', async () => {
await next.patchFile('standalone/data.txt', 'show')
- const res = await fetchViaHTTP(appPort, '/gsp', undefined, {
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/gsp',
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res.status).toBe(200)
expect(res.headers.get('cache-control')).toBe(
's-maxage=1, stale-while-revalidate=31535999'
@@ -181,9 +197,14 @@ describe('required server files i18n', () => {
await waitFor(2000)
await next.patchFile('standalone/data.txt', 'hide')
- const res2 = await fetchViaHTTP(appPort, '/gsp', undefined, {
- redirect: 'manual',
- })
+ const res2 = await fetchViaHTTP(
+ appPort,
+ '/gsp',
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res2.status).toBe(404)
expect(res2.headers.get('cache-control')).toBe(
's-maxage=1, stale-while-revalidate=31535999'
@@ -193,9 +214,14 @@ describe('required server files i18n', () => {
it('should set correct SWR headers with notFound gssp', async () => {
await next.patchFile('standalone/data.txt', 'show')
- const res = await fetchViaHTTP(appPort, '/gssp', undefined, {
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/gssp',
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res.status).toBe(200)
expect(res.headers.get('cache-control')).toBe(
's-maxage=1, stale-while-revalidate=31535999'
@@ -203,9 +229,14 @@ describe('required server files i18n', () => {
await next.patchFile('standalone/data.txt', 'hide')
- const res2 = await fetchViaHTTP(appPort, '/gssp', undefined, {
- redirect: 'manual',
- })
+ const res2 = await fetchViaHTTP(
+ appPort,
+ '/gssp',
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
await next.patchFile('standalone/data.txt', 'show')
expect(res2.status).toBe(404)
@@ -215,14 +246,24 @@ describe('required server files i18n', () => {
})
it('should render SSR page correctly', async () => {
- const html = await renderViaHTTP(appPort, '/gssp')
+ const html = await renderViaHTTP(
+ appPort,
+ '/gssp',
+ undefined,
+ withInvocationId()
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
expect($('#gssp').text()).toBe('getServerSideProps page')
expect(data.hello).toBe('world')
- const html2 = await renderViaHTTP(appPort, '/gssp')
+ const html2 = await renderViaHTTP(
+ appPort,
+ '/gssp',
+ undefined,
+ withInvocationId()
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -232,7 +273,12 @@ describe('required server files i18n', () => {
})
it('should render dynamic SSR page correctly', async () => {
- const html = await renderViaHTTP(appPort, '/dynamic/first')
+ const html = await renderViaHTTP(
+ appPort,
+ '/dynamic/first',
+ undefined,
+ withInvocationId()
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -240,7 +286,12 @@ describe('required server files i18n', () => {
expect($('#slug').text()).toBe('first')
expect(data.hello).toBe('world')
- const html2 = await renderViaHTTP(appPort, '/dynamic/second')
+ const html2 = await renderViaHTTP(
+ appPort,
+ '/dynamic/second',
+ undefined,
+ withInvocationId()
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -251,7 +302,12 @@ describe('required server files i18n', () => {
})
it('should render fallback page correctly', async () => {
- const html = await renderViaHTTP(appPort, '/fallback/first')
+ const html = await renderViaHTTP(
+ appPort,
+ '/fallback/first',
+ undefined,
+ withInvocationId()
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -260,7 +316,13 @@ describe('required server files i18n', () => {
expect(data.hello).toBe('world')
await waitFor(2000)
- const html2 = await renderViaHTTP(appPort, '/fallback/first')
+
+ const html2 = await renderViaHTTP(
+ appPort,
+ '/fallback/first',
+ undefined,
+ withInvocationId()
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -269,7 +331,12 @@ describe('required server files i18n', () => {
expect(isNaN(data2.random)).toBe(false)
expect(data2.random).not.toBe(data.random)
- const html3 = await renderViaHTTP(appPort, '/fallback/second')
+ const html3 = await renderViaHTTP(
+ appPort,
+ '/fallback/second',
+ undefined,
+ withInvocationId()
+ )
const $3 = cheerio.load(html3)
const data3 = JSON.parse($3('#props').text())
@@ -280,7 +347,9 @@ describe('required server files i18n', () => {
const { pageProps: data4 } = JSON.parse(
await renderViaHTTP(
appPort,
- `/_next/data/${next.buildId}/en/fallback/third.json`
+ `/_next/data/${next.buildId}/en/fallback/third.json`,
+ undefined,
+ withInvocationId()
)
)
expect(data4.hello).toBe('world')
@@ -288,22 +357,32 @@ describe('required server files i18n', () => {
})
it('should render SSR page correctly with x-matched-path', async () => {
- const html = await renderViaHTTP(appPort, '/some-other-path', undefined, {
- headers: {
- 'x-matched-path': '/gssp',
- },
- })
+ const html = await renderViaHTTP(
+ appPort,
+ '/some-other-path',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/gssp',
+ },
+ })
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
expect($('#gssp').text()).toBe('getServerSideProps page')
expect(data.hello).toBe('world')
- const html2 = await renderViaHTTP(appPort, '/some-other-path', undefined, {
- headers: {
- 'x-matched-path': '/gssp',
- },
- })
+ const html2 = await renderViaHTTP(
+ appPort,
+ '/some-other-path',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/gssp',
+ },
+ })
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -317,11 +396,11 @@ describe('required server files i18n', () => {
appPort,
'/some-other-path?nxtPslug=first',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/dynamic/[slug]',
},
- }
+ })
)
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -334,11 +413,11 @@ describe('required server files i18n', () => {
appPort,
'/some-other-path?nxtPslug=second',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/dynamic/[slug]',
},
- }
+ })
)
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -352,11 +431,11 @@ describe('required server files i18n', () => {
appPort,
'/some-other-path?nxtPslug=second',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/dynamic/[slug]?slug=%5Bslug%5D.json',
},
- }
+ })
)
const $3 = cheerio.load(html3)
const data3 = JSON.parse($3('#props').text())
@@ -368,14 +447,19 @@ describe('required server files i18n', () => {
})
it('should render fallback page correctly with x-matched-path and routes-matches', async () => {
- const html = await renderViaHTTP(appPort, '/fallback/first', undefined, {
- headers: {
- 'x-matched-path': '/fallback/first',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'first',
- }).toString(),
- },
- })
+ const html = await renderViaHTTP(
+ appPort,
+ '/fallback/first',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/fallback/first',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'first',
+ }).toString(),
+ },
+ })
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -383,14 +467,19 @@ describe('required server files i18n', () => {
expect($('#slug').text()).toBe('first')
expect(data.hello).toBe('world')
- const html2 = await renderViaHTTP(appPort, `/fallback/[slug]`, undefined, {
- headers: {
- 'x-matched-path': '/fallback/[slug]',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'second',
- }).toString(),
- },
- })
+ const html2 = await renderViaHTTP(
+ appPort,
+ `/fallback/[slug]`,
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/fallback/[slug]',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'second',
+ }).toString(),
+ },
+ })
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -407,11 +496,11 @@ describe('required server files i18n', () => {
{ slug: 'first' }
).toString()}`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/dynamic/[slug]',
},
- }
+ })
)
const { pageProps: data } = await res.json()
@@ -423,14 +512,14 @@ describe('required server files i18n', () => {
appPort,
`/_next/data/${next.buildId}/en/fallback/[slug].json`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': `/_next/data/${next.buildId}/en/fallback/[slug].json`,
'x-now-route-matches': createNowRouteMatches({
slug: 'second',
}).toString(),
},
- }
+ })
)
const { pageProps: data2 } = await res2.json()
@@ -444,12 +533,12 @@ describe('required server files i18n', () => {
appPort,
'/catch-all/[[...rest]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
'x-now-route-matches': '',
},
- }
+ })
)
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -462,14 +551,14 @@ describe('required server files i18n', () => {
appPort,
'/catch-all/[[...rest]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
'x-now-route-matches': createNowRouteMatches({
rest: 'hello',
}).toString(),
},
- }
+ })
)
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -483,14 +572,14 @@ describe('required server files i18n', () => {
appPort,
'/catch-all/[[...rest]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
'x-now-route-matches': createNowRouteMatches({
rest: 'hello/world',
}).toString(),
},
- }
+ })
)
const $3 = cheerio.load(html3)
const data3 = JSON.parse($3('#props').text())
@@ -506,11 +595,11 @@ describe('required server files i18n', () => {
appPort,
`/_next/data/${next.buildId}/en/catch-all.json`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/en/catch-all/[[...rest]]',
},
- }
+ })
)
const { pageProps: data } = await res.json()
@@ -522,14 +611,14 @@ describe('required server files i18n', () => {
appPort,
`/_next/data/${next.buildId}/en/catch-all/[[...rest]].json`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': `/_next/data/${next.buildId}/en/catch-all/[[...rest]].json`,
'x-now-route-matches': createNowRouteMatches({
rest: 'hello',
}).toString(),
},
- }
+ })
)
const { pageProps: data2 } = await res2.json()
@@ -541,14 +630,14 @@ describe('required server files i18n', () => {
appPort,
`/_next/data/${next.buildId}/en/catch-all/[[...rest]].json`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': `/_next/data/${next.buildId}/en/catch-all/[[...rest]].json`,
'x-now-route-matches': createNowRouteMatches({
rest: 'hello/world',
}).toString(),
},
- }
+ })
)
const { pageProps: data3 } = await res3.json()
@@ -567,9 +656,14 @@ describe('required server files i18n', () => {
'/fallback/another/',
'/fallback/another',
]) {
- const res = await fetchViaHTTP(appPort, path, undefined, {
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ path,
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res.status).toBe(200)
}
@@ -582,11 +676,11 @@ describe('required server files i18n', () => {
{
path: 'hello/world',
},
- {
+ withInvocationId({
headers: {
'x-matched-path': '/gssp',
},
- }
+ })
)
const $ = cheerio.load(html)
expect(JSON.parse($('#router').text()).query.path).toEqual([
@@ -604,18 +698,23 @@ describe('required server files i18n', () => {
path: '%c0.%c0.',
})
),
- {
+ withInvocationId({
headers: {
'x-matched-path': '/dynamic/[slug]',
},
- }
+ })
)
expect(res.status).toBe(400)
expect(await res.text()).toContain('Bad Request')
})
it('should bubble error correctly for gip page', async () => {
- const res = await fetchViaHTTP(appPort, '/errors/gip', { crash: '1' })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/errors/gip',
+ { crash: '1' },
+ withInvocationId()
+ )
expect(res.status).toBe(500)
expect(await res.text()).toBe('Internal Server Error')
@@ -629,7 +728,12 @@ describe('required server files i18n', () => {
})
it('should bubble error correctly for gssp page', async () => {
- const res = await fetchViaHTTP(appPort, '/errors/gssp', { crash: '1' })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/errors/gssp',
+ { crash: '1' },
+ withInvocationId()
+ )
expect(res.status).toBe(500)
expect(await res.text()).toBe('Internal Server Error')
await check(
@@ -642,7 +746,12 @@ describe('required server files i18n', () => {
})
it('should bubble error correctly for gsp page', async () => {
- const res = await fetchViaHTTP(appPort, '/errors/gsp/crash')
+ const res = await fetchViaHTTP(
+ appPort,
+ '/errors/gsp/crash',
+ undefined,
+ withInvocationId()
+ )
expect(res.status).toBe(500)
expect(await res.text()).toBe('Internal Server Error')
await check(
@@ -656,7 +765,12 @@ describe('required server files i18n', () => {
it('should bubble error correctly for API page', async () => {
errors = []
- const res = await fetchViaHTTP(appPort, '/api/error')
+ const res = await fetchViaHTTP(
+ appPort,
+ '/api/error',
+ undefined,
+ withInvocationId()
+ )
expect(res.status).toBe(500)
expect(await res.text()).toBe('Internal Server Error')
await check(
@@ -682,11 +796,11 @@ describe('required server files i18n', () => {
}
)
),
- {
+ withInvocationId({
headers: {
'x-matched-path': '/optional-ssp/[[...rest]]',
},
- }
+ })
)
const html = await res.text()
@@ -701,7 +815,7 @@ describe('required server files i18n', () => {
appPort,
'/en/optional-ssg/[[...rest]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/en/optional-ssg/[[...rest]]',
'x-now-route-matches': createNowRouteMatches(
@@ -711,7 +825,7 @@ describe('required server files i18n', () => {
}
).toString(),
},
- }
+ })
)
const html = await res.text()
@@ -725,7 +839,7 @@ describe('required server files i18n', () => {
appPort,
'/en/[slug]/social/[[...rest]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/en/[slug]/social/[[...rest]]',
'x-now-route-matches': createNowRouteMatches(
@@ -733,7 +847,7 @@ describe('required server files i18n', () => {
{ nextLocale: 'en' }
).toString(),
},
- }
+ })
)
const html = await res.text()
@@ -750,12 +864,12 @@ describe('required server files i18n', () => {
appPort,
'/optional-ssg/[[...rest]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/optional-ssg/[[...rest]]',
'x-now-route-matches': 'nxtPrest=en%2Fes%2Fhello%252Fworld',
},
- }
+ })
)
const html = await res.text()
@@ -780,11 +894,11 @@ describe('required server files i18n', () => {
}
)
),
- {
+ withInvocationId({
headers: {
'x-matched-path': '/api/optional/[[...rest]]',
},
- }
+ })
)
const json = await res.json()
@@ -793,12 +907,17 @@ describe('required server files i18n', () => {
})
it('should match the index page correctly', async () => {
- const res = await fetchViaHTTP(appPort, '/', undefined, {
- headers: {
- 'x-matched-path': '/index',
- },
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/index',
+ },
+ redirect: 'manual',
+ })
+ )
const html = await res.text()
const $ = cheerio.load(html)
@@ -806,12 +925,17 @@ describe('required server files i18n', () => {
})
it('should match the root dyanmic page correctly', async () => {
- const res = await fetchViaHTTP(appPort, '/slug-1', undefined, {
- headers: {
- 'x-matched-path': '/[slug]',
- },
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/slug-1',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/[slug]',
+ },
+ redirect: 'manual',
+ })
+ )
const html = await res.text()
const $ = cheerio.load(html)
@@ -819,20 +943,25 @@ describe('required server files i18n', () => {
})
it('should have the correct asPath for fallback page', async () => {
- const res = await fetchViaHTTP(appPort, '/en/fallback/[slug]', undefined, {
- headers: {
- 'x-matched-path': '/en/fallback/[slug]',
- 'x-now-route-matches': createNowRouteMatches(
- {
- slug: 'another',
- },
- {
- nextLocale: 'en',
- }
- ).toString(),
- },
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/en/fallback/[slug]',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/en/fallback/[slug]',
+ 'x-now-route-matches': createNowRouteMatches(
+ {
+ slug: 'another',
+ },
+ {
+ nextLocale: 'en',
+ }
+ ).toString(),
+ },
+ redirect: 'manual',
+ })
+ )
const html = await res.text()
const $ = cheerio.load(html)
@@ -844,20 +973,25 @@ describe('required server files i18n', () => {
})
it('should have the correct asPath for fallback page locale', async () => {
- const res = await fetchViaHTTP(appPort, '/fr/fallback/[slug]', undefined, {
- headers: {
- 'x-matched-path': '/fr/fallback/[slug]',
- 'x-now-route-matches': createNowRouteMatches(
- {
- slug: 'another',
- },
- {
- nextLocale: 'fr',
- }
- ).toString(),
- },
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/fr/fallback/[slug]',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/fr/fallback/[slug]',
+ 'x-now-route-matches': createNowRouteMatches(
+ {
+ slug: 'another',
+ },
+ {
+ nextLocale: 'fr',
+ }
+ ).toString(),
+ },
+ redirect: 'manual',
+ })
+ )
const html = await res.text()
const $ = cheerio.load(html)
diff --git a/test/production/standalone-mode/required-server-files/required-server-files-ppr.test.ts b/test/production/standalone-mode/required-server-files/required-server-files-ppr.test.ts
index 3329c96d7a4a7..5cbc455a2decf 100644
--- a/test/production/standalone-mode/required-server-files/required-server-files-ppr.test.ts
+++ b/test/production/standalone-mode/required-server-files/required-server-files-ppr.test.ts
@@ -10,6 +10,7 @@ import {
initNextServerScript,
killApp,
retry,
+ withInvocationId,
} from 'next-test-utils'
import { ChildProcess } from 'node:child_process'
@@ -144,14 +145,19 @@ describe.skip('required server files app router', () => {
require('console').error('requesting', outputSegmentPath)
- const res = await fetchViaHTTP(appPort, outputSegmentPath, undefined, {
- headers: {
- 'x-matched-path': '/isr/[slug].segments/_tree.segment.rsc',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'first',
- }).toString(),
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ outputSegmentPath,
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/isr/[slug].segments/_tree.segment.rsc',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'first',
+ }).toString(),
+ },
+ })
+ )
expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toBe('text/x-component')
@@ -167,14 +173,19 @@ describe.skip('required server files app router', () => {
})
it('should properly stream resume with Next-Resume', async () => {
- const res = await fetchViaHTTP(appPort, '/delayed', undefined, {
- headers: {
- 'x-matched-path': '/delayed',
- 'next-resume': '1',
- },
- method: 'POST',
- body: delayedPostpone,
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/delayed',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/delayed',
+ 'next-resume': '1',
+ },
+ method: 'POST',
+ body: delayedPostpone,
+ })
+ )
expect(res.status).toBe(200)
@@ -199,13 +210,18 @@ describe.skip('required server files app router', () => {
})
it('should properly handle prerender for bot request', async () => {
- const res = await fetchViaHTTP(appPort, '/isr/first', undefined, {
- headers: {
- 'user-agent':
- 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
- 'x-matched-path': '/isr/first',
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/isr/first',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'user-agent':
+ 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
+ 'x-matched-path': '/isr/first',
+ },
+ })
+ )
expect(res.status).toBe(200)
const html = await res.text()
@@ -213,28 +229,38 @@ describe.skip('required server files app router', () => {
expect($('#page').text()).toBe('/isr/[slug]')
- const rscRes = await fetchViaHTTP(appPort, '/isr/first.rsc', undefined, {
- headers: {
- 'user-agent':
- 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
- 'x-matched-path': '/isr/first',
- },
- })
+ const rscRes = await fetchViaHTTP(
+ appPort,
+ '/isr/first.rsc',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'user-agent':
+ 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
+ 'x-matched-path': '/isr/first',
+ },
+ })
+ )
expect(rscRes.status).toBe(200)
})
it('should properly handle fallback for bot request', async () => {
- const res = await fetchViaHTTP(appPort, '/isr/[slug]', undefined, {
- headers: {
- 'user-agent':
- 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'new',
- }).toString(),
- 'x-matched-path': '/isr/[slug]',
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/isr/[slug]',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'user-agent':
+ 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'new',
+ }).toString(),
+ 'x-matched-path': '/isr/[slug]',
+ },
+ })
+ )
expect(res.status).toBe(200)
const html = await res.text()
@@ -242,16 +268,21 @@ describe.skip('required server files app router', () => {
expect($('#page').text()).toBe('/isr/[slug]')
- const rscRes = await fetchViaHTTP(appPort, '/isr/[slug].rsc', undefined, {
- headers: {
- 'user-agent':
- 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'new',
- }).toString(),
- 'x-matched-path': '/isr/[slug]',
- },
- })
+ const rscRes = await fetchViaHTTP(
+ appPort,
+ '/isr/[slug].rsc',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'user-agent':
+ 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.179 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'new',
+ }).toString(),
+ 'x-matched-path': '/isr/[slug]',
+ },
+ })
+ )
expect(rscRes.status).toBe(200)
})
@@ -267,7 +298,7 @@ describe.skip('required server files app router', () => {
// route backed by PPR.
`/_next/data/${next.buildId}/index.json`,
undefined,
- {
+ withInvocationId({
method: 'POST',
headers: {
'x-matched-path': '/[...catchAll]',
@@ -277,7 +308,7 @@ describe.skip('required server files app router', () => {
'next-resume': '1',
},
body: postponed,
- }
+ })
)
// Expect that the status code is 422, we asked for a /_next/data route and
@@ -301,14 +332,14 @@ describe.skip('required server files app router', () => {
appPort,
'/rewrite-with-cookie',
undefined,
- {
+ withInvocationId({
method: 'POST',
headers: {
'x-matched-path': '/rewrite/first-cookie',
'next-resume': '1',
},
body: rewritePostpone,
- }
+ })
)
expect(res.status).toBe(200)
@@ -327,15 +358,20 @@ describe.skip('required server files app router', () => {
it('should still render when postponed is corrupted with Next-Resume', async () => {
const random = Math.random().toString(36).substring(2)
- const res = await fetchViaHTTP(appPort, '/dyn/' + random, undefined, {
- method: 'POST',
- headers: {
- 'x-matched-path': '/dyn/[slug]',
- 'next-resume': '1',
- },
- // This is a corrupted postponed JSON payload.
- body: '{',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/dyn/' + random,
+ undefined,
+ withInvocationId({
+ method: 'POST',
+ headers: {
+ 'x-matched-path': '/dyn/[slug]',
+ 'next-resume': '1',
+ },
+ // This is a corrupted postponed JSON payload.
+ body: '{',
+ })
+ )
expect(res.status).toBe(200)
@@ -369,9 +405,14 @@ describe.skip('required server files app router', () => {
],
]) {
require('console').error('checking', { path, tags })
- const res = await fetchViaHTTP(appPort, path, undefined, {
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ path,
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res.status).toBe(200)
expect(res.headers.get('x-next-cache-tags')).toBe(tags)
@@ -390,9 +431,14 @@ describe.skip('required server files app router', () => {
'/api/ssr/first',
'/api/ssr/second',
]) {
- const res = await fetchViaHTTP(appPort, path, undefined, {
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ path,
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res.status).toBe(200)
expect(res.headers.get('x-next-cache-tags')).toBeFalsy()
@@ -416,9 +462,9 @@ describe.skip('required server files app router', () => {
appPort,
path,
{ hello: 'world' },
- {
+ withInvocationId({
redirect: 'manual',
- }
+ })
)
expect(res.status).toBe(200)
@@ -433,14 +479,19 @@ describe.skip('required server files app router', () => {
})
it('should handle RSC requests', async () => {
- const res = await fetchViaHTTP(appPort, '/dyn/first.rsc', undefined, {
- headers: {
- 'x-matched-path': '/dyn/[slug]',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'first',
- }).toString(),
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/dyn/first.rsc',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/dyn/[slug]',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'first',
+ }).toString(),
+ },
+ })
+ )
expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toEqual('text/x-component')
@@ -466,14 +517,14 @@ describe.skip('required server files app router', () => {
appPort,
'/rewrite/second-cookie.rsc',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/rewrite/[slug]',
'x-now-route-matches': createNowRouteMatches({
slug: 'second-cookie',
}).toString(),
},
- }
+ })
)
expect(res.status).toBe(200)
@@ -508,17 +559,22 @@ describe.skip('required server files app router', () => {
// Then let's get the Dynamic RSC request and verify that the random value
// is present in the response by passing the postponed state.
- res = await fetchViaHTTP(appPort, '/rewrite/second-cookie.rsc', undefined, {
- method: 'POST',
- headers: {
- 'x-matched-path': '/rewrite/[slug]',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'second-cookie',
- }).toString(),
- 'next-resume': '1',
- },
- body: secondCookiePostpone,
- })
+ res = await fetchViaHTTP(
+ appPort,
+ '/rewrite/second-cookie.rsc',
+ undefined,
+ withInvocationId({
+ method: 'POST',
+ headers: {
+ 'x-matched-path': '/rewrite/[slug]',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'second-cookie',
+ }).toString(),
+ 'next-resume': '1',
+ },
+ body: secondCookiePostpone,
+ })
+ )
expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toEqual('text/x-component')
@@ -551,14 +607,19 @@ describe.skip('required server files app router', () => {
})
it('should handle revalidating the fallback page', async () => {
- const res = await fetchViaHTTP(appPort, '/postpone/isr/[slug]', undefined, {
- headers: {
- 'x-matched-path': '/postpone/isr/[slug]',
- // We don't include the `x-now-route-matches` header because we want to
- // test that the fallback route params are correctly set.
- 'x-now-route-matches': '',
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/postpone/isr/[slug]',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/postpone/isr/[slug]',
+ // We don't include the `x-now-route-matches` header because we want to
+ // test that the fallback route params are correctly set.
+ 'x-now-route-matches': '',
+ },
+ })
+ )
expect(res.status).toBe(200)
diff --git a/test/production/standalone-mode/required-server-files/required-server-files.test.ts b/test/production/standalone-mode/required-server-files/required-server-files.test.ts
index 47f60363ebbcd..7ced25b418ff1 100644
--- a/test/production/standalone-mode/required-server-files/required-server-files.test.ts
+++ b/test/production/standalone-mode/required-server-files/required-server-files.test.ts
@@ -15,6 +15,7 @@ import {
renderViaHTTP,
retry,
waitFor,
+ withInvocationId,
} from 'next-test-utils'
import { ChildProcess } from 'child_process'
@@ -177,12 +178,12 @@ describe('required server files', () => {
appPort,
'/route-resolving/import/first',
undefined,
- {
+ withInvocationId({
redirect: 'manual',
headers: {
'x-matched-path': '/route-resolving/import/[slug]',
},
- }
+ })
)
expect(res.status).toBe(307)
expect(new URL(res.headers.get('location'), 'http://n').pathname).toBe(
@@ -201,11 +202,16 @@ describe('required server files', () => {
await next.renameFile(toRename, `${toRename}.bak`)
try {
- const res = await fetchViaHTTP(appPort, '/auto-static', undefined, {
- headers: {
- 'x-matched-path': '/auto-static',
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/auto-static',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/auto-static',
+ },
+ })
+ )
expect(res.status).toBe(500)
await check(() => stderr, /Invariant: failed to load static page/)
@@ -230,9 +236,14 @@ describe('required server files', () => {
])(
`should have correct cache-control for $case`,
async ({ path, dest, cacheControl }) => {
- const res = await fetchViaHTTP(appPort, path, undefined, {
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ path,
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res.status).toBe(307)
expect(new URL(res.headers.get('location'), 'http://n').pathname).toBe(
dest
@@ -243,9 +254,9 @@ describe('required server files', () => {
appPort,
`/_next/data/${next.buildId}${path}.json`,
undefined,
- {
+ withInvocationId({
redirect: 'manual',
- }
+ })
)
expect((await dataRes.json()).pageProps).toEqual({
__N_REDIRECT: dest,
@@ -260,11 +271,11 @@ describe('required server files', () => {
appPort,
`/_next/data/${next.buildId}/catch-all.json`,
{},
- {
+ withInvocationId({
headers: {
'x-matched-path': `/_next/data/${next.buildId}/catch-all.json`,
},
- }
+ })
)
expect(res.status).toBe(200)
@@ -277,11 +288,11 @@ describe('required server files', () => {
appPort,
`/_next/data/${next.buildId}/catch-all/next.js.json`,
{},
- {
+ withInvocationId({
headers: {
'x-matched-path': `/_next/data/${next.buildId}/catch-all/next.js.json`,
},
- }
+ })
)
expect(res.status).toBe(200)
@@ -307,9 +318,14 @@ describe('required server files', () => {
])(
`should have correct cache-control for $case`,
async ({ path, dest, cacheControl }) => {
- const res = await fetchViaHTTP(appPort, path, undefined, {
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ path,
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res.status).toBe(404)
expect(res.headers.get('cache-control')).toBe(cacheControl)
@@ -317,16 +333,21 @@ describe('required server files', () => {
appPort,
`/_next/data/${next.buildId}${path}.json`,
undefined,
- {
+ withInvocationId({
redirect: 'manual',
- }
+ })
)
expect(dataRes.headers.get('cache-control')).toBe(cacheControl)
}
)
it('should have the correct cache-control for props with no revalidate', async () => {
- const res = await fetchViaHTTP(appPort, '/optional-ssg/props-no-revalidate')
+ const res = await fetchViaHTTP(
+ appPort,
+ '/optional-ssg/props-no-revalidate',
+ undefined,
+ withInvocationId()
+ )
expect(res.status).toBe(200)
expect(res.headers.get('cache-control')).toBe('s-maxage=31536000')
const $ = cheerio.load(await res.text())
@@ -337,7 +358,8 @@ describe('required server files', () => {
const dataRes = await fetchViaHTTP(
appPort,
`/_next/data/${next.buildId}/optional-ssg/props-no-revalidate.json`,
- undefined
+ undefined,
+ withInvocationId()
)
expect(dataRes.status).toBe(200)
expect(res.headers.get('cache-control')).toBe('s-maxage=31536000')
@@ -350,7 +372,7 @@ describe('required server files', () => {
;(process.env.IS_TURBOPACK_TEST ? it.skip : it)(
'should warn when "next" is imported directly',
async () => {
- await renderViaHTTP(appPort, '/gssp')
+ await renderViaHTTP(appPort, '/gssp', undefined, withInvocationId())
await check(
() => stderr,
/"next" should not be imported directly, imported in/
@@ -430,9 +452,14 @@ describe('required server files', () => {
})
it('should de-dupe HTML/data requests', async () => {
+ // Create a shared invocation ID for /gsp - both HTML and JSON requests share same x-invocation-id
+ const gspOpts = withInvocationId()
+
const res = await fetchViaHTTP(appPort, '/gsp', undefined, {
+ ...gspOpts,
redirect: 'manual',
headers: {
+ ...gspOpts.headers,
// ensure the nextjs-data header being present
// doesn't incorrectly return JSON for HTML path
// during prerendering
@@ -450,6 +477,7 @@ describe('required server files', () => {
`/_next/data/${next.buildId}/gsp.json`,
undefined,
{
+ ...gspOpts,
redirect: 'manual',
}
)
@@ -458,9 +486,14 @@ describe('required server files', () => {
const { pageProps: props2 } = await res2.json()
expect(props2.gspCalls).toBe(props.gspCalls)
+ // Create a separate shared invocation ID for /index - different x-invocation-id
+ const indexOpts = withInvocationId()
+
const res3 = await fetchViaHTTP(appPort, '/index', undefined, {
+ ...indexOpts,
redirect: 'manual',
headers: {
+ ...indexOpts.headers,
'x-matched-path': '/index',
},
})
@@ -474,6 +507,7 @@ describe('required server files', () => {
`/_next/data/${next.buildId}/index.json`,
undefined,
{
+ ...indexOpts,
redirect: 'manual',
}
)
@@ -482,30 +516,6 @@ describe('required server files', () => {
expect(props4.gspCalls).toBe(props3.gspCalls)
})
- it('should cap de-dupe previousCacheItem expires time', async () => {
- const res = await fetchViaHTTP(appPort, '/gsp-long-revalidate', undefined, {
- redirect: 'manual',
- })
- expect(res.status).toBe(200)
- const $ = cheerio.load(await res.text())
- const props = JSON.parse($('#props').text())
- expect(props.gspCalls).toBeDefined()
-
- await waitFor(1000)
-
- const res2 = await fetchViaHTTP(
- appPort,
- `/_next/data/${next.buildId}/gsp-long-revalidate.json`,
- undefined,
- {
- redirect: 'manual',
- }
- )
- expect(res2.status).toBe(200)
- const { pageProps: props2 } = await res2.json()
- expect(props2.gspCalls).not.toBe(props.gspCalls)
- })
-
it('should not 404 for onlyGenerated on-demand revalidate in minimal mode', async () => {
const previewProps = JSON.parse(
await next.readFile('standalone/.next/prerender-manifest.json')
@@ -515,12 +525,12 @@ describe('required server files', () => {
appPort,
'/optional-ssg/only-generated-1',
undefined,
- {
+ withInvocationId({
headers: {
'x-prerender-revalidate': previewProps.previewModeId,
'x-prerender-revalidate-if-generated': '1',
},
- }
+ })
)
expect(res.status).toBe(200)
})
@@ -529,9 +539,14 @@ describe('required server files', () => {
await waitFor(2000)
await next.patchFile('standalone/data.txt', 'show')
- const res = await fetchViaHTTP(appPort, '/gsp', undefined, {
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/gsp',
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res.status).toBe(200)
expect(res.headers.get('cache-control')).toBe(
's-maxage=1, stale-while-revalidate=31535999'
@@ -540,9 +555,14 @@ describe('required server files', () => {
await waitFor(2000)
await next.patchFile('standalone/data.txt', 'hide')
- const res2 = await fetchViaHTTP(appPort, '/gsp', undefined, {
- redirect: 'manual',
- })
+ const res2 = await fetchViaHTTP(
+ appPort,
+ '/gsp',
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res2.status).toBe(404)
expect(res2.headers.get('cache-control')).toBe(
's-maxage=1, stale-while-revalidate=31535999'
@@ -552,9 +572,14 @@ describe('required server files', () => {
it('should set correct SWR headers with notFound gssp', async () => {
await next.patchFile('standalone/data.txt', 'show')
- const res = await fetchViaHTTP(appPort, '/gssp', undefined, {
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/gssp',
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res.status).toBe(200)
expect(res.headers.get('cache-control')).toBe(
's-maxage=1, stale-while-revalidate=31535999'
@@ -562,9 +587,14 @@ describe('required server files', () => {
await next.patchFile('standalone/data.txt', 'hide')
- const res2 = await fetchViaHTTP(appPort, '/gssp', undefined, {
- redirect: 'manual',
- })
+ const res2 = await fetchViaHTTP(
+ appPort,
+ '/gssp',
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
await next.patchFile('standalone/data.txt', 'show')
expect(res2.status).toBe(404)
@@ -574,14 +604,24 @@ describe('required server files', () => {
})
it('should render SSR page correctly', async () => {
- const html = await renderViaHTTP(appPort, '/gssp')
+ const html = await renderViaHTTP(
+ appPort,
+ '/gssp',
+ undefined,
+ withInvocationId()
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
expect($('#gssp').text()).toBe('getServerSideProps page')
expect(data.hello).toBe('world')
- const html2 = await renderViaHTTP(appPort, '/gssp')
+ const html2 = await renderViaHTTP(
+ appPort,
+ '/gssp',
+ undefined,
+ withInvocationId()
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -591,7 +631,12 @@ describe('required server files', () => {
})
it('should render dynamic SSR page correctly', async () => {
- const html = await renderViaHTTP(appPort, '/dynamic/first')
+ const html = await renderViaHTTP(
+ appPort,
+ '/dynamic/first',
+ undefined,
+ withInvocationId()
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -599,7 +644,12 @@ describe('required server files', () => {
expect($('#slug').text()).toBe('first')
expect(data.hello).toBe('world')
- const html2 = await renderViaHTTP(appPort, '/dynamic/second')
+ const html2 = await renderViaHTTP(
+ appPort,
+ '/dynamic/second',
+ undefined,
+ withInvocationId()
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -610,7 +660,12 @@ describe('required server files', () => {
})
it('should render fallback page correctly', async () => {
- const html = await renderViaHTTP(appPort, '/fallback/first')
+ const html = await renderViaHTTP(
+ appPort,
+ '/fallback/first',
+ undefined,
+ withInvocationId()
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -619,7 +674,13 @@ describe('required server files', () => {
expect(data.hello).toBe('world')
await waitFor(2000)
- const html2 = await renderViaHTTP(appPort, '/fallback/first')
+
+ const html2 = await renderViaHTTP(
+ appPort,
+ '/fallback/first',
+ undefined,
+ withInvocationId()
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -628,7 +689,12 @@ describe('required server files', () => {
expect(isNaN(data2.random)).toBe(false)
expect(data2.random).not.toBe(data.random)
- const html3 = await renderViaHTTP(appPort, '/fallback/second')
+ const html3 = await renderViaHTTP(
+ appPort,
+ '/fallback/second',
+ undefined,
+ withInvocationId()
+ )
const $3 = cheerio.load(html3)
const data3 = JSON.parse($3('#props').text())
@@ -639,7 +705,9 @@ describe('required server files', () => {
const { pageProps: data4 } = JSON.parse(
await renderViaHTTP(
appPort,
- `/_next/data/${next.buildId}/fallback/third.json`
+ `/_next/data/${next.buildId}/fallback/third.json`,
+ undefined,
+ withInvocationId()
)
)
expect(data4.hello).toBe('world')
@@ -647,22 +715,32 @@ describe('required server files', () => {
})
it('should render SSR page correctly with x-matched-path', async () => {
- const html = await renderViaHTTP(appPort, '/some-other-path', undefined, {
- headers: {
- 'x-matched-path': '/gssp',
- },
- })
+ const html = await renderViaHTTP(
+ appPort,
+ '/some-other-path',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/gssp',
+ },
+ })
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
expect($('#gssp').text()).toBe('getServerSideProps page')
expect(data.hello).toBe('world')
- const html2 = await renderViaHTTP(appPort, '/some-other-path', undefined, {
- headers: {
- 'x-matched-path': '/gssp',
- },
- })
+ const html2 = await renderViaHTTP(
+ appPort,
+ '/some-other-path',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/gssp',
+ },
+ })
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -676,11 +754,11 @@ describe('required server files', () => {
appPort,
'/some-other-path?nxtPslug=first',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/dynamic/[slug]',
},
- }
+ })
)
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -693,11 +771,11 @@ describe('required server files', () => {
appPort,
'/some-other-path?nxtPslug=second',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/dynamic/[slug]',
},
- }
+ })
)
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -711,11 +789,11 @@ describe('required server files', () => {
appPort,
'/some-other-path?nxtPslug=second',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/dynamic/[slug]',
},
- }
+ })
)
const $3 = cheerio.load(html3)
const data3 = JSON.parse($3('#props').text())
@@ -727,14 +805,19 @@ describe('required server files', () => {
})
it('should render fallback page correctly with x-matched-path and routes-matches', async () => {
- const html = await renderViaHTTP(appPort, '/fallback/first', undefined, {
- headers: {
- 'x-matched-path': '/fallback/first',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'first',
- }).toString(),
- },
- })
+ const html = await renderViaHTTP(
+ appPort,
+ '/fallback/first',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/fallback/first',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'first',
+ }).toString(),
+ },
+ })
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -742,14 +825,19 @@ describe('required server files', () => {
expect($('#slug').text()).toBe('first')
expect(data.hello).toBe('world')
- const html2 = await renderViaHTTP(appPort, `/fallback/[slug]`, undefined, {
- headers: {
- 'x-matched-path': '/fallback/[slug]',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'second',
- }).toString(),
- },
- })
+ const html2 = await renderViaHTTP(
+ appPort,
+ `/fallback/[slug]`,
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/fallback/[slug]',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'second',
+ }).toString(),
+ },
+ })
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -760,14 +848,19 @@ describe('required server files', () => {
})
it('should favor valid route params over routes-matches', async () => {
- const html = await renderViaHTTP(appPort, '/fallback/first', undefined, {
- headers: {
- 'x-matched-path': '/fallback/first',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'fallback/first',
- }).toString(),
- },
- })
+ const html = await renderViaHTTP(
+ appPort,
+ '/fallback/first',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/fallback/first',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'fallback/first',
+ }).toString(),
+ },
+ })
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -775,14 +868,19 @@ describe('required server files', () => {
expect($('#slug').text()).toBe('first')
expect(data.hello).toBe('world')
- const html2 = await renderViaHTTP(appPort, `/fallback/second`, undefined, {
- headers: {
- 'x-matched-path': '/fallback/[slug]',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'fallback/second',
- }).toString(),
- },
- })
+ const html2 = await renderViaHTTP(
+ appPort,
+ `/fallback/second`,
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/fallback/[slug]',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'fallback/second',
+ }).toString(),
+ },
+ })
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -793,24 +891,34 @@ describe('required server files', () => {
})
it('should favor valid route params over routes-matches optional', async () => {
- const html = await renderViaHTTP(appPort, '/optional-ssg', undefined, {
- headers: {
- 'x-matched-path': '/optional-ssg',
- 'x-now-route-matches': '',
- },
- })
+ const html = await renderViaHTTP(
+ appPort,
+ '/optional-ssg',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/optional-ssg',
+ 'x-now-route-matches': '',
+ },
+ })
+ )
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
expect(data.params).toEqual({})
- const html2 = await renderViaHTTP(appPort, `/optional-ssg`, undefined, {
- headers: {
- 'x-matched-path': '/optional-ssg',
- 'x-now-route-matches': createNowRouteMatches({
- slug: 'another',
- }).toString(),
- },
- })
+ const html2 = await renderViaHTTP(
+ appPort,
+ `/optional-ssg`,
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/optional-ssg',
+ 'x-now-route-matches': createNowRouteMatches({
+ slug: 'another',
+ }).toString(),
+ },
+ })
+ )
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -825,11 +933,11 @@ describe('required server files', () => {
slug: 'first',
}).toString()}`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': `/dynamic/[slug]`,
},
- }
+ })
)
const { pageProps: data } = await res.json()
@@ -841,14 +949,14 @@ describe('required server files', () => {
appPort,
`/_next/data/${next.buildId}/fallback/[slug].json`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': `/_next/data/${next.buildId}/fallback/[slug].json`,
'x-now-route-matches': createNowRouteMatches({
slug: 'second',
}).toString(),
},
- }
+ })
)
const { pageProps: data2 } = await res2.json()
@@ -862,12 +970,12 @@ describe('required server files', () => {
appPort,
'/catch-all/[[...rest]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
'x-now-route-matches': '',
},
- }
+ })
)
const $ = cheerio.load(html)
const data = JSON.parse($('#props').text())
@@ -880,14 +988,14 @@ describe('required server files', () => {
appPort,
'/catch-all/[[...rest]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
'x-now-route-matches': createNowRouteMatches({
rest: 'hello',
}).toString(),
},
- }
+ })
)
const $2 = cheerio.load(html2)
const data2 = JSON.parse($2('#props').text())
@@ -901,14 +1009,14 @@ describe('required server files', () => {
appPort,
'/catch-all/[[...rest]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
'x-now-route-matches': createNowRouteMatches({
rest: 'hello/world',
}).toString(),
},
- }
+ })
)
const $3 = cheerio.load(html3)
const data3 = JSON.parse($3('#props').text())
@@ -925,11 +1033,11 @@ describe('required server files', () => {
`/_next/data/${next.buildId}/catch-all.json`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/catch-all/[[...rest]]',
},
- }
+ })
)
const { pageProps: data } = await res.json()
@@ -941,14 +1049,14 @@ describe('required server files', () => {
appPort,
`/_next/data/${next.buildId}/catch-all/[[...rest]].json`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': `/_next/data/${next.buildId}/catch-all/[[...rest]].json`,
'x-now-route-matches': createNowRouteMatches({
rest: 'hello',
}).toString(),
},
- }
+ })
)
const { pageProps: data2 } = await res2.json()
@@ -960,14 +1068,14 @@ describe('required server files', () => {
appPort,
`/_next/data/${next.buildId}/catch-all/[[...rest]].json`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': `/_next/data/${next.buildId}/catch-all/[[...rest]].json`,
'x-now-route-matches': createNowRouteMatches({
rest: 'hello/world',
}).toString(),
},
- }
+ })
)
const { pageProps: data3 } = await res3.json()
@@ -986,9 +1094,14 @@ describe('required server files', () => {
'/fallback/another/',
'/fallback/another',
]) {
- const res = await fetchViaHTTP(appPort, path, undefined, {
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ path,
+ undefined,
+ withInvocationId({
+ redirect: 'manual',
+ })
+ )
expect(res.status).toBe(200)
}
@@ -1001,11 +1114,11 @@ describe('required server files', () => {
{
path: 'hello/world',
},
- {
+ withInvocationId({
headers: {
'x-matched-path': '/gssp',
},
- }
+ })
)
const $ = cheerio.load(html)
expect(JSON.parse($('#router').text()).query).toEqual({
@@ -1020,33 +1133,43 @@ describe('required server files', () => {
{
path: '%c0.%c0.',
},
- {
+ withInvocationId({
headers: {
'x-matched-path': '/dynamic/[slug]',
},
- }
+ })
)
expect(res.status).toBe(400)
expect(await res.text()).toContain('Bad Request')
})
it('should have correct resolvedUrl from rewrite', async () => {
- const res = await fetchViaHTTP(appPort, '/to-dynamic/post-1', undefined, {
- headers: {
- 'x-matched-path': '/dynamic/[slug]',
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/to-dynamic/post-1',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/dynamic/[slug]',
+ },
+ })
+ )
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('#resolved-url').text()).toBe('/dynamic/post-1')
})
it('should have correct resolvedUrl from rewrite with added query', async () => {
- const res = await fetchViaHTTP(appPort, '/to-dynamic/post-2', undefined, {
- headers: {
- 'x-matched-path': '/dynamic/[slug]',
- },
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/to-dynamic/post-2',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/dynamic/[slug]',
+ },
+ })
+ )
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('#resolved-url').text()).toBe('/dynamic/post-2')
@@ -1058,11 +1181,11 @@ describe('required server files', () => {
appPort,
`/_next/data/${next.buildId}/dynamic/post-2.json`,
{ slug: 'post-2' },
- {
+ withInvocationId({
headers: {
'x-matched-path': '/dynamic/[slug]',
},
- }
+ })
)
expect(res.status).toBe(200)
const json = await res.json()
@@ -1070,7 +1193,12 @@ describe('required server files', () => {
})
it('should bubble error correctly for gip page', async () => {
- const res = await fetchViaHTTP(appPort, '/errors/gip', { crash: '1' })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/errors/gip',
+ { crash: '1' },
+ withInvocationId()
+ )
expect(res.status).toBe(500)
expect(await res.text()).toBe('Internal Server Error')
@@ -1080,7 +1208,12 @@ describe('required server files', () => {
})
it('should bubble error correctly for gssp page', async () => {
- const res = await fetchViaHTTP(appPort, '/errors/gssp', { crash: '1' })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/errors/gssp',
+ { crash: '1' },
+ withInvocationId()
+ )
expect(res.status).toBe(500)
expect(await res.text()).toBe('Internal Server Error')
@@ -1090,7 +1223,12 @@ describe('required server files', () => {
})
it('should bubble error correctly for gsp page', async () => {
- const res = await fetchViaHTTP(appPort, '/errors/gsp/crash')
+ const res = await fetchViaHTTP(
+ appPort,
+ '/errors/gsp/crash',
+ undefined,
+ withInvocationId()
+ )
expect(res.status).toBe(500)
expect(await res.text()).toBe('Internal Server Error')
@@ -1100,7 +1238,12 @@ describe('required server files', () => {
})
it('should bubble error correctly for API page', async () => {
- const res = await fetchViaHTTP(appPort, '/api/error')
+ const res = await fetchViaHTTP(
+ appPort,
+ '/api/error',
+ undefined,
+ withInvocationId()
+ )
expect(res.status).toBe(500)
expect(await res.text()).toBe('Internal Server Error')
@@ -1114,11 +1257,11 @@ describe('required server files', () => {
appPort,
'/optional-ssp',
{ nxtPrest: '', another: 'value' },
- {
+ withInvocationId({
headers: {
'x-matched-path': '/optional-ssp/[[...rest]]',
},
- }
+ })
)
const html = await res.text()
@@ -1133,11 +1276,11 @@ describe('required server files', () => {
appPort,
'/optional-ssg',
{ nxtPrest: '', another: 'value' },
- {
+ withInvocationId({
headers: {
'x-matched-path': '/optional-ssg/[[...rest]]',
},
- }
+ })
)
const html = await res.text()
@@ -1187,7 +1330,7 @@ describe('required server files', () => {
headers: {
'x-matched-path': `/_next/data/${next.buildId}/optional-ssg/[[...rest]].json`,
'x-now-route-matches': '',
- 'x-vercel-id': 'cle1::',
+ 'x-invocation-id': 'cle1::',
},
},
{
@@ -1195,7 +1338,7 @@ describe('required server files', () => {
headers: {
'x-matched-path': `/_next/data/${next.buildId}/optional-ssg/[[...rest]].json`,
'x-now-route-matches': '',
- 'x-vercel-id': 'cle1::',
+ 'x-invocation-id': 'cle1::',
},
},
{
@@ -1203,7 +1346,7 @@ describe('required server files', () => {
headers: {
'x-matched-path': `/optional-ssg/[[...rest]]`,
'x-now-route-matches': '',
- 'x-vercel-id': 'cle1::',
+ 'x-invocation-id': 'cle1::',
},
},
]
@@ -1211,6 +1354,7 @@ describe('required server files', () => {
for (const req of reqs) {
console.error('checking', req)
const res = await fetchViaHTTP(appPort, req.path, req.query, {
+ ...withInvocationId(),
headers: req.headers,
})
@@ -1233,12 +1377,12 @@ describe('required server files', () => {
appPort,
'/optional-ssg/[[...rest]]',
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/optional-ssg/[[...rest]]',
'x-now-route-matches': 'nxtPrest=en%2Fes%2Fhello%252Fworld',
},
- }
+ })
)
const html = await res.text()
@@ -1254,11 +1398,11 @@ describe('required server files', () => {
appPort,
'/api/optional',
{ nxtPrest: '', another: 'value' },
- {
+ withInvocationId({
headers: {
'x-matched-path': '/api/optional/[[...rest]]',
},
- }
+ })
)
const json = await res.json()
@@ -1271,11 +1415,11 @@ describe('required server files', () => {
appPort,
'/api/optional/index',
{ nxtPrest: 'index', another: 'value' },
- {
+ withInvocationId({
headers: {
'x-matched-path': '/api/optional/[[...rest]]',
},
- }
+ })
)
const json = await res.json()
@@ -1284,12 +1428,17 @@ describe('required server files', () => {
})
it('should match the index page correctly', async () => {
- const res = await fetchViaHTTP(appPort, '/', undefined, {
- headers: {
- 'x-matched-path': '/index',
- },
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/index',
+ },
+ redirect: 'manual',
+ })
+ )
const html = await res.text()
const $ = cheerio.load(html)
@@ -1297,12 +1446,17 @@ describe('required server files', () => {
})
it('should match the root dynamic page correctly', async () => {
- const res = await fetchViaHTTP(appPort, '/slug-1', undefined, {
- headers: {
- 'x-matched-path': '/[slug]',
- },
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/slug-1',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/[slug]',
+ },
+ redirect: 'manual',
+ })
+ )
const html = await res.text()
const $ = cheerio.load(html)
@@ -1311,12 +1465,17 @@ describe('required server files', () => {
slug: 'slug-1',
})
- const res2 = await fetchViaHTTP(appPort, '/[slug]', undefined, {
- headers: {
- 'x-matched-path': '/[slug]',
- },
- redirect: 'manual',
- })
+ const res2 = await fetchViaHTTP(
+ appPort,
+ '/[slug]',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/[slug]',
+ },
+ redirect: 'manual',
+ })
+ )
const html2 = await res2.text()
const $2 = cheerio.load(html2)
@@ -1327,12 +1486,17 @@ describe('required server files', () => {
})
it('should have correct asPath on dynamic SSG page correctly', async () => {
- const res = await fetchViaHTTP(appPort, '/an-ssg-path', undefined, {
- headers: {
- 'x-matched-path': '/[slug]',
- },
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ '/an-ssg-path',
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': '/[slug]',
+ },
+ redirect: 'manual',
+ })
+ )
const html = await res.text()
const $ = cheerio.load(html)
@@ -1353,12 +1517,17 @@ describe('required server files', () => {
]
for (const check of toCheck) {
console.warn('checking', check)
- const res = await fetchViaHTTP(appPort, check.pathname, undefined, {
- headers: {
- 'x-matched-path': check.matchedPath,
- },
- redirect: 'manual',
- })
+ const res = await fetchViaHTTP(
+ appPort,
+ check.pathname,
+ undefined,
+ withInvocationId({
+ headers: {
+ 'x-matched-path': check.matchedPath,
+ },
+ redirect: 'manual',
+ })
+ )
const html = await res.text()
const $ = cheerio.load(html)
@@ -1370,7 +1539,12 @@ describe('required server files', () => {
})
it('should read .env files and process.env', async () => {
- const res = await fetchViaHTTP(appPort, '/api/env')
+ const res = await fetchViaHTTP(
+ appPort,
+ '/api/env',
+ undefined,
+ withInvocationId()
+ )
const envVariables = await res.json()
@@ -1387,7 +1561,12 @@ describe('required server files', () => {
it('should run middleware correctly', async () => {
const standaloneDir = join(next.testDir, 'standalone')
- const res = await fetchViaHTTP(appPort, '/')
+ const res = await fetchViaHTTP(
+ appPort,
+ '/',
+ undefined,
+ withInvocationId()
+ )
expect(res.status).toBe(200)
expect(await res.text()).toContain('index page')
@@ -1405,7 +1584,9 @@ describe('required server files', () => {
const resImageResponse = await fetchViaHTTP(
appPort,
- '/a-non-existent-page/to-test-with-middleware'
+ '/a-non-existent-page/to-test-with-middleware',
+ undefined,
+ withInvocationId()
)
expect(resImageResponse.status).toBe(200)
@@ -1418,11 +1599,11 @@ describe('required server files', () => {
appPort,
`/_next/data/${nanoid()}/index.json`,
undefined,
- {
+ withInvocationId({
headers: {
'x-matched-path': '/[teamSlug]/[project]/[id]/[suffix]',
},
- }
+ })
)
expect(res.status).toBe(404)
diff --git a/test/production/standalone-mode/response-cache/index.test.ts b/test/production/standalone-mode/response-cache/index.test.ts
index 04111664910aa..d92863c19064a 100644
--- a/test/production/standalone-mode/response-cache/index.test.ts
+++ b/test/production/standalone-mode/response-cache/index.test.ts
@@ -96,7 +96,7 @@ describe('minimal-mode-response-cache', () => {
vary: 'rsc, next-router-state-tree, next-router-prefetch',
'x-now-route-matches': '1=compare&rsc=1',
'x-matched-path': '/app-blog/compare.rsc',
- 'x-vercel-id': '1',
+ 'x-invocation-id': '1',
rsc: '1',
}
const res1 = await fetchViaHTTP(
@@ -126,7 +126,7 @@ describe('minimal-mode-response-cache', () => {
vary: 'rsc, next-router-state-tree, next-router-prefetch',
'x-now-route-matches': '1=app-another&rsc=1',
'x-matched-path': '/app-another.rsc',
- 'x-vercel-id': '1',
+ 'x-invocation-id': '1',
rsc: '1',
}
const res1 = await fetchViaHTTP(appPort, '/app-another.rsc', undefined, {
From d6d573493e575592e76f277a0e239cc8d5f08331 Mon Sep 17 00:00:00 2001
From: Zack Tanner <1939140+ztanner@users.noreply.github.com>
Date: Tue, 27 Jan 2026 12:26:59 -0800
Subject: [PATCH 3/6] tweak LRU sentinel cache key (#89123)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
We originally prefixed the TTL sentinel with the null-byte separator to
avoid collisions with real invocation IDs, but that makes
`extractInvocationID` return "ttl" instead of undefined for TTL-mode
keys.
This switches to a reserved magic string sentinel so TTL-mode entries
are correctly treated as “no invocation ID”.
---
packages/next/src/server/response-cache/index.ts | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/packages/next/src/server/response-cache/index.ts b/packages/next/src/server/response-cache/index.ts
index e40ef2f00596a..4981741e6c31f 100644
--- a/packages/next/src/server/response-cache/index.ts
+++ b/packages/next/src/server/response-cache/index.ts
@@ -63,10 +63,9 @@ const KEY_SEPARATOR = '\0'
/**
* Sentinel value used for TTL-based cache entries (when invocationID is undefined).
- * Uses KEY_SEPARATOR prefix to guarantee uniqueness since null bytes cannot appear
- * in HTTP headers (RFC 7230), making collision with real invocation IDs impossible.
+ * Chosen to be a clearly reserved marker for internal cache keys.
*/
-const TTL_SENTINEL = `${KEY_SEPARATOR}ttl`
+const TTL_SENTINEL = '__ttl_sentinel__'
/**
* Entry stored in the LRU cache.
From a43df3279bdd6f3f69297565bc5c8679ccfe5813 Mon Sep 17 00:00:00 2001
From: Joseph
Date: Tue, 27 Jan 2026 21:47:26 +0100
Subject: [PATCH 4/6] Backport/docs fixes jan 25 16.1.x (#89124)
Backport new guide and glossary update.
---------
Co-authored-by: Delba de Oliveira <32464864+delbaoliveira@users.noreply.github.com>
Co-authored-by: Eng Zer Jun
---
.../01-getting-started/07-fetching-data.mdx | 4 +-
docs/01-app/02-guides/public-static-pages.mdx | 253 ++++++++++++++++++
docs/01-app/{glossary.mdx => 04-glossary.mdx} | 34 ++-
3 files changed, 276 insertions(+), 15 deletions(-)
create mode 100644 docs/01-app/02-guides/public-static-pages.mdx
rename docs/01-app/{glossary.mdx => 04-glossary.mdx} (95%)
diff --git a/docs/01-app/01-getting-started/07-fetching-data.mdx b/docs/01-app/01-getting-started/07-fetching-data.mdx
index df4309a245416..8dfbf61f95879 100644
--- a/docs/01-app/01-getting-started/07-fetching-data.mdx
+++ b/docs/01-app/01-getting-started/07-fetching-data.mdx
@@ -499,7 +499,7 @@ export default async function Page({ params }) {
Start multiple requests by calling `fetch`, then await them with [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all). Requests begin as soon as `fetch` is called.
-```tsx filename="app/artist/[username]/page.tsx" highlight={3,8,23} switcher
+```tsx filename="app/artist/[username]/page.tsx" highlight={3,8,24} switcher
import Albums from './albums'
async function getArtist(username: string) {
@@ -534,7 +534,7 @@ export default async function Page({
}
```
-```jsx filename="app/artist/[username]/page.js" highlight={3,8,19} switcher
+```jsx filename="app/artist/[username]/page.js" highlight={3,8,20} switcher
import Albums from './albums'
async function getArtist(username) {
diff --git a/docs/01-app/02-guides/public-static-pages.mdx b/docs/01-app/02-guides/public-static-pages.mdx
new file mode 100644
index 0000000000000..428c76446d341
--- /dev/null
+++ b/docs/01-app/02-guides/public-static-pages.mdx
@@ -0,0 +1,253 @@
+---
+title: Building public pages
+description: Learn how to build public, "static" pages that share data across users, such as landing pages, list pages (products, blogs, etc.), marketing and news sites.
+nav_title: Public pages
+---
+
+Public pages show the same content to every user. Common examples include landing pages, marketing pages, and product pages.
+
+Since data is shared, these kind of pages can be [prerendered](/docs/app/glossary#prerendering) ahead of time and reused. This leads to faster page loads and lower server costs.
+
+This guide will show you how to build public pages that share data across users.
+
+## Example
+
+As an example, we'll build a product list page.
+
+We'll start with a static header, add a product list with async external data, and learn how to render it without blocking the response. Finally, we'll add a user-specific promotion banner without switching the entire page to [request-time rendering](/docs/app/glossary#request-time-rendering).
+
+You can find the resources used in this example here:
+
+- [Video](https://youtu.be/F6romq71KtI)
+- [Demo](https://cache-components-public-pages.labs.vercel.dev/)
+- [Code](https://github.com/vercel-labs/cache-components-public-pages)
+
+### Step 1: Add a simple header
+
+Let's start with a simple header.
+
+```tsx filename="app/products/page.tsx"
+// Static component
+function Header() {
+ return Shop
+}
+
+export default async function Page() {
+ return (
+ <>
+
+ >
+ )
+}
+```
+
+#### Static components
+
+The `` component doesn't depend on any inputs that change between requests, such as: external data, request headers, route params, the current time, or random values.
+
+Since its output never changes and can be determined ahead of time, this kind of component is called a **static** component. With no reason to wait for a request, Next.js can safely **prerender** the page at [build time](/docs/app/glossary#build-time).
+
+We can confirm this by running [`next build`](/docs/app/api-reference/cli/next#next-build-options).
+
+```bash filename="Terminal"
+Route (app) Revalidate Expire
+┌ ○ /products 15m 1y
+└ ○ /_not-found
+
+○ (Static) prerendered as static content
+```
+
+Notice that the product route is marked as static, even though we didn't add any explicit configuration.
+
+### Step 2: Add the product list
+
+Now, let's fetch and render our product list.
+
+```tsx filename="app/products/page.tsx"
+import db from '@/db'
+import { List } from '@/app/products/ui'
+
+function Header() {}
+
+// Dynamic component
+async function ProductList() {
+ const products = await db.product.findMany()
+ return
+}
+
+export default async function Page() {
+ return (
+ <>
+
+
+ >
+ )
+}
+```
+
+Unlike the header, the product list depends on external data.
+
+#### Dynamic components
+
+Since this data **can** change over time, the rendered output is no longer guaranteed to be stable. This makes the product list a **dynamic** component.
+
+Without guidance, the framework assumes you want to fetch **fresh** data on every user request. This design choice reflects standard web behavior where a new server request renders the page.
+
+However, if this component is rendered at request time, fetching its data will delay the **entire** route from responding. If we refresh the page, we can see this happen.
+
+Even though the header is rendered instantly, it can't be sent to the browser until the product list has finished fetching.
+
+To protect us from this performance cliff, Next.js will show us a [warning](/docs/messages/blocking-route) the first time we **await** data: `Blocking data was accessed outside of Suspense`
+
+At this point, we have to decide how to **unblock** the response. Either:
+
+- [**Cache**](/docs/app/glossary#cache-components) the component, so it becomes **stable** and can be prerendered with the rest of the page.
+- [**Stream**](/docs/app/glossary#streaming) the component, so it becomes **non-blocking** and the rest of the page doesn't have to wait for it.
+
+In our case, the product catalog is shared across all users, so caching is the right choice.
+
+### Cache components
+
+We can mark a function as cacheable using the [`'use cache'`](/docs/app/api-reference/directives/use-cache) directive.
+
+```tsx filename="app/products/page.tsx"
+import db from '@/db'
+import { List } from '@/app/products/ui'
+
+function Header() {}
+
+// Cache component
+async function ProductList() {
+ 'use cache'
+ const products = await db.product.findMany()
+ return
+}
+
+export default async function Page() {
+ return (
+ <>
+
+
+ >
+ )
+}
+```
+
+This turns it into a [cache component](/docs/app/glossary#cache-components). The first time it runs, whatever we return will be cached and reused.
+
+If a cache component's inputs are available **before** the request arrives, it can be prerendered just like a static component.
+
+If we refresh again, we can see the page loads instantly because the cache component doesn't block the response. And, if we run `next build` again, we can confirm the page is still static:
+
+```bash filename="Terminal"
+Route (app) Revalidate Expire
+┌ ○ /products 15m 1y
+└ ○ /_not-found
+
+○ (Static) prerendered as static content
+```
+
+But, pages rarely stay static forever.
+
+### Step 3: Add a dynamic promotion banner
+
+Sooner or later, even simple pages need some dynamic content. To demonstrate this, let's add a promotional banner:
+
+```tsx filename="app/products/page.tsx"
+import db from '@/db'
+import { List, Promotion } from '@/app/products/ui'
+import { getPromotion } from '@/app/products/data'
+
+function Header() {}
+
+async function ProductList() {}
+
+// Dynamic component
+async function PromotionContent() {
+ const promotion = await getPromotion()
+ return
+}
+
+export default async function Page() {
+ return (
+ <>
+
+
+
+ >
+ )
+}
+```
+
+Once again, this starts off as dynamic. And as before, introducing blocking behavior triggers a Next.js warning.
+
+Last time, the data was shared, so it could be cached. This time, the promotion depends on request specific inputs like the user's location and A/B tests, so we can't cache our way out of the blocking behavior.
+
+### Partial prerendering
+
+Adding dynamic content doesn't mean we have to go back to a fully blocking render. We can unblock the response with streaming.
+
+Next.js supports streaming by default. We can use a [Suspense boundary](/docs/app/glossary#suspense-boundary) to tell the framework where to slice the streamed response into _chunks_, and what fallback UI to show while content loads.
+
+```tsx filename="app/products/page.tsx"
+import { Suspense } from 'react'
+import db from '@/db'
+import { List, Promotion, PromotionSkeleton } from '@/app/products/ui'
+import { getPromotion } from '@/app/products/data'
+
+function Header() {}
+
+async function ProductList() {}
+
+// Dynamic component (streamed)
+async function PromotionContent() {
+ const promotion = await getPromotion()
+ return
+}
+
+export default async function Page() {
+ return (
+ <>
+ }>
+
+
+
+
+ >
+ )
+}
+```
+
+The fallback is prerendered alongside the rest of our static and cached content. The inner component streams in later, once its async work completes.
+
+With this change, Next.js can separate prerenderable work from request-time work and the route becomes [partially prerendered](/docs/app/glossary#partial-prerendering-ppr).
+
+Again, we can confirm this by running `next build`:
+
+```bash filename="Terminal"
+Route (app) Revalidate Expire
+┌ ◐ /products 15m 1y
+└ ◐ /_not-found
+
+◐ (Partial Prerender) Prerendered as static HTML with dynamic server-streamed content
+```
+
+At [**build time**](/docs/app/glossary#build-time), most of the page, including the header, product list and promotion fallback, is rendered, cached and pushed to a content delivery network.
+
+At [**request time**](/docs/app/glossary#request-time), the prerendered part is served instantly from a CDN node close to the user.
+
+In parallel, the user specific promotion is rendered on the server, streamed to the client, and swapped into the fallback slot.
+
+If we refresh the page one last time, we can see most of the page loads instantly, while the dynamic parts stream in as they become available.
+
+### Next steps
+
+We've learned how to build mostly static pages that include pockets of dynamic content.
+
+We started with a static page, added async work, and resolved the blocking behavior by caching what could be prerendered, and streaming what couldn't.
+
+In future guides, we'll learn how to:
+
+- Revalidate prerendered pages or cached data.
+- Create variants of the same page with route params.
+- Create private pages with personalized user data.
diff --git a/docs/01-app/glossary.mdx b/docs/01-app/04-glossary.mdx
similarity index 95%
rename from docs/01-app/glossary.mdx
rename to docs/01-app/04-glossary.mdx
index aa47c9d5a6cc0..00c34402f3e4c 100644
--- a/docs/01-app/glossary.mdx
+++ b/docs/01-app/04-glossary.mdx
@@ -44,18 +44,9 @@ The process of dividing your application into smaller JavaScript chunks based on
# D
-## Dynamic APIs
-
-Functions that access request-specific data, causing a component to opt into [dynamic rendering](#dynamic-rendering). These include:
-
-- [`cookies()`](/docs/app/api-reference/functions/cookies) - Access request cookies
-- [`headers()`](/docs/app/api-reference/functions/headers) - Access request headers
-- [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) - Access URL query parameters
-- [`draftMode()`](/docs/app/api-reference/functions/draft-mode) - Enable or check draft mode
-
## Dynamic rendering
-When a component is rendered at [request time](#request-time) rather than [build time](#build-time). A component becomes dynamic when it uses [Dynamic APIs](#dynamic-apis).
+See [Request-time rendering](#request-time-rendering).
## Dynamic route segments
@@ -131,6 +122,10 @@ Information about a page used by browsers and search engines, such as title, des
Caching the return value of a function so that calling the same function multiple times during a render pass (request) only executes it once. In Next.js, fetch requests with the same URL and options are automatically memoized. Learn more about [React Cache](https://react.dev/reference/react/cache).
+## Middleware
+
+See [Proxy](#proxy).
+
# N
## Not Found
@@ -161,7 +156,7 @@ Loading a route in the background before the user navigates to it. Next.js autom
## Prerendering
-Generating HTML for a page ahead of time, either at build time (static rendering) or in the background (revalidation). The pre-rendered HTML is served immediately, then hydrated on the client.
+When a component is rendered at [build time](#build-time) or in the background during [revalidation](#revalidation). The result is HTML and [RSC Payload](#rsc-payload), which can be cached and served from a CDN. Prerendering is the default for components that don't use [Request-time APIs](#request-time-apis).
## Proxy
@@ -177,6 +172,19 @@ Sending users from one URL to another. In Next.js, redirects can be configured i
The time when a user makes a request to your application. At request time, dynamic routes are rendered, cookies and headers are accessible, and request-specific data can be used.
+## Request-time APIs
+
+Functions that access request-specific data, causing a component to opt into [request-time rendering](#request-time-rendering). These include:
+
+- [`cookies()`](/docs/app/api-reference/functions/cookies) - Access request cookies
+- [`headers()`](/docs/app/api-reference/functions/headers) - Access request headers
+- [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) - Access URL query parameters
+- [`draftMode()`](/docs/app/api-reference/functions/draft-mode) - Enable or check draft mode
+
+## Request-time rendering
+
+When a component is rendered at [request time](#request-time) rather than [build time](#build-time). A component becomes dynamic when it uses [Request-time APIs](#request-time-apis).
+
## Revalidation
The process of updating cached data. Can be time-based (using [`cacheLife()`](/docs/app/api-reference/functions/cacheLife) to set cache duration) or on-demand (using [`cacheTag()`](/docs/app/api-reference/functions/cacheTag) to tag data, then [`updateTag()`](/docs/app/api-reference/functions/updateTag) to invalidate). Learn more in [Caching and Revalidating](/docs/app/getting-started/caching-and-revalidating).
@@ -221,7 +229,7 @@ A deployment mode that generates a fully static site with HTML, CSS, and JavaScr
## Static rendering
-When a component is rendered at [build time](#build-time) or in the background during [revalidation](#revalidation). The result is cached and can be served from a CDN. Static rendering is the default for components that don't use [Dynamic APIs](#dynamic-apis).
+See [Prerendering](#prerendering).
## Static Assets
@@ -229,7 +237,7 @@ Files such as images, fonts, videos, and other media that are served directly wi
## Static Shell
-The pre-rendered HTML structure of a page that's served immediately to the browser. With [Partial Prerendering](#partial-prerendering-ppr), the static shell includes all statically renderable content plus [Suspense boundary](#suspense-boundary) fallbacks for dynamic content that streams in later.
+The prerendered HTML structure of a page that's served immediately to the browser. With [Partial Prerendering](#partial-prerendering-ppr), the static shell includes all statically renderable content plus [Suspense boundary](#suspense-boundary) fallbacks for dynamic content that streams in later.
## Streaming
From 098c0c0f2b1fa59b78315cf2e2a98ee1934956b8 Mon Sep 17 00:00:00 2001
From: "Sebastian \"Sebbie\" Silbermann"
Date: Tue, 27 Jan 2026 22:39:33 +0100
Subject: [PATCH 5/6] [backport][ci] Make gh auth status optional when
triggering a release (#89100)
Backports https://github.com/vercel/next.js/pull/89098
Part of Closes https://linear.app/vercel/issue/NAR-753/
---
.github/workflows/trigger_release.yml | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml
index ed34fd9eea9e9..46d56e1336f8c 100644
--- a/.github/workflows/trigger_release.yml
+++ b/.github/workflows/trigger_release.yml
@@ -28,10 +28,6 @@ on:
default: false
type: boolean
- secrets:
- RELEASE_BOT_GITHUB_TOKEN:
- required: true
-
name: Trigger Release
env:
@@ -62,6 +58,9 @@ jobs:
- name: Check token
run: gh auth status
+ # This sometimes fails for unknown reasons.
+ # Ignoring failures for now to check if a failure truly implies a failed publish.
+ continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}
From adf8c612adddd103647c90ff0f511ea35c57076e Mon Sep 17 00:00:00 2001
From: nextjs-bot
Date: Tue, 27 Jan 2026 21:41:14 +0000
Subject: [PATCH 6/6] v16.1.6
---
lerna.json | 2 +-
packages/create-next-app/package.json | 2 +-
packages/eslint-config-next/package.json | 4 ++--
packages/eslint-plugin-internal/package.json | 2 +-
packages/eslint-plugin-next/package.json | 2 +-
packages/font/package.json | 2 +-
packages/next-bundle-analyzer/package.json | 2 +-
packages/next-codemod/package.json | 2 +-
packages/next-env/package.json | 2 +-
packages/next-mdx/package.json | 2 +-
packages/next-plugin-storybook/package.json | 2 +-
packages/next-polyfill-module/package.json | 2 +-
packages/next-polyfill-nomodule/package.json | 2 +-
packages/next-rspack/package.json | 2 +-
packages/next-swc/package.json | 2 +-
packages/next/package.json | 14 +++++++-------
packages/react-refresh-utils/package.json | 2 +-
packages/third-parties/package.json | 4 ++--
pnpm-lock.yaml | 16 ++++++++--------
19 files changed, 34 insertions(+), 34 deletions(-)
diff --git a/lerna.json b/lerna.json
index 353e22eec863e..11d911334616a 100644
--- a/lerna.json
+++ b/lerna.json
@@ -16,5 +16,5 @@
"registry": "https://registry.npmjs.org/"
}
},
- "version": "16.1.5"
+ "version": "16.1.6"
}
\ No newline at end of file
diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json
index fad7b50a5440c..6b5ef15d5930c 100644
--- a/packages/create-next-app/package.json
+++ b/packages/create-next-app/package.json
@@ -1,6 +1,6 @@
{
"name": "create-next-app",
- "version": "16.1.5",
+ "version": "16.1.6",
"keywords": [
"react",
"next",
diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json
index 7affa5a13edd5..ccd80742b78e8 100644
--- a/packages/eslint-config-next/package.json
+++ b/packages/eslint-config-next/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint-config-next",
- "version": "16.1.5",
+ "version": "16.1.6",
"description": "ESLint configuration used by Next.js.",
"license": "MIT",
"repository": {
@@ -12,7 +12,7 @@
"dist"
],
"dependencies": {
- "@next/eslint-plugin-next": "16.1.5",
+ "@next/eslint-plugin-next": "16.1.6",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.32.0",
diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json
index 75a59fc484dde..a56edcfa1c69f 100644
--- a/packages/eslint-plugin-internal/package.json
+++ b/packages/eslint-plugin-internal/package.json
@@ -1,7 +1,7 @@
{
"name": "@next/eslint-plugin-internal",
"private": true,
- "version": "16.1.5",
+ "version": "16.1.6",
"description": "ESLint plugin for working on Next.js.",
"exports": {
".": "./src/eslint-plugin-internal.js"
diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json
index 3adf5e57d50b4..6b51a5c0134a8 100644
--- a/packages/eslint-plugin-next/package.json
+++ b/packages/eslint-plugin-next/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/eslint-plugin-next",
- "version": "16.1.5",
+ "version": "16.1.6",
"description": "ESLint plugin for Next.js.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
diff --git a/packages/font/package.json b/packages/font/package.json
index ac86590f3dca5..be5eaed235fb7 100644
--- a/packages/font/package.json
+++ b/packages/font/package.json
@@ -1,7 +1,7 @@
{
"name": "@next/font",
"private": true,
- "version": "16.1.5",
+ "version": "16.1.6",
"repository": {
"url": "vercel/next.js",
"directory": "packages/font"
diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json
index d7382f6f0532d..a508419109b19 100644
--- a/packages/next-bundle-analyzer/package.json
+++ b/packages/next-bundle-analyzer/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/bundle-analyzer",
- "version": "16.1.5",
+ "version": "16.1.6",
"main": "index.js",
"types": "index.d.ts",
"license": "MIT",
diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json
index 0ee3ef166d8b7..313bf752bc1eb 100644
--- a/packages/next-codemod/package.json
+++ b/packages/next-codemod/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/codemod",
- "version": "16.1.5",
+ "version": "16.1.6",
"license": "MIT",
"repository": {
"type": "git",
diff --git a/packages/next-env/package.json b/packages/next-env/package.json
index 18f9925ccefb3..ab543e3940100 100644
--- a/packages/next-env/package.json
+++ b/packages/next-env/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/env",
- "version": "16.1.5",
+ "version": "16.1.6",
"keywords": [
"react",
"next",
diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json
index cf66bf4b443d2..bc173643c89f1 100644
--- a/packages/next-mdx/package.json
+++ b/packages/next-mdx/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/mdx",
- "version": "16.1.5",
+ "version": "16.1.6",
"main": "index.js",
"license": "MIT",
"repository": {
diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json
index e732b4d2f5f18..b40f392039070 100644
--- a/packages/next-plugin-storybook/package.json
+++ b/packages/next-plugin-storybook/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-storybook",
- "version": "16.1.5",
+ "version": "16.1.6",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-storybook"
diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json
index 6d5095c7cd396..ea9037e8496b6 100644
--- a/packages/next-polyfill-module/package.json
+++ b/packages/next-polyfill-module/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-module",
- "version": "16.1.5",
+ "version": "16.1.6",
"description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)",
"main": "dist/polyfill-module.js",
"license": "MIT",
diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json
index f65386073be8d..beb2e3c288015 100644
--- a/packages/next-polyfill-nomodule/package.json
+++ b/packages/next-polyfill-nomodule/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-nomodule",
- "version": "16.1.5",
+ "version": "16.1.6",
"description": "A polyfill for non-dead, nomodule browsers.",
"main": "dist/polyfill-nomodule.js",
"license": "MIT",
diff --git a/packages/next-rspack/package.json b/packages/next-rspack/package.json
index 85ae9e493ea8c..35a6892571a1a 100644
--- a/packages/next-rspack/package.json
+++ b/packages/next-rspack/package.json
@@ -1,6 +1,6 @@
{
"name": "next-rspack",
- "version": "16.1.5",
+ "version": "16.1.6",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-rspack"
diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json
index a5af56c112c06..ac05b943d8222 100644
--- a/packages/next-swc/package.json
+++ b/packages/next-swc/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/swc",
- "version": "16.1.5",
+ "version": "16.1.6",
"private": true,
"files": [
"native/"
diff --git a/packages/next/package.json b/packages/next/package.json
index cf4c962a2904c..60a787866a9cc 100644
--- a/packages/next/package.json
+++ b/packages/next/package.json
@@ -1,6 +1,6 @@
{
"name": "next",
- "version": "16.1.5",
+ "version": "16.1.6",
"description": "The React Framework",
"main": "./dist/server/next.js",
"license": "MIT",
@@ -97,7 +97,7 @@
]
},
"dependencies": {
- "@next/env": "16.1.5",
+ "@next/env": "16.1.6",
"@swc/helpers": "0.5.15",
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001579",
@@ -162,11 +162,11 @@
"@modelcontextprotocol/sdk": "1.18.1",
"@mswjs/interceptors": "0.23.0",
"@napi-rs/triples": "1.2.0",
- "@next/font": "16.1.5",
- "@next/polyfill-module": "16.1.5",
- "@next/polyfill-nomodule": "16.1.5",
- "@next/react-refresh-utils": "16.1.5",
- "@next/swc": "16.1.5",
+ "@next/font": "16.1.6",
+ "@next/polyfill-module": "16.1.6",
+ "@next/polyfill-nomodule": "16.1.6",
+ "@next/react-refresh-utils": "16.1.6",
+ "@next/swc": "16.1.6",
"@opentelemetry/api": "1.6.0",
"@playwright/test": "1.51.1",
"@rspack/core": "1.6.7",
diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json
index 300f70335499f..4893effacdcff 100644
--- a/packages/react-refresh-utils/package.json
+++ b/packages/react-refresh-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-refresh-utils",
- "version": "16.1.5",
+ "version": "16.1.6",
"description": "An experimental package providing utilities for React Refresh.",
"repository": {
"url": "vercel/next.js",
diff --git a/packages/third-parties/package.json b/packages/third-parties/package.json
index 05e8e25a4b48d..a6765ad41a241 100644
--- a/packages/third-parties/package.json
+++ b/packages/third-parties/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/third-parties",
- "version": "16.1.5",
+ "version": "16.1.6",
"repository": {
"url": "vercel/next.js",
"directory": "packages/third-parties"
@@ -26,7 +26,7 @@
"third-party-capital": "1.0.20"
},
"devDependencies": {
- "next": "16.1.5",
+ "next": "16.1.6",
"outdent": "0.8.0",
"prettier": "2.5.1",
"typescript": "5.9.2"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ce242dbfb049c..ea7a4d24fe464 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1005,7 +1005,7 @@ importers:
packages/eslint-config-next:
dependencies:
'@next/eslint-plugin-next':
- specifier: 16.1.5
+ specifier: 16.1.6
version: link:../eslint-plugin-next
eslint:
specifier: '>=9.0.0'
@@ -1082,7 +1082,7 @@ importers:
packages/next:
dependencies:
'@next/env':
- specifier: 16.1.5
+ specifier: 16.1.6
version: link:../next-env
'@swc/helpers':
specifier: 0.5.15
@@ -1210,19 +1210,19 @@ importers:
specifier: 1.2.0
version: 1.2.0
'@next/font':
- specifier: 16.1.5
+ specifier: 16.1.6
version: link:../font
'@next/polyfill-module':
- specifier: 16.1.5
+ specifier: 16.1.6
version: link:../next-polyfill-module
'@next/polyfill-nomodule':
- specifier: 16.1.5
+ specifier: 16.1.6
version: link:../next-polyfill-nomodule
'@next/react-refresh-utils':
- specifier: 16.1.5
+ specifier: 16.1.6
version: link:../react-refresh-utils
'@next/swc':
- specifier: 16.1.5
+ specifier: 16.1.6
version: link:../next-swc
'@opentelemetry/api':
specifier: 1.6.0
@@ -1925,7 +1925,7 @@ importers:
version: 1.0.20
devDependencies:
next:
- specifier: 16.1.5
+ specifier: 16.1.6
version: link:../next
outdent:
specifier: 0.8.0