Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

lubieowoce
Copy link
Member

@lubieowoce lubieowoce commented Jun 6, 2025

Implements accessing root params (params segments that occur above root layouts) via compiler-generated getters from the next/root-params module:

// app/[lang]/layout.tsx
// (there is no app/layout.tsx, this is the root layout)

import { lang } from "next/root-params";
//       ^^^^ coresponds to the [lang] segment above this module

export default async function RootLayout({ children }) {
  return (
    <html lang={await lang()}>
      <body>{children}</body>
    </html>
  );
}
  • This API is only usable within server components (support for using it in route handlers will be added in the future). It can be called anywhere in the component tree, not just in a page or a layout.
  • It cannot be used in server actions, because they're not tied to a route.
  • It will replace unstable_rootParams, which will be removed soon.

Note that we can also have multiple root layouts with distinct params, like this:

app/product/[productId]/layout.tsx
app/brand/[brandId]/layout.tsx

In this case we'll generate getters for both productId and brandId. They can be called anywhere, but they'll return undefined if used in a subtree where a param isn't available.

Note that this PR does not yet generate type declarations for the generated getters, essentially leaving them typed as any. This will be handled in a follow up.

Implementation notes

Turbopack

We collect the root param names when analyzing the folder structure in app_structure.rs. The set is then passed down (as a Vc) all the way to next_import_map.rs, where we insert an alias to a virtual next/root-params.js module. We use a ImportMapping::Dynamic to generate its code lazily in a separate turbo task (which is the only place that actually reads the root params). This avoids invalidating the whole ResolveOptionsContext if the root params change.

Webpack

