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":"AAAA;;;;;CAKC,GAED,oDAAoD,GAEpD,6CAA6C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAU7C,IAAM,qBAAqB,IAAI;AAE/B;;CAEC,GACD,SAAS,QAEP,MAAc,EACd,OAAgB;IAEhB,IAAI,CAAC,CAAC,GAAG;IACT,gFAAgF;IAChF,yEAAyE;IACzE,+CAA+C;IAC/C,8EAA8E;IAC9E,sEAAsE;IACtE,iBAAiB;IACjB,4FAA4F;IAC5F,IAAI,CAAC,CAAC,GAAG;AACX;AACA,IAAM,mBAAmB,QAAQ,SAAS;AA+B1C,IAAM,iBAAiB,OAAO,SAAS,CAAC,cAAc;AACtD,IAAM,cAAc,OAAO,WAAW,eAAe,OAAO,WAAW;AAEvE,SAAS,WACP,GAAQ,EACR,IAAiB,EACjB,OAA2C;IAE3C,IAAI,CAAC,eAAe,IAAI,CAAC,KAAK,OAAO,OAAO,cAAc,CAAC,KAAK,MAAM;AACxE;AAEA,SAAS,qBACP,WAAgC,EAChC,EAAY;IAEZ,IAAI,SAAS,WAAW,CAAC,GAAG;IAC5B,IAAI,CAAC,QAAQ;QACX,0FAA0F;QAC1F,4DAA4D;QAC5D,SAAS,mBAAmB;QAC5B,WAAW,CAAC,GAAG,GAAG;IACpB;IACA,OAAO;AACT;AAEA;;CAEC,GACD,SAAS,mBAAmB,EAAY;IACtC,OAAO;QACL,SAAS,CAAC;QACV,OAAO;QACP,IAAA;QACA,iBAAiB;IACnB;AACF;AAGA,IAAM,mBAAmB;AAUzB;;CAEC,GACD,SAAS,IAAI,OAAgB,EAAE,QAAqB;IAClD,WAAW,SAAS,cAAc;QAAE,OAAO;IAAK;IAChD,IAAI,aAAa,WAAW,SAAS,aAAa;QAAE,OAAO;IAAS;IACpE,IAAI,IAAI;IACR,MAAO,IAAI,SAAS,MAAM,CAAE;QAC1B,IAAM,WAAW,QAAQ,CAAC,IAAI;QAC9B,IAAM,gBAAgB,QAAQ,CAAC,IAAI;QACnC,IAAI,OAAO,kBAAkB,UAAU;YACrC,IAAI,kBAAkB,kBAAkB;gBACtC,WAAW,SAAS,UAAU;oBAC5B,OAAO,QAAQ,CAAC,IAAI;oBACpB,YAAY;oBACZ,UAAU;gBACZ;YACF,OAAO;gBACL,MAAM,IAAI,MAAM,CAAC,gBAAgB,EAAE,eAAe;YACpD;QACF,OAAO;YACL,IAAM,WAAW;YACjB,IAAI,OAAO,QAAQ,CAAC,EAAE,KAAK,YAAY;gBACrC,IAAM,WAAW,QAAQ,CAAC,IAAI;gBAC9B,WAAW,SAAS,UAAU;oBAC5B,KAAK;oBACL,KAAK;oBACL,YAAY;gBACd;YACF,OAAO;gBACL,WAAW,SAAS,UAAU;oBAC5B,KAAK;oBACL,YAAY;gBACd;YACF;QACF;IACF;IACA,OAAO,IAAI,CAAC;AACd;AAEA;;CAEC,GACD,SAAS,UAEP,QAAqB,EACrB,EAAwB;IAExB,IAAI;IACJ,IAAI;IACJ,IAAI,MAAM,MAAM;QACd,SAAS,qBAAqB,IAAI,CAAC,CAAC,EAAE;QACtC,UAAU,OAAO,OAAO;IAC1B,OAAO;QACL,SAAS,IAAI,CAAC,CAAC;QACf,UAAU,IAAI,CAAC,CAAC;IAClB;IACA,OAAO,eAAe,GAAG;IACzB,IAAI,SAAS;AACf;AACA,iBAAiB,CAAC,GAAG;AAGrB,SAAS,qBACP,MAAc,EACd,OAAgB;IAEhB,IAAI,oBACF,mBAAmB,GAAG,CAAC;IAEzB,IAAI,CAAC,mBAAmB;QACtB,mBAAmB,GAAG,CAAC,QAAS,oBAAoB,EAAE;QACtD,OAAO,OAAO,GAAG,OAAO,eAAe,GAAG,IAAI,MAAM,SAAS;YAC3D,KAAA,SAAA,IAAI,MAAM,EAAE,IAAI;gBACd,IACE,eAAe,IAAI,CAAC,QAAQ,SAC5B,SAAS,aACT,SAAS,cACT;oBACA,OAAO,QAAQ,GAAG,CAAC,QAAQ;gBAC7B;oBACK,kCAAA,2BAAA;;oBAAL,QAAK,YAAa,sCAAb,SAAA,6BAAA,QAAA,yBAAA,iCAAiC;wBAAjC,IAAM,MAAN;wBACH,IAAM,QAAQ,QAAQ,GAAG,CAAC,KAAK;wBAC/B,IAAI,UAAU,WAAW,OAAO;oBAClC;;oBAHK;oBAAA;;;6BAAA,6BAAA;4BAAA;;;4BAAA;kCAAA;;;;gBAIL,OAAO;YACT;YACA,SAAA,SAAA,QAAQ,MAAM;gBACZ,IAAM,OAAO,QAAQ,OAAO,CAAC;oBACxB,kCAAA,2BAAA;;oBAAL,QAAK,YAAa,sCAAb,SAAA,6BAAA,QAAA,yBAAA,iCAAiC;wBAAjC,IAAM,MAAN;4BACE,mCAAA,4BAAA;;4BAAL,QAAK,aAAa,QAAQ,OAAO,CAAC,yBAA7B,UAAA,8BAAA,SAAA,0BAAA,kCAAmC;gCAAnC,IAAM,MAAN;gCACH,IAAI,QAAQ,aAAa,CAAC,KAAK,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC;4BAC1D;;4BAFK;4BAAA;;;qCAAA,8BAAA;oCAAA;;;oCAAA;0CAAA;;;;oBAGP;;oBAJK;oBAAA;;;6BAAA,6BAAA;4BAAA;;;4BAAA;kCAAA;;;;gBAKL,OAAO;YACT;QACF;IACF;IACA,OAAO;AACT;AAEA;;CAEC,GACD,SAAS,cAEP,MAA2B,EAC3B,EAAwB;IAExB,IAAI;IACJ,IAAI;IACJ,IAAI,MAAM,MAAM;QACd,SAAS,qBAAqB,IAAI,CAAC,CAAC,EAAE;QACtC,UAAU,OAAO,OAAO;IAC1B,OAAO;QACL,SAAS,IAAI,CAAC,CAAC;QACf,UAAU,IAAI,CAAC,CAAC;IAClB;IACA,IAAM,oBAAoB,qBAAqB,QAAQ;IAEvD,IAAI,CAAA,OAAO,uCAAP,SAAO,OAAK,MAAM,YAAY,WAAW,MAAM;QACjD,kBAAkB,IAAI,CAAC;IACzB;AACF;AACA,iBAAiB,CAAC,GAAG;AAErB,SAAS,YAEP,KAAU,EACV,EAAwB;IAExB,IAAI;IACJ,IAAI,MAAM,MAAM;QACd,SAAS,qBAAqB,IAAI,CAAC,CAAC,EAAE;IACxC,OAAO;QACL,SAAS,IAAI,CAAC,CAAC;IACjB;IACA,OAAO,OAAO,GAAG;AACnB;AACA,iBAAiB,CAAC,GAAG;AAErB,SAAS,gBAEP,SAAc,EACd,EAAwB;IAExB,IAAI;IACJ,IAAI,MAAM,MAAM;QACd,SAAS,qBAAqB,IAAI,CAAC,CAAC,EAAE;IACxC,OAAO;QACL,SAAS,IAAI,CAAC,CAAC;IACjB;IACA,OAAO,OAAO,GAAG,OAAO,eAAe,GAAG;AAC5C;AACA,iBAAiB,CAAC,GAAG;AAErB,SAAS,aAAa,GAAiC,EAAE,GAAoB;IAC3E,OAAO;eAAM,GAAG,CAAC,IAAI;;AACvB;AAEA;;CAEC,GACD,IAAM,WAA8B,OAAO,cAAc,GACrD,SAAC;WAAQ,OAAO,cAAc,CAAC;IAC/B,SAAC;WAAQ,IAAI,SAAS;;AAE1B,iDAAiD,GACjD,IAAM,kBAAkB;IAAC;IAAM,SAAS,CAAC;IAAI,SAAS,EAAE;IAAG,SAAS;CAAU;AAE9E;;;;;;CAMC,GACD,SAAS,WACP,GAAY,EACZ,EAAsB,EACtB,kBAA4B;IAE5B,IAAM,WAAwB,EAAE;IAChC,IAAI,kBAAkB,CAAC;IACvB,IACE,IAAI,UAAU,KACd,CAAC,CAAA,OAAO,wCAAP,SAAO,QAAM,MAAM,YAAY,OAAO,YAAY,UAAU,KAC7D,CAAC,gBAAgB,QAAQ,CAAC,UAC1B,UAAU,SAAS,SACnB;YACK,kCAAA,2BAAA;;YAAL,QAAK,YAAa,OAAO,mBAAmB,CAAC,6BAAxC,SAAA,6BAAA,QAAA,yBAAA,iCAAkD;gBAAlD,IAAM,MAAN;gBACH,SAAS,IAAI,CAAC,KAAK,aAAa,KAAK;gBACrC,IAAI,oBAAoB,CAAC,KAAK,QAAQ,WAAW;oBAC/C,kBAAkB,SAAS,MAAM,GAAG;gBACtC;YACF;;YALK;YAAA;;;qBAAA,6BAAA;oBAAA;;;oBAAA;0BAAA;;;;IAMP;IAEA,6BAA6B;IAC7B,6EAA6E;IAC7E,IAAI,CAAC,CAAC,sBAAsB,mBAAmB,CAAC,GAAG;QACjD,8FAA8F;QAC9F,IAAI,mBAAmB,GAAG;YACxB,oCAAoC;YACpC,SAAS,MAAM,CAAC,iBAAiB,GAAG,kBAAkB;QACxD,OAAO;YACL,SAAS,IAAI,CAAC,WAAW,kBAAkB;QAC7C;IACF;IAEA,IAAI,IAAI;IACR,OAAO;AACT;AAEA,SAAS,SAAS,GAAsB;IACtC,IAAI,OAAO,QAAQ,YAAY;QAC7B,OAAO;YAAqB,IAAA,IAAA,OAAA,UAAA,QAAA,AAAG,OAAH,UAAA,OAAA,OAAA,GAAA,OAAA,MAAA;gBAAG,KAAH,QAAA,SAAA,CAAA,KAAc;;YACxC,OAAO,IAAI,KAAK,CAAC,IAAI,EAAE;QACzB;IACF,OAAO;QACL,OAAO,OAAO,MAAM,CAAC;IACvB;AACF;AAEA,SAAS,UAEP,EAAY;IAEZ,IAAM,SAAS,iCAAiC,IAAI,IAAI,CAAC,CAAC;IAE1D,8DAA8D;IAC9D,IAAI,OAAO,eAAe,EAAE,OAAO,OAAO,eAAe;IAEzD,iGAAiG;IACjG,IAAM,MAAM,OAAO,OAAO;IAC1B,OAAQ,OAAO,eAAe,GAAG,WAC/B,KACA,SAAS,MACT,OAAO,AAAC,IAAY,UAAU;AAElC;AACA,iBAAiB,CAAC,GAAG;AAErB,SAAS,YAEP,QAAkB;IAElB,IAAM,SAAS,IAAI,CAAC,CAAC,CAAC;IAGtB,OAAO,OAAO,UAAU,IAAI,CAAC,IAAI;AACnC;AACA,iBAAiB,CAAC,GAAG;AAErB,+EAA+E;AAC/E,6EAA6E;AAC7E,IAAM,iBACJ,aAAa;AACb,OAAO,YAAY,aAEf,UACA,SAAS;IACP,MAAM,IAAI,MAAM;AAClB;AACN,iBAAiB,CAAC,GAAG;AAErB,SAAS,gBAEP,EAAY;IAEZ,OAAO,iCAAiC,IAAI,IAAI,CAAC,CAAC,EAAE,OAAO;AAC7D;AACA,iBAAiB,CAAC,GAAG;AAErB;;;;;;CAMC,GACD,SAAS,aAAa,OAAe;IACnC,wFAAwF;IACxF,4DAA4D;IAC5D,IAAM,YAAY,QAAQ,OAAO,CAAC;IAClC,IAAI,cAAc,CAAC,GAAG;QACpB,UAAU,QAAQ,SAAS,CAAC,GAAG;IACjC;IAEA,IAAM,aAAa,QAAQ,OAAO,CAAC;IACnC,IAAI,eAAe,CAAC,GAAG;QACrB,UAAU,QAAQ,SAAS,CAAC,GAAG;IACjC;IAEA,OAAO;AACT;AACA;;CAEC,GACD,SAAS,cAAc,GAAqB;IAC1C,SAAS,cAAc,EAAU;QAC/B,KAAK,aAAa;QAClB,IAAI,eAAe,IAAI,CAAC,KAAK,KAAK;YAChC,OAAO,GAAG,CAAC,GAAG,CAAC,MAAM;QACvB;QAEA,IAAM,IAAI,IAAI,MAAM,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;QAC9C,EAAU,IAAI,GAAG;QACnB,MAAM;IACR;IAEA,cAAc,IAAI,GAAG;QACnB,OAAO,OAAO,IAAI,CAAC;IACrB;IAEA,cAAc,OAAO,GAAG,SAAC;QACvB,KAAK,aAAa;QAClB,IAAI,eAAe,IAAI,CAAC,KAAK,KAAK;YAChC,OAAO,GAAG,CAAC,GAAG,CAAC,EAAE;QACnB;QAEA,IAAM,IAAI,IAAI,MAAM,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;QAC9C,EAAU,IAAI,GAAG;QACnB,MAAM;IACR;IAEA,cAAc,MAAM,GAAG,SAAO;;;;;wBACrB;;4BAAO,cAAc;;;wBAA5B;;4BAAO;;;;QACT;;IAEA,OAAO;AACT;AACA,iBAAiB,CAAC,GAAG;AAErB;;CAEC,GACD,SAAS,aAAa,SAAoB;IACxC,OAAO,OAAO,cAAc,WAAW,YAAY,UAAU,IAAI;AACnE;AAEA,SAAS,UAAmB,YAAiB;IAC3C,OACE,gBAAgB,QAChB,CAAA,OAAO,6CAAP,SAAO,aAAW,MAAM,YACxB,UAAU,gBACV,OAAO,aAAa,IAAI,KAAK;AAEjC;AAEA,SAAS,iBAA+B,GAAM;IAC5C,OAAO,mBAAmB;AAC5B;AAEA,SAAS;IACP,IAAI;IACJ,IAAI;IAEJ,IAAM,UAAU,IAAI,QAAW,SAAC,KAAK;QACnC,SAAS;QACT,UAAU;IACZ;IAEA,OAAO;QACL,SAAA;QACA,SAAS;QACT,QAAQ;IACV;AACF;AAEA,gFAAgF;AAChF,0CAA0C;AAC1C,yBAAyB;AACzB,8BAA8B;AAC9B,6EAA6E;AAC7E,wEAAwE;AACxE,SAAS,iCACP,YAAuC,EACvC,MAAc,EACd,eAAgC,EAChC,WAAoC;IAEpC,IAAI,IAAI;IACR,MAAO,IAAI,aAAa,MAAM,CAAE;QAC9B,IAAI,WAAW,YAAY,CAAC,EAAE;QAC9B,IAAI,MAAM,IAAI;QACd,4BAA4B;QAC5B,MACE,MAAM,aAAa,MAAM,IACzB,OAAO,YAAY,CAAC,IAAI,KAAK,WAC7B;YACA;QACF;QACA,IAAI,QAAQ,aAAa,MAAM,EAAE;YAC/B,MAAM,IAAI,MAAM;QAClB;QACA,+FAA+F;QAC/F,sFAAsF;QACtF,IAAI,CAAC,gBAAgB,GAAG,CAAC,WAAW;YAClC,IAAM,kBAAkB,YAAY,CAAC,IAAI;YACzC,uBAAuB;YACvB,wBAAA,kCAAA,YAAc;YACd,MAAO,IAAI,KAAK,IAAK;gBACnB,WAAW,YAAY,CAAC,EAAE;gBAC1B,gBAAgB,GAAG,CAAC,UAAU;YAChC;QACF;QACA,IAAI,MAAM,GAAE,sFAAsF;IACpG;AACF;AAEA,2CAA2C;AAC3C,+HAA+H;AAE/H,IAAM,kBAAkB,OAAO;AAC/B,IAAM,mBAAmB,OAAO;AAChC,IAAM,iBAAiB,OAAO;AAa9B,SAAS,aAAa,KAAkB;IACtC,IAAI,SAAS,MAAM,MAAM,QAA2B;QAClD,MAAM,MAAM;QACZ,MAAM,OAAO,CAAC,SAAC;mBAAO,GAAG,UAAU;;QACnC,MAAM,OAAO,CAAC,SAAC;mBAAQ,GAAG,UAAU,KAAK,GAAG,UAAU,KAAK;;IAC7D;AACF;AAYA,SAAS,SAAS,IAAW;IAC3B,OAAO,KAAK,GAAG,CAAC,SAAC;QACf,IAAI,QAAQ,QAAQ,CAAA,OAAO,oCAAP,SAAO,IAAE,MAAM,UAAU;YAC3C,IAAI,iBAAiB,MAAM,OAAO;YAClC,IAAI,UAAU,MAAM;gBAClB,IAAM,QAAoB,OAAO,MAAM,CAAC,EAAE,EAAE;oBAC1C,MAAM;gBACR;oBAE4B;gBAA5B,IAAM,OAAsB,WAC1B,iBAD0B,MACzB,kBAAmB,CAAC,IACrB,iBAF0B,MAEzB,iBAAkB,SAAC;2BAAoC,GAAG;oBAFjC;gBAK5B,IAAI,IAAI,CACN,SAAC;oBACC,GAAG,CAAC,iBAAiB,GAAG;oBACxB,aAAa;gBACf,GACA,SAAC;oBACC,GAAG,CAAC,eAAe,GAAG;oBACtB,aAAa;gBACf;gBAGF,OAAO;YACT;QACF;YAEO;QAAP,OAAO,YACL,iBADK,OACJ,kBAAmB,MACpB,iBAFK,OAEJ,iBAAkB,YAAO,IAFrB;IAIT;AACF;AAEA,SAAS,YAEP,IAKS,EACT,QAAiB;IAEjB,IAAM,SAAS,IAAI,CAAC,CAAC;IACrB,IAAM,QAAgC,WAClC,OAAO,MAAM,CAAC,EAAE,EAAE;QAAE,MAAM;IAAsB,KAChD;IAEJ,IAAM,YAA6B,IAAI;IAEvC,IAAiD,iBAAA,iBAAzC,UAAyC,eAAzC,SAAS,SAAgC,eAAhC,QAAQ,AAAS,aAAe,eAAxB;QAEqC;IAA9D,IAAM,UAA8B,OAAO,MAAM,CAAC,aAAY,WAC5D,iBAD4D,MAC3D,kBAAmB,OAAO,OAAO,GAClC,iBAF4D,MAE3D,iBAAkB,SAAC;QAClB,SAAS,GAAG;QACZ,UAAU,OAAO,CAAC;QAClB,OAAO,CAAC,QAAQ,CAAC,YAAO;IAC1B,IAN4D;IAS9D,IAAM,aAAiC;QACrC,KAAA,SAAA;YACE,OAAO;QACT;QACA,KAAA,SAAA,IAAI,CAAM;YACR,qCAAqC;YACrC,IAAI,MAAM,SAAS;gBACjB,OAAO,CAAC,iBAAiB,GAAG;YAC9B;QACF;IACF;IAEA,OAAO,cAAc,CAAC,QAAQ,WAAW;IACzC,OAAO,cAAc,CAAC,QAAQ,mBAAmB;IAEjD,SAAS,wBAAwB,IAAW;QAC1C,IAAM,cAAc,SAAS;QAE7B,IAAM,YAAY;mBAChB,YAAY,GAAG,CAAC,SAAC;gBACf,IAAI,CAAC,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,eAAe;gBAC9C,OAAO,CAAC,CAAC,iBAAiB;YAC5B;;QAEF,IAA6B,iBAAA,iBAArB,UAAqB,eAArB,SAAS,UAAY,eAAZ;QAEjB,IAAM,KAAmB,OAAO,MAAM,CAAC;mBAAM,QAAQ;WAAY;YAC/D,YAAY;QACd;QAEA,SAAS,QAAQ,CAAa;YAC5B,IAAI,MAAM,SAAS,CAAC,UAAU,GAAG,CAAC,IAAI;gBACpC,UAAU,GAAG,CAAC;gBACd,IAAI,KAAK,EAAE,MAAM,QAA6B;oBAC5C,GAAG,UAAU;oBACb,EAAE,IAAI,CAAC;gBACT;YACF;QACF;QAEA,YAAY,GAAG,CAAC,SAAC;mBAAQ,GAAG,CAAC,gBAAgB,CAAC;;QAE9C,OAAO,GAAG,UAAU,GAAG,UAAU;IACnC;IAEA,SAAS,YAAY,GAAS;QAC5B,IAAI,KAAK;YACP,OAAQ,OAAO,CAAC,eAAe,GAAG;QACpC,OAAO;YACL,QAAQ,OAAO,CAAC,iBAAiB;QACnC;QAEA,aAAa;IACf;IAEA,KAAK,yBAAyB;IAE9B,IAAI,SAAS,MAAM,MAAM,SAA0B;QACjD,MAAM,MAAM;IACd;AACF;AACA,iBAAiB,CAAC,GAAG;AAErB;;;;;;;;;CASC,GACD,IAAM,cAAc,SAAS,YAAuB,QAAgB;IAClE,IAAM,UAAU,IAAI,IAAI,UAAU;IAClC,IAAM,SAA8B,CAAC;IACrC,IAAK,IAAM,OAAO,QAAS,MAAM,CAAC,IAAI,GAAG,AAAC,OAAe,CAAC,IAAI;IAC9D,OAAO,IAAI,GAAG;IACd,OAAO,QAAQ,GAAG,SAAS,OAAO,CAAC,UAAU;IAC7C,OAAO,MAAM,GAAG,OAAO,QAAQ,GAAG;IAClC,OAAO,QAAQ,GAAG,OAAO,MAAM,GAAG;yCAAI;YAAA;;eAAsB;;IAC5D,IAAK,IAAM,QAAO,OAChB,OAAO,cAAc,CAAC,IAAI,EAAE,MAAK;QAC/B,YAAY;QACZ,cAAc;QACd,OAAO,MAAM,CAAC,KAAI;IACpB;AACJ;AACA,YAAY,SAAS,GAAG,IAAI,SAAS;AACrC,iBAAiB,CAAC,GAAG;AAErB;;CAEC,GACD,SAAS,UAAU,KAAY,EAAE,cAAoC;IACnE,MAAM,IAAI,MAAM,CAAC,WAAW,EAAE,eAAe,QAAQ;AACvD;AAEA;;CAEC,GACD,SAAS,YAAY,SAAmB;IACtC,MAAM,IAAI,MAAM;AAClB;AACA,iBAAiB,CAAC,GAAG;AAErB,kGAAkG;AAClG,iBAAiB,CAAC,GAAG;AAMrB,SAAS,uBAAuB,OAAiB;IAC/C,+DAA+D;IAC/D,OAAO,cAAc,CAAC,SAAS,QAAQ;QACrC,OAAO;IACT;AACF","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