We alias next/root-params to next-root-params-loader, which scans the directory structure manually (i haven't found a good way to re-use existing methods for doing this) and returns the generated module. Each directory we traverse before finding a root layout is marked as a contextDependency, which should invalidate the loader's result if new files are added to any of them or if they get renamed, because it might mean that the set of root params changed.

@ijjk ijjk added created-by: Next.js team PRs by the Next.js team. Rspack tests Turbopack Related to Turbopack with Next.js. type: next labels Jun 6, 2025
@lubieowoce lubieowoce force-pushed the lubieowoce/rootparams-module branch 2 times, most recently from 16510b4 to 6d78322 Compare June 10, 2025 22:36
@ijjk
Copy link
Member

ijjk commented Jun 10, 2025

Tests Passed

@ijjk
Copy link
Member

ijjk commented Jun 10, 2025

Stats from current PR

Default Build (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary vercel/next.js lubieowoce/rootparams-module Change
buildDuration 19.7s 15.3s N/A
buildDurationCached 14.5s 12.9s N/A
nodeModulesSize 443 MB 443 MB ⚠️ +144 kB
nextStartRea..uration (ms) 380ms 381ms N/A
Client Bundles (main, webpack) Overall increase ⚠️
vercel/next.js canary vercel/next.js lubieowoce/rootparams-module Change
194b18f3-HASH.js gzip 54.1 kB 54.1 kB N/A
2192.HASH.js gzip 169 B 169 B
4719-HASH.js gzip 5.3 kB 5.26 kB N/A
6236-HASH.js gzip 44.2 kB 44.7 kB ⚠️ +510 B
framework-HASH.js gzip 57.4 kB 57.4 kB N/A
main-app-HASH.js gzip 254 B 257 B N/A
main-HASH.js gzip 36 kB 36.2 kB ⚠️ +197 B
webpack-HASH.js gzip 1.71 kB 1.71 kB N/A
Overall change 80.3 kB 81 kB ⚠️ +707 B
Legacy Client Bundles (polyfills)
vercel/next.js canary vercel/next.js lubieowoce/rootparams-module Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Overall change 39.4 kB 39.4 kB
Client Pages
vercel/next.js canary vercel/next.js lubieowoce/rootparams-module Change
_app-HASH.js gzip 193 B 193 B
_error-HASH.js gzip 182 B 182 B
amp-HASH.js gzip 501 B 503 B N/A
css-HASH.js gzip 335 B 334 B N/A
dynamic-HASH.js gzip 1.83 kB 1.83 kB N/A
edge-ssr-HASH.js gzip 256 B 255 B N/A
head-HASH.js gzip 350 B 351 B N/A
hooks-HASH.js gzip 382 B 383 B N/A
image-HASH.js gzip 4.68 kB 4.65 kB N/A
index-HASH.js gzip 259 B 258 B N/A
link-HASH.js gzip 2.52 kB 2.51 kB N/A
routerDirect..HASH.js gzip 319 B 319 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 316 B 316 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Overall change 1.5 kB 1.5 kB
Client Build Manifests
vercel/next.js canary vercel/next.js lubieowoce/rootparams-module Change
_buildManifest.js gzip 752 B 753 B N/A
Overall change 0 B 0 B
Rendered Page Sizes
vercel/next.js canary vercel/next.js lubieowoce/rootparams-module Change
index.html gzip 523 B 524 B N/A
link.html gzip 537 B 540 B N/A
withRouter.html gzip 520 B 520 B
Overall change 520 B 520 B
Edge SSR bundle Size
vercel/next.js canary vercel/next.js lubieowoce/rootparams-module Change
edge-ssr.js gzip 121 kB 121 kB N/A
page.js gzip 222 kB 221 kB N/A
Overall change 0 B 0 B
Middleware size Overall increase ⚠️
vercel/next.js canary vercel/next.js lubieowoce/rootparams-module Change
middleware-b..fest.js gzip 678 B 674 B N/A
middleware-r..fest.js gzip 155 B 155 B
middleware.js gzip 32.2 kB 32.4 kB ⚠️ +194 B
edge-runtime..pack.js gzip 853 B 853 B
Overall change 33.2 kB 33.4 kB ⚠️ +194 B
Next Runtimes
vercel/next.js canary vercel/next.js lubieowoce/rootparams-module Change
app-page-exp...dev.js gzip 280 kB 280 kB N/A
app-page-exp..prod.js gzip 154 kB 154 kB
app-page-tur...dev.js gzip 280 kB 280 kB
app-page-tur..prod.js gzip 154 kB 154 kB
app-page-tur...dev.js gzip 268 kB 268 kB
app-page-tur..prod.js gzip 148 kB 148 kB
app-page.run...dev.js gzip 268 kB 268 kB
app-page.run..prod.js gzip 148 kB 148 kB
app-route-ex...dev.js gzip 69.1 kB 69.1 kB
app-route-ex..prod.js gzip 48.6 kB 48.6 kB
app-route-tu...dev.js gzip 69.1 kB 69.1 kB
app-route-tu..prod.js gzip 48.6 kB 48.6 kB
app-route-tu...dev.js gzip 68.5 kB 68.5 kB
app-route-tu..prod.js gzip 48.2 kB 48.2 kB
app-route.ru...dev.js gzip 68.4 kB 68.4 kB
app-route.ru..prod.js gzip 48.2 kB 48.2 kB
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 328 B 328 B
dist_client_...dev.js gzip 320 B 320 B
dist_client_...dev.js gzip 318 B 318 B
pages-api-tu...dev.js gzip 42.3 kB 42.3 kB
pages-api-tu..prod.js gzip 32.5 kB 32.5 kB
pages-api.ru...dev.js gzip 42.2 kB 42.2 kB
pages-api.ru..prod.js gzip 32.5 kB 32.5 kB
pages-turbo....dev.js gzip 52.3 kB 52.3 kB
pages-turbo...prod.js gzip 39.9 kB 39.9 kB
pages.runtim...dev.js gzip 52.4 kB 52.4 kB
pages.runtim..prod.js gzip 40 kB 40 kB
server.runti..prod.js gzip 59.5 kB 59.5 kB
Overall change 2.28 MB 2.28 MB
build cache Overall increase ⚠️
vercel/next.js canary vercel/next.js lubieowoce/rootparams-module Change
0.pack gzip 2.8 MB 2.81 MB ⚠️ +6.04 kB
index.pack gzip 91.8 kB 91.7 kB N/A
Overall change 2.8 MB 2.81 MB ⚠️ +6.04 kB
Diff details
Diff for page.js

Diff too large to display

Diff for middleware.js

Diff too large to display

Diff for edge-ssr.js

Diff too large to display

Diff for amp-HASH.js
@@ -1,56 +1,34 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [5034],
   {
-    /***/ 3960: /***/ (
+    /***/ 3870: /***/ (
       module,
       __unused_webpack_exports,
       __webpack_require__
     ) => {
-      module.exports = __webpack_require__(5313);
+      module.exports = __webpack_require__(9298);
 
       /***/
     },
 
-    /***/ 5313: /***/ (module, exports, __webpack_require__) => {
-      "use strict";
-
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "useAmp", {
-        enumerable: true,
-        get: function () {
-          return useAmp;
+    /***/ 8318: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/amp",
+        function () {
+          return __webpack_require__(8921);
         },
-      });
-      const _interop_require_default = __webpack_require__(1532);
-      const _react = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(148)
-      );
-      const _ampcontextsharedruntime = __webpack_require__(919);
-      const _ampmode = __webpack_require__(1615);
-      function useAmp() {
-        // Don't assign the context value to a variable to save bytes
-        return (0, _ampmode.isInAmpMode)(
-          _react.default.useContext(_ampcontextsharedruntime.AmpStateContext)
-        );
+      ]);
+      if (false) {
       }
-      if (
-        (typeof exports.default === "function" ||
-          (typeof exports.default === "object" && exports.default !== null)) &&
-        typeof exports.default.__esModule === "undefined"
-      ) {
-        Object.defineProperty(exports.default, "__esModule", {
-          value: true,
-        });
-        Object.assign(exports.default, exports);
-        module.exports = exports.default;
-      } //# sourceMappingURL=amp.js.map
 
       /***/
     },
 
-    /***/ 6756: /***/ (
+    /***/ 8921: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -63,7 +41,7 @@
         /* harmony export */
       });
       /* harmony import */ var next_amp__WEBPACK_IMPORTED_MODULE_0__ =
-        __webpack_require__(3960);
+        __webpack_require__(3870);
       /* harmony import */ var next_amp__WEBPACK_IMPORTED_MODULE_0___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_amp__WEBPACK_IMPORTED_MODULE_0__
@@ -81,19 +59,41 @@
       /***/
     },
 
-    /***/ 7252: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/amp",
-        function () {
-          return __webpack_require__(6756);
+    /***/ 9298: /***/ (module, exports, __webpack_require__) => {
+      "use strict";
+
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "useAmp", {
+        enumerable: true,
+        get: function () {
+          return useAmp;
         },
-      ]);
-      if (false) {
+      });
+      const _interop_require_default = __webpack_require__(1532);
+      const _react = /*#__PURE__*/ _interop_require_default._(
+        __webpack_require__(148)
+      );
+      const _ampcontextsharedruntime = __webpack_require__(326);
+      const _ampmode = __webpack_require__(9474);
+      function useAmp() {
+        // Don't assign the context value to a variable to save bytes
+        return (0, _ampmode.isInAmpMode)(
+          _react.default.useContext(_ampcontextsharedruntime.AmpStateContext)
+        );
       }
+      if (
+        (typeof exports.default === "function" ||
+          (typeof exports.default === "object" && exports.default !== null)) &&
+        typeof exports.default.__esModule === "undefined"
+      ) {
+        Object.defineProperty(exports.default, "__esModule", {
+          value: true,
+        });
+        Object.assign(exports.default, exports);
+        module.exports = exports.default;
+      } //# sourceMappingURL=amp.js.map
 
       /***/
     },
@@ -103,7 +103,7 @@
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(7252)
+      __webpack_exec__(8318)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for css-HASH.js
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [9813],
   {
-    /***/ 1586: /***/ (
+    /***/ 2628: /***/ (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/css",
         function () {
-          return __webpack_require__(4362);
+          return __webpack_require__(5892);
         },
       ]);
       if (false) {
@@ -18,14 +18,7 @@
       /***/
     },
 
-    /***/ 4350: /***/ (module) => {
-      // extracted by mini-css-extract-plugin
-      module.exports = { helloWorld: "css_helloWorld__aUdUq" };
-
-      /***/
-    },
-
-    /***/ 4362: /***/ (
+    /***/ 5892: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -39,7 +32,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(5640);
       /* harmony import */ var _css_module_css__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(4350);
+        __webpack_require__(8937);
       /* harmony import */ var _css_module_css__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           _css_module_css__WEBPACK_IMPORTED_MODULE_1__
@@ -58,13 +51,20 @@
 
       /***/
     },
+
+    /***/ 8937: /***/ (module) => {
+      // extracted by mini-css-extract-plugin
+      module.exports = { helloWorld: "css_helloWorld__aUdUq" };
+
+      /***/
+    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(1586)
+      __webpack_exec__(2628)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for dynamic-HASH.js
@@ -1,17 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [2291],
   {
-    /***/ 283: /***/ (
-      module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      module.exports = __webpack_require__(6990);
-
-      /***/
-    },
-
-    /***/ 505: /***/ (
+    /***/ 290: /***/ (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -53,7 +43,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       const _react = /*#__PURE__*/ _interop_require_default._(
         __webpack_require__(148)
       );
-      const _loadablecontextsharedruntime = __webpack_require__(6179);
+      const _loadablecontextsharedruntime = __webpack_require__(8452);
       function resolve(obj) {
         return obj && obj.default ? obj.default : obj;
       }
@@ -288,7 +278,17 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       /***/
     },
 
-    /***/ 5703: /***/ (
+    /***/ 1934: /***/ (
+      module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      module.exports = __webpack_require__(9141);
+
+      /***/
+    },
+
+    /***/ 2598: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -303,7 +303,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(5640);
       /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(283);
+        __webpack_require__(1934);
       /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_dynamic__WEBPACK_IMPORTED_MODULE_1__
@@ -312,12 +312,12 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       const DynamicHello = next_dynamic__WEBPACK_IMPORTED_MODULE_1___default()(
         () =>
           __webpack_require__
-            .e(/* import() */ 2192)
-            .then(__webpack_require__.bind(__webpack_require__, 2192))
+            .e(/* import() */ 8751)
+            .then(__webpack_require__.bind(__webpack_require__, 8751))
             .then((mod) => mod.Hello),
         {
           loadableGenerated: {
-            webpack: () => [/*require.resolve*/ 2192],
+            webpack: () => [/*require.resolve*/ 8751],
           },
         }
       );
@@ -344,7 +344,24 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       /***/
     },
 
-    /***/ 6179: /***/ (
+    /***/ 2976: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/dynamic",
+        function () {
+          return __webpack_require__(2598);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
+
+    /***/ 8452: /***/ (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -371,7 +388,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       /***/
     },
 
-    /***/ 6990: /***/ (module, exports, __webpack_require__) => {
+    /***/ 9141: /***/ (module, exports, __webpack_require__) => {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -404,7 +421,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
         __webpack_require__(148)
       );
       const _loadablesharedruntime = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(505)
+        __webpack_require__(290)
       );
       const isServerSide = "object" === "undefined";
       // Normalize loader to return the module as form { default: Component } for `React.lazy`.
@@ -504,30 +521,13 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
 
       /***/
     },
-
-    /***/ 9254: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/dynamic",
-        function () {
-          return __webpack_require__(5703);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(9254)
+      __webpack_exec__(2976)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for hooks-HASH.js
@@ -1,24 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [9804],
   {
-    /***/ 1664: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/hooks",
-        function () {
-          return __webpack_require__(6130);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
-
-    /***/ 6130: /***/ (
+    /***/ 3251: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -76,13 +59,30 @@
 
       /***/
     },
+
+    /***/ 5426: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/hooks",
+        function () {
+          return __webpack_require__(3251);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(1664)
+      __webpack_exec__(5426)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for image-HASH.js

Diff too large to display

Diff for link-HASH.js
@@ -1,108 +1,116 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [4672],
   {
-    /***/ 1854: /***/ (
-      __unused_webpack_module,
-      __webpack_exports__,
-      __webpack_require__
-    ) => {
-      "use strict";
-      __webpack_require__.r(__webpack_exports__);
-      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
-        /* harmony export */ __N_SSP: () => /* binding */ __N_SSP,
-        /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
-        /* harmony export */
-      });
-      /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
-        __webpack_require__(5640);
-      /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(8770);
-      /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1___default =
-        /*#__PURE__*/ __webpack_require__.n(
-          next_link__WEBPACK_IMPORTED_MODULE_1__
-        );
-
-      function aLink(props) {
-        return /*#__PURE__*/ (0,
-        react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
-          children: [
-            /*#__PURE__*/ (0,
-            react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h3", {
-              children: "A Link page!",
-            }),
-            /*#__PURE__*/ (0,
-            react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(
-              next_link__WEBPACK_IMPORTED_MODULE_1___default(),
-              {
-                href: "/",
-                children: "Go to /",
-              }
-            ),
-          ],
-        });
-      }
-      var __N_SSP = true;
-      /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = aLink;
-
-      /***/
-    },
-
-    /***/ 3199: /***/ (__unused_webpack_module, exports) => {
-      "use strict";
-
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "errorOnce", {
-        enumerable: true,
-        get: function () {
-          return errorOnce;
-        },
-      });
-      let errorOnce = (_) => {};
-      if (false) {
-      } //# sourceMappingURL=error-once.js.map
-
-      /***/
-    },
-
-    /***/ 3568: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/link",
-        function () {
-          return __webpack_require__(1854);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
-
-    /***/ 3857: /***/ (module, exports, __webpack_require__) => {
+    /***/ 606: /***/ (module, exports, __webpack_require__) => {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
         value: true,
       });
-      Object.defineProperty(exports, "getDomainLocale", {
+      Object.defineProperty(exports, "useIntersection", {
         enumerable: true,
         get: function () {
-          return getDomainLocale;
+          return useIntersection;
         },
       });
-      const _normalizetrailingslash = __webpack_require__(4869);
-      const basePath =
-        /* unused pure expression or super */ null && (false || "");
-      function getDomainLocale(path, locale, locales, domainLocales) {
-        if (false) {
-        } else {
-          return false;
+      const _react = __webpack_require__(148);
+      const _requestidlecallback = __webpack_require__(9940);
+      const hasIntersectionObserver =
+        typeof IntersectionObserver === "function";
+      const observers = new Map();
+      const idList = [];
+      function createObserver(options) {
+        const id = {
+          root: options.root || null,
+          margin: options.rootMargin || "",
+        };
+        const existing = idList.find(
+          (obj) => obj.root === id.root && obj.margin === id.margin
+        );
+        let instance;
+        if (existing) {
+          instance = observers.get(existing);
+          if (instance) {
+            return instance;
+          }
         }
+        const elements = new Map();
+        const observer = new IntersectionObserver((entries) => {
+          entries.forEach((entry) => {
+            const callback = elements.get(entry.target);
+            const isVisible =
+              entry.isIntersecting || entry.intersectionRatio > 0;
+            if (callback && isVisible) {
+              callback(isVisible);
+            }
+          });
+        }, options);
+        instance = {
+          id,
+          observer,
+          elements,
+        };
+        idList.push(id);
+        observers.set(id, instance);
+        return instance;
+      }
+      function observe(element, callback, options) {
+        const { id, observer, elements } = createObserver(options);
+        elements.set(element, callback);
+        observer.observe(element);
+        return function unobserve() {
+          elements.delete(element);
+          observer.unobserve(element);
+          // Destroy observer when there's nothing left to watch:
+          if (elements.size === 0) {
+            observer.disconnect();
+            observers.delete(id);
+            const index = idList.findIndex(
+              (obj) => obj.root === id.root && obj.margin === id.margin
+            );
+            if (index > -1) {
+              idList.splice(index, 1);
+            }
+          }
+        };
+      }
+      function useIntersection(param) {
+        let { rootRef, rootMargin, disabled } = param;
+        const isDisabled = disabled || !hasIntersectionObserver;
+        const [visible, setVisible] = (0, _react.useState)(false);
+        const elementRef = (0, _react.useRef)(null);
+        const setElement = (0, _react.useCallback)((element) => {
+          elementRef.current = element;
+        }, []);
+        (0, _react.useEffect)(() => {
+          if (hasIntersectionObserver) {
+            if (isDisabled || visible) return;
+            const element = elementRef.current;
+            if (element && element.tagName) {
+              const unobserve = observe(
+                element,
+                (isVisible) => isVisible && setVisible(isVisible),
+                {
+                  root: rootRef == null ? void 0 : rootRef.current,
+                  rootMargin,
+                }
+              );
+              return unobserve;
+            }
+          } else {
+            if (!visible) {
+              const idleCallback = (0,
+              _requestidlecallback.requestIdleCallback)(() => setVisible(true));
+              return () =>
+                (0, _requestidlecallback.cancelIdleCallback)(idleCallback);
+            }
+          }
+          // eslint-disable-next-line react-hooks/exhaustive-deps
+        }, [isDisabled, rootMargin, rootRef, visible, elementRef.current]);
+        const resetVisible = (0, _react.useCallback)(() => {
+          setVisible(false);
+        }, []);
+        return [setElement, visible, resetVisible];
       }
       if (
         (typeof exports.default === "function" ||
@@ -114,12 +122,22 @@
         });
         Object.assign(exports.default, exports);
         module.exports = exports.default;
-      } //# sourceMappingURL=get-domain-locale.js.map
+      } //# sourceMappingURL=use-intersection.js.map
 
       /***/
     },
 
-    /***/ 3947: /***/ (module, exports, __webpack_require__) => {
+    /***/ 1148: /***/ (
+      module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      module.exports = __webpack_require__(2994);
+
+      /***/
+    },
+
+    /***/ 2994: /***/ (module, exports, __webpack_require__) => {
       "use strict";
       /* __next_internal_client_entry_do_not_use__  cjs */
       Object.defineProperty(exports, "__esModule", {
@@ -146,17 +164,17 @@
       const _react = /*#__PURE__*/ _interop_require_wildcard._(
         __webpack_require__(148)
       );
-      const _resolvehref = __webpack_require__(3161);
-      const _islocalurl = __webpack_require__(2309);
-      const _formaturl = __webpack_require__(3768);
-      const _utils = __webpack_require__(5554);
-      const _addlocale = __webpack_require__(7591);
-      const _routercontextsharedruntime = __webpack_require__(3556);
-      const _useintersection = __webpack_require__(5624);
-      const _getdomainlocale = __webpack_require__(3857);
-      const _addbasepath = __webpack_require__(4356);
-      const _usemergedref = __webpack_require__(4985);
-      const _erroronce = __webpack_require__(3199);
+      const _resolvehref = __webpack_require__(8624);
+      const _islocalurl = __webpack_require__(6722);
+      const _formaturl = __webpack_require__(4411);
+      const _utils = __webpack_require__(1061);
+      const _addlocale = __webpack_require__(2006);
+      const _routercontextsharedruntime = __webpack_require__(2479);
+      const _useintersection = __webpack_require__(606);
+      const _getdomainlocale = __webpack_require__(4194);
+      const _addbasepath = __webpack_require__(2135);
+      const _usemergedref = __webpack_require__(6156);
+      const _erroronce = __webpack_require__(5174);
       const prefetched = new Set();
       function prefetch(router, href, as, options) {
         if (false) {
@@ -545,7 +563,108 @@
       /***/
     },
 
-    /***/ 4985: /***/ (module, exports, __webpack_require__) => {
+    /***/ 3645: /***/ (
+      __unused_webpack_module,
+      __webpack_exports__,
+      __webpack_require__
+    ) => {
+      "use strict";
+      __webpack_require__.r(__webpack_exports__);
+      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
+        /* harmony export */ __N_SSP: () => /* binding */ __N_SSP,
+        /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
+        /* harmony export */
+      });
+      /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
+        __webpack_require__(5640);
+      /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1__ =
+        __webpack_require__(1148);
+      /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1___default =
+        /*#__PURE__*/ __webpack_require__.n(
+          next_link__WEBPACK_IMPORTED_MODULE_1__
+        );
+
+      function aLink(props) {
+        return /*#__PURE__*/ (0,
+        react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
+          children: [
+            /*#__PURE__*/ (0,
+            react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h3", {
+              children: "A Link page!",
+            }),
+            /*#__PURE__*/ (0,
+            react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(
+              next_link__WEBPACK_IMPORTED_MODULE_1___default(),
+              {
+                href: "/",
+                children: "Go to /",
+              }
+            ),
+          ],
+        });
+      }
+      var __N_SSP = true;
+      /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = aLink;
+
+      /***/
+    },
+
+    /***/ 4194: /***/ (module, exports, __webpack_require__) => {
+      "use strict";
+
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "getDomainLocale", {
+        enumerable: true,
+        get: function () {
+          return getDomainLocale;
+        },
+      });
+      const _normalizetrailingslash = __webpack_require__(6036);
+      const basePath =
+        /* unused pure expression or super */ null && (false || "");
+      function getDomainLocale(path, locale, locales, domainLocales) {
+        if (false) {
+        } else {
+          return false;
+        }
+      }
+      if (
+        (typeof exports.default === "function" ||
+          (typeof exports.default === "object" && exports.default !== null)) &&
+        typeof exports.default.__esModule === "undefined"
+      ) {
+        Object.defineProperty(exports.default, "__esModule", {
+          value: true,
+        });
+        Object.assign(exports.default, exports);
+        module.exports = exports.default;
+      } //# sourceMappingURL=get-domain-locale.js.map
+
+      /***/
+    },
+
+    /***/ 5174: /***/ (__unused_webpack_module, exports) => {
+      "use strict";
+
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "errorOnce", {
+        enumerable: true,
+        get: function () {
+          return errorOnce;
+        },
+      });
+      let errorOnce = (_) => {};
+      if (false) {
+      } //# sourceMappingURL=error-once.js.map
+
+      /***/
+    },
+
+    /***/ 6156: /***/ (module, exports, __webpack_require__) => {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -623,138 +742,19 @@
       /***/
     },
 
-    /***/ 5624: /***/ (module, exports, __webpack_require__) => {
-      "use strict";
-
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "useIntersection", {
-        enumerable: true,
-        get: function () {
-          return useIntersection;
-        },
-      });
-      const _react = __webpack_require__(148);
-      const _requestidlecallback = __webpack_require__(3543);
-      const hasIntersectionObserver =
-        typeof IntersectionObserver === "function";
-      const observers = new Map();
-      const idList = [];
-      function createObserver(options) {
-        const id = {
-          root: options.root || null,
-          margin: options.rootMargin || "",
-        };
-        const existing = idList.find(
-          (obj) => obj.root === id.root && obj.margin === id.margin
-        );
-        let instance;
-        if (existing) {
-          instance = observers.get(existing);
-          if (instance) {
-            return instance;
-          }
-        }
-        const elements = new Map();
-        const observer = new IntersectionObserver((entries) => {
-          entries.forEach((entry) => {
-            const callback = elements.get(entry.target);
-            const isVisible =
-              entry.isIntersecting || entry.intersectionRatio > 0;
-            if (callback && isVisible) {
-              callback(isVisible);
-            }
-          });
-        }, options);
-        instance = {
-          id,
-          observer,
-          elements,
-        };
-        idList.push(id);
-        observers.set(id, instance);
-        return instance;
-      }
-      function observe(element, callback, options) {
-        const { id, observer, elements } = createObserver(options);
-        elements.set(element, callback);
-        observer.observe(element);
-        return function unobserve() {
-          elements.delete(element);
-          observer.unobserve(element);
-          // Destroy observer when there's nothing left to watch:
-          if (elements.size === 0) {
-            observer.disconnect();
-            observers.delete(id);
-            const index = idList.findIndex(
-              (obj) => obj.root === id.root && obj.margin === id.margin
-            );
-            if (index > -1) {
-              idList.splice(index, 1);
-            }
-          }
-        };
-      }
-      function useIntersection(param) {
-        let { rootRef, rootMargin, disabled } = param;
-        const isDisabled = disabled || !hasIntersectionObserver;
-        const [visible, setVisible] = (0, _react.useState)(false);
-        const elementRef = (0, _react.useRef)(null);
-        const setElement = (0, _react.useCallback)((element) => {
-          elementRef.current = element;
-        }, []);
-        (0, _react.useEffect)(() => {
-          if (hasIntersectionObserver) {
-            if (isDisabled || visible) return;
-            const element = elementRef.current;
-            if (element && element.tagName) {
-              const unobserve = observe(
-                element,
-                (isVisible) => isVisible && setVisible(isVisible),
-                {
-                  root: rootRef == null ? void 0 : rootRef.current,
-                  rootMargin,
-                }
-              );
-              return unobserve;
-            }
-          } else {
-            if (!visible) {
-              const idleCallback = (0,
-              _requestidlecallback.requestIdleCallback)(() => setVisible(true));
-              return () =>
-                (0, _requestidlecallback.cancelIdleCallback)(idleCallback);
-            }
-          }
-          // eslint-disable-next-line react-hooks/exhaustive-deps
-        }, [isDisabled, rootMargin, rootRef, visible, elementRef.current]);
-        const resetVisible = (0, _react.useCallback)(() => {
-          setVisible(false);
-        }, []);
-        return [setElement, visible, resetVisible];
-      }
-      if (
-        (typeof exports.default === "function" ||
-          (typeof exports.default === "object" && exports.default !== null)) &&
-        typeof exports.default.__esModule === "undefined"
-      ) {
-        Object.defineProperty(exports.default, "__esModule", {
-          value: true,
-        });
-        Object.assign(exports.default, exports);
-        module.exports = exports.default;
-      } //# sourceMappingURL=use-intersection.js.map
-
-      /***/
-    },
-
-    /***/ 8770: /***/ (
-      module,
+    /***/ 9666: /***/ (
+      __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
     ) => {
-      module.exports = __webpack_require__(3947);
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/link",
+        function () {
+          return __webpack_require__(3645);
+        },
+      ]);
+      if (false) {
+      }
 
       /***/
     },
@@ -764,7 +764,7 @@
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(3568)
+      __webpack_exec__(9666)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for routerDirect-HASH.js
@@ -1,7 +1,24 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [188],
   {
-    /***/ 3618: /***/ (
+    /***/ 1810: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/routerDirect",
+        function () {
+          return __webpack_require__(7989);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
+
+    /***/ 7989: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -16,7 +33,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(5640);
       /* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(4631);
+        __webpack_require__(9413);
       /* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_router__WEBPACK_IMPORTED_MODULE_1__
@@ -36,29 +53,12 @@
       /***/
     },
 
-    /***/ 4631: /***/ (
+    /***/ 9413: /***/ (
       module,
       __unused_webpack_exports,
       __webpack_require__
     ) => {
-      module.exports = __webpack_require__(7086);
-
-      /***/
-    },
-
-    /***/ 7824: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/routerDirect",
-        function () {
-          return __webpack_require__(3618);
-        },
-      ]);
-      if (false) {
-      }
+      module.exports = __webpack_require__(9751);
 
       /***/
     },
@@ -68,7 +68,7 @@
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(7824)
+      __webpack_exec__(1810)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for script-HASH.js
@@ -1,7 +1,17 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [1209],
   {
-    /***/ 1984: /***/ (
+    /***/ 2227: /***/ (
+      module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      module.exports = __webpack_require__(9765);
+
+      /***/
+    },
+
+    /***/ 3642: /***/ (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +19,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/script",
         function () {
-          return __webpack_require__(5769);
+          return __webpack_require__(8034);
         },
       ]);
       if (false) {
@@ -18,7 +28,7 @@
       /***/
     },
 
-    /***/ 5769: /***/ (
+    /***/ 8034: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -33,7 +43,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(5640);
       /* harmony import */ var next_script__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(8293);
+        __webpack_require__(2227);
       /* harmony import */ var next_script__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_script__WEBPACK_IMPORTED_MODULE_1__
@@ -65,23 +75,13 @@
 
       /***/
     },
-
-    /***/ 8293: /***/ (
-      module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      module.exports = __webpack_require__(900);
-
-      /***/
-    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(1984)
+      __webpack_exec__(3642)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for withRouter-HASH.js
@@ -1,34 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [3263],
   {
-    /***/ 4631: /***/ (
-      module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      module.exports = __webpack_require__(7086);
-
-      /***/
-    },
-
-    /***/ 9216: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/withRouter",
-        function () {
-          return __webpack_require__(9803);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
-
-    /***/ 9803: /***/ (
+    /***/ 2148: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -43,7 +16,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(5640);
       /* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(4631);
+        __webpack_require__(9413);
       /* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_router__WEBPACK_IMPORTED_MODULE_1__
@@ -61,13 +34,40 @@
 
       /***/
     },
+
+    /***/ 3962: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/withRouter",
+        function () {
+          return __webpack_require__(2148);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
+
+    /***/ 9413: /***/ (
+      module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      module.exports = __webpack_require__(9751);
+
+      /***/
+    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(9216)
+      __webpack_exec__(3962)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for 4719-HASH.js

Diff too large to display

Diff for 6236-HASH.js

Diff too large to display

Diff for main-HASH.js

Diff too large to display

Commit: c18f675

Copy link

codspeed-hq bot commented Jun 10, 2025

CodSpeed Performance Report

Merging #80255 will not alter performance

Comparing lubieowoce/rootparams-module (c18f675) with canary (29ae22b)

Summary

✅ 9 untouched benchmarks

@lubieowoce lubieowoce force-pushed the lubieowoce/rootparams-module branch 2 times, most recently from 86f28a3 to 06e0a33 Compare June 12, 2025 12:21
@mischnic
Copy link
Contributor

I'm slightly worried that this might cause some unnecessary invalidations when new root params are added. AFAIU if the alias definition changes (because collected_root_params changes), we'll also have update the whole ResolveOptionsContext, which seems like it'd invalidate a lot of other things. feedback/ideas for improvement welcome.

That is absolutely correct.

What you want to do is pass down a Vc<CollectedRootParams>, that cell then doesn't change which when adding/removing root params. And then you mustn't read the cell when inserting the import mapping (which currently happens in get_next_root_params_mapping), so if you instead create a ImportMapping::Dynamic, then you would only read the root params when actually encountering a next/root-params imports (and then create the JS sourcecode inside of that).

@lubieowoce lubieowoce force-pushed the lubieowoce/rootparams-module branch 5 times, most recently from 915d276 to 0e59829 Compare June 13, 2025 11:53
Copy link
Member Author

lubieowoce commented Jun 13, 2025

@lubieowoce lubieowoce force-pushed the lubieowoce/rootparams-module branch from 0e59829 to d146b4f Compare June 17, 2025 14:24
@lubieowoce lubieowoce force-pushed the lubieowoce/rootparams-module branch 2 times, most recently from 6e0bd4a to 9f4af52 Compare June 19, 2025 11:29
@lubieowoce lubieowoce force-pushed the lubieowoce/rootparams-module branch from 9f4af52 to 3f90bd5 Compare June 19, 2025 12:08
@lubieowoce lubieowoce changed the title next/root-params [WIP] next/root-params Jun 19, 2025
@lubieowoce lubieowoce force-pushed the lubieowoce/rootparams-module branch from 3f90bd5 to c25486a Compare June 19, 2025 13:12
@lubieowoce lubieowoce marked this pull request as ready for review June 19, 2025 13:57
@sokra
Copy link
Member

sokra commented Jul 21, 2025

Each directory we traverse before finding a root layout is marked as a contextDependency

Any new directory in app could also cause a new root param to be created. So I think you need to contextDependency on the app dir. And since it's recursive, this is the only dependency needed.


#[turbo_tasks::value(transparent)]
#[derive(Default)]
pub struct RootParamVecOption(Option<Vec<RcStr>>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a difference between None and Some(vec![])?

For me it looks like no. In this case I would change it to

Suggested change
pub struct RootParamVecOption(Option<Vec<RcStr>>);
pub struct RootParamVecOption(Vec<RcStr>);

Copy link
Member Author

@lubieowoce lubieowoce Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's needed for the traversal in directory_tree_to_entrypoints_internal_untraced. when we encounter a root layout, we'll compute its root params based on the current app_page path. it should not be recomputed anywhere further below

so

  • None signals "we don't know the root params for this subtree yet" (because we haven't found a root layout)
  • Some(vec!["foo"]) signals "we already have the root params, do not add anything more"
  • Some(vec![]) signals "this subtree has no root params" (which is possible if there's no dynamic segments that occur above a root layout)

for example, the Some/None distinction lets us avoid adding bar for page.tsx here:

app/
  [foo]/
    layout.tsx
    [bar]/
      page.tsx
  • first, we'll visit app/[foo], notice there's a layout (and root_params is None), and make root_params = Some(["foo"]).
  • then, we'll visit app/[foo]/[bar], but we already have root_params: Some(["foo"])passed down from above, so we know not to compute them again. (which would be bad, because here we'd getSome(["foo", "bar"])`)

it's similar for a case with no root params:

app/
  layout.tsx
  [bar]/
    page.tsx

we visit app/, find a root layout, and get root_params = Some([]), which'll also prevent us from making bar a root param.

return getRootParam('{PARAM_NAME}');
}}
"#,
PARAM_NAME = param_name,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
PARAM_NAME = param_name,
PARAM_NAME = StringifyJs(param_name),

It needs different arguments for the identifier and the string argument

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm gonna pull these out into a follow-up, this also needs a bunch of tests and logic to generate a relatively human friendly function name for the getter, and using a param that isn't a valid identifier is uncommon

const content = [
`import { getRootParam } from 'next/dist/server/request/root-params';`,
...sortedRootParamNames.map((paramName) => {
return `export function ${paramName}() { return getRootParam('${paramName}'); }`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return `export function ${paramName}() { return getRootParam('${paramName}'); }`
return `export function ${paramName}() { return getRootParam(${JSON.stringify(paramName)}); }`

.chain(collected_root_params.iter().map(|param_name| {
formatdoc!(
r#"
export function {PARAM_NAME}() {{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes that param_name is an valid identifier. If not this emit invalid code. (the webpack loader too)

@lubieowoce lubieowoce force-pushed the lubieowoce/rootparams-module branch 7 times, most recently from 82edf3c to c5f0638 Compare July 25, 2025 13:34
@lubieowoce lubieowoce force-pushed the lubieowoce/rootparams-module branch from c5f0638 to c326530 Compare July 25, 2025 14:14
@lubieowoce lubieowoce force-pushed the lubieowoce/rootparams-module branch from c326530 to c18f675 Compare July 28, 2025 12:20
@lubieowoce lubieowoce merged commit 742a2c7 into canary Jul 28, 2025
178 of 180 checks passed
@lubieowoce lubieowoce deleted the lubieowoce/rootparams-module branch July 28, 2025 13:01
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 12, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
created-by: Next.js team PRs by the Next.js team. locked Rspack tests Turbopack Related to Turbopack with Next.js. type: next
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants