Skip to content

Refactor: Navigate and refresh simultaneously#86747

Merged
acdlite merged 1 commit intovercel:canaryfrom
acdlite:refactor-navigate-and-refresh
Dec 11, 2025
Merged

Refactor: Navigate and refresh simultaneously#86747
acdlite merged 1 commit intovercel:canaryfrom
acdlite:refactor-navigate-and-refresh

Conversation

@acdlite
Copy link
Contributor

@acdlite acdlite commented Dec 2, 2025

This is a refactor of the ppr-navigations module to support navigating to a new tree and refreshing shared layouts within the same navigation.

Rather than modeling navigations and refreshes/revalidations as separate operations, a refresh is considered a special case of a navigation where any existing dynamic data is disregarded, and new data is fetched from the server. The rest of the tree is diffed as usual, and the routing behavior is the same.

Previously, the ppr-navigations module would revert to the "create" path whenever it was missing dynamic data (the CacheNode) for a shared segment. Now, the FlightRouterState alone is used to diff the old and new trees.

When dynamic data is missing, any shared segment (one that exists in both the old and new trees) is considered to be part of a "refresh", and any segment that is present only in the new tree is considered to be part of a "navigation". There are subtle distinctions in behavior between refreshes and navigations. For example, refreshed page segments aren't scrolled to, but navigated page segments are.

@ijjk ijjk added created-by: Next.js team PRs by the Next.js team. type: next labels Dec 2, 2025
@ijjk
Copy link
Member

ijjk commented Dec 2, 2025

Stats from current PR

Default Build (Increase detected ⚠️)
General
vercel/next.js canary acdlite/next.js refactor-navigate-and-refresh Change
buildDuration 18.7s 15s N/A
buildDurationCached 14.1s 11s N/A
nodeModulesSize 456 MB 456 MB N/A
nextStartRea..uration (ms) 716ms 725ms N/A
Client Bundles (main, webpack) Overall increase ⚠️
vercel/next.js canary acdlite/next.js refactor-navigate-and-refresh Change
4951-HASH.js gzip 5.37 kB 5.38 kB N/A
68680909-HASH.js gzip 62.3 kB 62.3 kB N/A
8034-HASH.js gzip 52.8 kB 52.9 kB ⚠️ +175 B
9564.HASH.js gzip 170 B 168 B N/A
9565-HASH.js gzip 4.46 kB 4.48 kB N/A
framework-HASH.js gzip 59.7 kB 59.7 kB N/A
main-app-HASH.js gzip 255 B 253 B N/A
main-HASH.js gzip 38.6 kB 38.5 kB N/A
webpack-HASH.js gzip 1.69 kB 1.69 kB N/A
Overall change 52.8 kB 52.9 kB ⚠️ +175 B
Legacy Client Bundles (polyfills)
vercel/next.js canary acdlite/next.js refactor-navigate-and-refresh 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 acdlite/next.js refactor-navigate-and-refresh Change
_app-HASH.js gzip 192 B 194 B N/A
_error-HASH.js gzip 183 B 182 B N/A
css-HASH.js gzip 334 B 333 B N/A
dynamic-HASH.js gzip 1.79 kB 1.81 kB N/A
edge-ssr-HASH.js gzip 254 B 255 B N/A
head-HASH.js gzip 352 B 349 B N/A
hooks-HASH.js gzip 383 B 378 B N/A
image-HASH.js gzip 580 B 581 B N/A
index-HASH.js gzip 259 B 258 B N/A
link-HASH.js gzip 2.51 kB 2.5 kB N/A
routerDirect..HASH.js gzip 317 B 319 B N/A
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 314 B 312 B N/A
1afbb74e6ecf..834.css gzip 106 B 106 B
Overall change 492 B 492 B
Client Build Manifests
vercel/next.js canary acdlite/next.js refactor-navigate-and-refresh Change
_buildManifest.js gzip 735 B 738 B N/A
Overall change 0 B 0 B
Rendered Page Sizes
vercel/next.js canary acdlite/next.js refactor-navigate-and-refresh Change
index.html gzip 524 B 522 B N/A
link.html gzip 539 B 538 B N/A
withRouter.html gzip 521 B 519 B N/A
Overall change 0 B 0 B
Edge SSR bundle Size Overall increase ⚠️
vercel/next.js canary acdlite/next.js refactor-navigate-and-refresh Change
edge-ssr.js gzip 124 kB 124 kB ⚠️ +129 B
page.js gzip 236 kB 236 kB N/A
Overall change 124 kB 124 kB ⚠️ +129 B
Middleware size
vercel/next.js canary acdlite/next.js refactor-navigate-and-refresh Change
middleware-b..fest.js gzip 653 B 653 B
middleware-r..fest.js gzip 156 B 156 B
middleware.js gzip 32.8 kB 32.7 kB N/A
edge-runtime..pack.js gzip 846 B 846 B
Overall change 1.66 kB 1.66 kB
Next Runtimes
vercel/next.js canary acdlite/next.js refactor-navigate-and-refresh Change
app-page-exp...dev.js gzip 301 kB 301 kB N/A
app-page-exp..prod.js gzip 155 kB 155 kB N/A
app-page-tur...dev.js gzip 301 kB 300 kB N/A
app-page-tur..prod.js gzip 155 kB 155 kB N/A
app-page-tur...dev.js gzip 297 kB 297 kB N/A
app-page-tur..prod.js gzip 153 kB 153 kB N/A
app-page.run...dev.js gzip 297 kB 297 kB N/A
app-page.run..prod.js gzip 153 kB 153 kB N/A
app-route-ex...dev.js gzip 68.6 kB 68.6 kB
app-route-ex..prod.js gzip 47.5 kB 47.5 kB
app-route-tu...dev.js gzip 68.7 kB 68.7 kB
app-route-tu..prod.js gzip 47.5 kB 47.5 kB
app-route-tu...dev.js gzip 68.3 kB 68.3 kB
app-route-tu..prod.js gzip 47.2 kB 47.2 kB
app-route.ru...dev.js gzip 68.2 kB 68.2 kB
app-route.ru..prod.js gzip 47.2 kB 47.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 41 kB 41 kB
pages-api-tu..prod.js gzip 31.1 kB 31.1 kB
pages-api.ru...dev.js gzip 41 kB 41 kB
pages-api.ru..prod.js gzip 31.1 kB 31.1 kB
pages-turbo....dev.js gzip 50.5 kB 50.5 kB
pages-turbo...prod.js gzip 38 kB 38 kB
pages.runtim...dev.js gzip 50.5 kB 50.5 kB
pages.runtim..prod.js gzip 38 kB 38 kB
server.runti..prod.js gzip 59.8 kB 59.8 kB
Overall change 846 kB 846 kB
build cache Overall increase ⚠️
vercel/next.js canary acdlite/next.js refactor-navigate-and-refresh Change
0.pack gzip 3.1 MB 3.1 MB ⚠️ +3.41 kB
index.pack gzip 94 kB 93 kB N/A
Overall change 3.1 MB 3.1 MB ⚠️ +3.41 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 _buildManifest.js
@@ -611,35 +611,35 @@ self.__BUILD_MANIFEST = (function (a, b, c) {
       numHashes: NaN,
       bitArray: [],
     },
-    "/": ["static\u002Fchunks\u002Fpages\u002Findex-b087a17851e05d6f.js"],
+    "/": ["static\u002Fchunks\u002Fpages\u002Findex-3361ed053e34d664.js"],
     "/_error": [
-      "static\u002Fchunks\u002Fpages\u002F_error-0d6cadd1684bf608.js",
+      "static\u002Fchunks\u002Fpages\u002F_error-c7ffde23a6d1e061.js",
     ],
     "/css": [
       "static\u002Fcss\u002Fded6b86ab9cc0a1f.css",
-      "static\u002Fchunks\u002Fpages\u002Fcss-d018beb4ee617700.js",
+      "static\u002Fchunks\u002Fpages\u002Fcss-0b6177a056485c89.js",
     ],
     "/dynamic": [
-      "static\u002Fchunks\u002Fpages\u002Fdynamic-22fa1f98925287e3.js",
+      "static\u002Fchunks\u002Fpages\u002Fdynamic-8e7954693aad6744.js",
     ],
     "/edge-ssr": [
-      "static\u002Fchunks\u002Fpages\u002Fedge-ssr-79392016bf448b0e.js",
+      "static\u002Fchunks\u002Fpages\u002Fedge-ssr-7515805a0793fb50.js",
     ],
-    "/head": ["static\u002Fchunks\u002Fpages\u002Fhead-7c5bbe7e16e0461c.js"],
-    "/hooks": ["static\u002Fchunks\u002Fpages\u002Fhooks-9fbae4ac20575871.js"],
+    "/head": ["static\u002Fchunks\u002Fpages\u002Fhead-18530459356f6f47.js"],
+    "/hooks": ["static\u002Fchunks\u002Fpages\u002Fhooks-221843a8082c5fe0.js"],
     "/image": [
-      "static\u002Fchunks\u002F9565-2e0c4004742fb14a.js",
-      "static\u002Fchunks\u002Fpages\u002Fimage-811425894781cf7d.js",
+      "static\u002Fchunks\u002F5467-5440e689c12d4e40.js",
+      "static\u002Fchunks\u002Fpages\u002Fimage-fe20cdcfe67a7d4d.js",
     ],
-    "/link": ["static\u002Fchunks\u002Fpages\u002Flink-14068c948da9dc39.js"],
+    "/link": ["static\u002Fchunks\u002Fpages\u002Flink-814c1a2b0c6775c5.js"],
     "/routerDirect": [
-      "static\u002Fchunks\u002Fpages\u002FrouterDirect-6d3e9dcd1348423d.js",
+      "static\u002Fchunks\u002Fpages\u002FrouterDirect-a0028e2a28aa8c85.js",
     ],
     "/script": [
-      "static\u002Fchunks\u002Fpages\u002Fscript-eda35f71f7a910ba.js",
+      "static\u002Fchunks\u002Fpages\u002Fscript-5b734e02315dd67a.js",
     ],
     "/withRouter": [
-      "static\u002Fchunks\u002Fpages\u002FwithRouter-4a6d5ec263e28451.js",
+      "static\u002Fchunks\u002Fpages\u002FwithRouter-db2aefa859481ae2.js",
     ],
     sortedPages: [
       "\u002F",
Diff for css-HASH.js
@@ -1,14 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [9813],
   {
-    /***/ 1361: /***/ (module) => {
-      // extracted by mini-css-extract-plugin
-      module.exports = { helloWorld: "css_helloWorld__aUdUq" };
-
-      /***/
-    },
-
-    /***/ 1651: /***/ (
+    /***/ 942: /***/ (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -16,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/css",
         function () {
-          return __webpack_require__(3321);
+          return __webpack_require__(3023);
         },
       ]);
       if (false) {
@@ -25,7 +18,7 @@
       /***/
     },
 
-    /***/ 3321: /***/ (
+    /***/ 3023: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -39,7 +32,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(7002);
       /* harmony import */ var _css_module_css__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(1361);
+        __webpack_require__(3264);
       /* 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 @@
 
       /***/
     },
+
+    /***/ 3264: /***/ (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__(1651)
+      __webpack_exec__(942)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for dynamic-HASH.js
@@ -1,177 +1,17 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [2291],
   {
-    /***/ 617: /***/ (
-      __unused_webpack_module,
-      exports,
-      __webpack_require__
-    ) => {
-      "use strict";
-      /* __next_internal_client_entry_do_not_use__  cjs */
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "LoadableContext", {
-        enumerable: true,
-        get: function () {
-          return LoadableContext;
-        },
-      });
-      const _interop_require_default = __webpack_require__(1532);
-      const _react = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(6466)
-      );
-      const LoadableContext = _react.default.createContext(null);
-      if (false) {
-      } //# sourceMappingURL=loadable-context.shared-runtime.js.map
-
-      /***/
-    },
-
-    /***/ 620: /***/ (module, exports, __webpack_require__) => {
-      "use strict";
-
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      0 && 0;
-      function _export(target, all) {
-        for (var name in all)
-          Object.defineProperty(target, name, {
-            enumerable: true,
-            get: all[name],
-          });
-      }
-      _export(exports, {
-        /**
-         * This function lets you dynamically import a component.
-         * It uses [React.lazy()](https://react.dev/reference/react/lazy) with [Suspense](https://react.dev/reference/react/Suspense) under the hood.
-         *
-         * Read more: [Next.js Docs: `next/dynamic`](https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading#nextdynamic)
-         */ default: function () {
-          return dynamic;
-        },
-        noSSR: function () {
-          return noSSR;
-        },
-      });
-      const _interop_require_default = __webpack_require__(1532);
-      const _jsxruntime = __webpack_require__(7002);
-      const _react = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(6466)
-      );
-      const _loadablesharedruntime = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(3147)
-      );
-      const isServerSide = "object" === "undefined";
-      // Normalize loader to return the module as form { default: Component } for `React.lazy`.
-      // Also for backward compatible since next/dynamic allows to resolve a component directly with loader
-      // Client component reference proxy need to be converted to a module.
-      function convertModule(mod) {
-        return {
-          default: mod?.default || mod,
-        };
-      }
-      function noSSR(LoadableInitializer, loadableOptions) {
-        // Removing webpack and modules means react-loadable won't try preloading
-        delete loadableOptions.webpack;
-        delete loadableOptions.modules;
-        // This check is necessary to prevent react-loadable from initializing on the server
-        if (!isServerSide) {
-          return LoadableInitializer(loadableOptions);
-        }
-        const Loading = loadableOptions.loading;
-        // This will only be rendered on the server side
-        return () =>
-          /*#__PURE__*/ (0, _jsxruntime.jsx)(Loading, {
-            error: null,
-            isLoading: true,
-            pastDelay: false,
-            timedOut: false,
-          });
-      }
-      function dynamic(dynamicOptions, options) {
-        let loadableFn = _loadablesharedruntime.default;
-        let loadableOptions = {
-          // A loading component is not required, so we default it
-          loading: ({ error, isLoading, pastDelay }) => {
-            if (!pastDelay) return null;
-            if (false) {
-            }
-            return null;
-          },
-        };
-        // Support for direct import(), eg: dynamic(import('../hello-world'))
-        // Note that this is only kept for the edge case where someone is passing in a promise as first argument
-        // The react-loadable babel plugin will turn dynamic(import('../hello-world')) into dynamic(() => import('../hello-world'))
-        // To make sure we don't execute the import without rendering first
-        if (dynamicOptions instanceof Promise) {
-          loadableOptions.loader = () => dynamicOptions;
-          // Support for having import as a function, eg: dynamic(() => import('../hello-world'))
-        } else if (typeof dynamicOptions === "function") {
-          loadableOptions.loader = dynamicOptions;
-          // Support for having first argument being options, eg: dynamic({loader: import('../hello-world')})
-        } else if (typeof dynamicOptions === "object") {
-          loadableOptions = {
-            ...loadableOptions,
-            ...dynamicOptions,
-          };
-        }
-        // Support for passing options, eg: dynamic(import('../hello-world'), {loading: () => <p>Loading something</p>})
-        loadableOptions = {
-          ...loadableOptions,
-          ...options,
-        };
-        const loaderFn = loadableOptions.loader;
-        const loader = () =>
-          loaderFn != null
-            ? loaderFn().then(convertModule)
-            : Promise.resolve(convertModule(() => null));
-        // coming from build/babel/plugins/react-loadable-plugin.js
-        if (loadableOptions.loadableGenerated) {
-          loadableOptions = {
-            ...loadableOptions,
-            ...loadableOptions.loadableGenerated,
-          };
-          delete loadableOptions.loadableGenerated;
-        }
-        // support for disabling server side rendering, eg: dynamic(() => import('../hello-world'), {ssr: false}).
-        if (typeof loadableOptions.ssr === "boolean" && !loadableOptions.ssr) {
-          delete loadableOptions.webpack;
-          delete loadableOptions.modules;
-          return noSSR(loadableFn, loadableOptions);
-        }
-        return loadableFn({
-          ...loadableOptions,
-          loader: loader,
-        });
-      }
-      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=dynamic.js.map
-
-      /***/
-    },
-
-    /***/ 1173: /***/ (
+    /***/ 1151: /***/ (
       module,
       __unused_webpack_exports,
       __webpack_require__
     ) => {
-      module.exports = __webpack_require__(620);
+      module.exports = __webpack_require__(7368);
 
       /***/
     },
 
-    /***/ 2907: /***/ (
+    /***/ 4773: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -186,7 +26,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(7002);
       /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(1173);
+        __webpack_require__(1151);
       /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_dynamic__WEBPACK_IMPORTED_MODULE_1__
@@ -195,12 +35,12 @@
       const DynamicHello = next_dynamic__WEBPACK_IMPORTED_MODULE_1___default()(
         () =>
           __webpack_require__
-            .e(/* import() */ 9564)
-            .then(__webpack_require__.bind(__webpack_require__, 9564))
+            .e(/* import() */ 6022)
+            .then(__webpack_require__.bind(__webpack_require__, 6022))
             .then((mod) => mod.Hello),
         {
           loadableGenerated: {
-            webpack: () => [/*require.resolve*/ 9564],
+            webpack: () => [/*require.resolve*/ 6022],
           },
         }
       );
@@ -227,7 +67,7 @@
       /***/
     },
 
-    /***/ 3147: /***/ (
+    /***/ 5631: /***/ (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -269,7 +109,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       const _react = /*#__PURE__*/ _interop_require_default._(
         __webpack_require__(6466)
       );
-      const _loadablecontextsharedruntime = __webpack_require__(617);
+      const _loadablecontextsharedruntime = __webpack_require__(6861);
       function resolve(obj) {
         return obj && obj.default ? obj.default : obj;
       }
@@ -502,7 +342,167 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       /***/
     },
 
-    /***/ 4236: /***/ (
+    /***/ 6861: /***/ (
+      __unused_webpack_module,
+      exports,
+      __webpack_require__
+    ) => {
+      "use strict";
+      /* __next_internal_client_entry_do_not_use__  cjs */
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "LoadableContext", {
+        enumerable: true,
+        get: function () {
+          return LoadableContext;
+        },
+      });
+      const _interop_require_default = __webpack_require__(1532);
+      const _react = /*#__PURE__*/ _interop_require_default._(
+        __webpack_require__(6466)
+      );
+      const LoadableContext = _react.default.createContext(null);
+      if (false) {
+      } //# sourceMappingURL=loadable-context.shared-runtime.js.map
+
+      /***/
+    },
+
+    /***/ 7368: /***/ (module, exports, __webpack_require__) => {
+      "use strict";
+
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      0 && 0;
+      function _export(target, all) {
+        for (var name in all)
+          Object.defineProperty(target, name, {
+            enumerable: true,
+            get: all[name],
+          });
+      }
+      _export(exports, {
+        /**
+         * This function lets you dynamically import a component.
+         * It uses [React.lazy()](https://react.dev/reference/react/lazy) with [Suspense](https://react.dev/reference/react/Suspense) under the hood.
+         *
+         * Read more: [Next.js Docs: `next/dynamic`](https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading#nextdynamic)
+         */ default: function () {
+          return dynamic;
+        },
+        noSSR: function () {
+          return noSSR;
+        },
+      });
+      const _interop_require_default = __webpack_require__(1532);
+      const _jsxruntime = __webpack_require__(7002);
+      const _react = /*#__PURE__*/ _interop_require_default._(
+        __webpack_require__(6466)
+      );
+      const _loadablesharedruntime = /*#__PURE__*/ _interop_require_default._(
+        __webpack_require__(5631)
+      );
+      const isServerSide = "object" === "undefined";
+      // Normalize loader to return the module as form { default: Component } for `React.lazy`.
+      // Also for backward compatible since next/dynamic allows to resolve a component directly with loader
+      // Client component reference proxy need to be converted to a module.
+      function convertModule(mod) {
+        return {
+          default: mod?.default || mod,
+        };
+      }
+      function noSSR(LoadableInitializer, loadableOptions) {
+        // Removing webpack and modules means react-loadable won't try preloading
+        delete loadableOptions.webpack;
+        delete loadableOptions.modules;
+        // This check is necessary to prevent react-loadable from initializing on the server
+        if (!isServerSide) {
+          return LoadableInitializer(loadableOptions);
+        }
+        const Loading = loadableOptions.loading;
+        // This will only be rendered on the server side
+        return () =>
+          /*#__PURE__*/ (0, _jsxruntime.jsx)(Loading, {
+            error: null,
+            isLoading: true,
+            pastDelay: false,
+            timedOut: false,
+          });
+      }
+      function dynamic(dynamicOptions, options) {
+        let loadableFn = _loadablesharedruntime.default;
+        let loadableOptions = {
+          // A loading component is not required, so we default it
+          loading: ({ error, isLoading, pastDelay }) => {
+            if (!pastDelay) return null;
+            if (false) {
+            }
+            return null;
+          },
+        };
+        // Support for direct import(), eg: dynamic(import('../hello-world'))
+        // Note that this is only kept for the edge case where someone is passing in a promise as first argument
+        // The react-loadable babel plugin will turn dynamic(import('../hello-world')) into dynamic(() => import('../hello-world'))
+        // To make sure we don't execute the import without rendering first
+        if (dynamicOptions instanceof Promise) {
+          loadableOptions.loader = () => dynamicOptions;
+          // Support for having import as a function, eg: dynamic(() => import('../hello-world'))
+        } else if (typeof dynamicOptions === "function") {
+          loadableOptions.loader = dynamicOptions;
+          // Support for having first argument being options, eg: dynamic({loader: import('../hello-world')})
+        } else if (typeof dynamicOptions === "object") {
+          loadableOptions = {
+            ...loadableOptions,
+            ...dynamicOptions,
+          };
+        }
+        // Support for passing options, eg: dynamic(import('../hello-world'), {loading: () => <p>Loading something</p>})
+        loadableOptions = {
+          ...loadableOptions,
+          ...options,
+        };
+        const loaderFn = loadableOptions.loader;
+        const loader = () =>
+          loaderFn != null
+            ? loaderFn().then(convertModule)
+            : Promise.resolve(convertModule(() => null));
+        // coming from build/babel/plugins/react-loadable-plugin.js
+        if (loadableOptions.loadableGenerated) {
+          loadableOptions = {
+            ...loadableOptions,
+            ...loadableOptions.loadableGenerated,
+          };
+          delete loadableOptions.loadableGenerated;
+        }
+        // support for disabling server side rendering, eg: dynamic(() => import('../hello-world'), {ssr: false}).
+        if (typeof loadableOptions.ssr === "boolean" && !loadableOptions.ssr) {
+          delete loadableOptions.webpack;
+          delete loadableOptions.modules;
+          return noSSR(loadableFn, loadableOptions);
+        }
+        return loadableFn({
+          ...loadableOptions,
+          loader: loader,
+        });
+      }
+      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=dynamic.js.map
+
+      /***/
+    },
+
+    /***/ 8258: /***/ (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -510,7 +510,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/dynamic",
         function () {
-          return __webpack_require__(2907);
+          return __webpack_require__(4773);
         },
       ]);
       if (false) {
@@ -524,7 +524,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(4236)
+      __webpack_exec__(8258)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for head-HASH.js
@@ -1,24 +1,13 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [5350],
   {
-    /***/ 2366: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/head",
-        function () {
-          return __webpack_require__(8140);
-        },
-      ]);
-      if (false) {
-      }
+    /***/ 48: /***/ (module, __unused_webpack_exports, __webpack_require__) => {
+      module.exports = __webpack_require__(8141);
 
       /***/
     },
 
-    /***/ 8140: /***/ (
+    /***/ 2682: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -33,7 +22,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(7002);
       /* harmony import */ var next_head__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(9386);
+        __webpack_require__(48);
       /* harmony import */ var next_head__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_head__WEBPACK_IMPORTED_MODULE_1__
@@ -67,12 +56,19 @@
       /***/
     },
 
-    /***/ 9386: /***/ (
-      module,
+    /***/ 2956: /***/ (
+      __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
     ) => {
-      module.exports = __webpack_require__(1649);
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/head",
+        function () {
+          return __webpack_require__(2682);
+        },
+      ]);
+      if (false) {
+      }
 
       /***/
     },
@@ -82,7 +78,7 @@
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(2366)
+      __webpack_exec__(2956)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for image-HASH.js
@@ -1,7 +1,24 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [2983],
   {
-    /***/ 497: /***/ (
+    /***/ 8114: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/image",
+        function () {
+          return __webpack_require__(9180);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
+
+    /***/ 9180: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -18,8 +35,8 @@
 
       // EXTERNAL MODULE: ./node_modules/.pnpm/react@19.2.1/node_modules/react/jsx-runtime.js
       var jsx_runtime = __webpack_require__(7002);
-      // EXTERNAL MODULE: ./node_modules/.pnpm/next@file+..+main-repo+packages+next+next-packed.tgz_react-dom@19.2.1_react@19.2.1__react@19.2.1/node_modules/next/image.js
-      var next_image = __webpack_require__(9565);
+      // EXTERNAL MODULE: ./node_modules/.pnpm/next@file+..+diff-repo+packages+next+next-packed.tgz_react-dom@19.2.1_react@19.2.1__react@19.2.1/node_modules/next/image.js
+      var next_image = __webpack_require__(5467);
       var image_default = /*#__PURE__*/ __webpack_require__.n(next_image); // ./pages/nextjs.png
       /* harmony default export */ const nextjs = {
         src: "/_next/static/media/nextjs.cae0b805.png",
@@ -48,30 +65,13 @@
 
       /***/
     },
-
-    /***/ 5748: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/image",
-        function () {
-          return __webpack_require__(497);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
-    /******/ __webpack_require__.O(0, [9565, 636, 6593, 8792], () =>
-      __webpack_exec__(5748)
+    /******/ __webpack_require__.O(0, [5467, 636, 6593, 8792], () =>
+      __webpack_exec__(8114)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for link-HASH.js
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [4672],
   {
-    /***/ 1182: /***/ (
+    /***/ 2156: /***/ (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/link",
         function () {
-          return __webpack_require__(4610);
+          return __webpack_require__(8272);
         },
       ]);
       if (false) {
@@ -18,7 +18,220 @@
       /***/
     },
 
-    /***/ 1557: /***/ (module, exports, __webpack_require__) => {
+    /***/ 4581: /***/ (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__(6466);
+      const _requestidlecallback = __webpack_require__(7441);
+      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({ rootRef, rootMargin, disabled }) {
+        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?.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
+
+      /***/
+    },
+
+    /***/ 6155: /***/ (module, exports, __webpack_require__) => {
+      "use strict";
+
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "useMergedRef", {
+        enumerable: true,
+        get: function () {
+          return useMergedRef;
+        },
+      });
+      const _react = __webpack_require__(6466);
+      function useMergedRef(refA, refB) {
+        const cleanupA = (0, _react.useRef)(null);
+        const cleanupB = (0, _react.useRef)(null);
+        // NOTE: In theory, we could skip the wrapping if only one of the refs is non-null.
+        // (this happens often if the user doesn't pass a ref to Link/Form/Image)
+        // But this can cause us to leak a cleanup-ref into user code (previously via `<Link legacyBehavior>`),
+        // and the user might pass that ref into ref-merging library that doesn't support cleanup refs
+        // (because it hasn't been updated for React 19)
+        // which can then cause things to blow up, because a cleanup-returning ref gets called with `null`.
+        // So in practice, it's safer to be defensive and always wrap the ref, even on React 19.
+        return (0, _react.useCallback)(
+          (current) => {
+            if (current === null) {
+              const cleanupFnA = cleanupA.current;
+              if (cleanupFnA) {
+                cleanupA.current = null;
+                cleanupFnA();
+              }
+              const cleanupFnB = cleanupB.current;
+              if (cleanupFnB) {
+                cleanupB.current = null;
+                cleanupFnB();
+              }
+            } else {
+              if (refA) {
+                cleanupA.current = applyRef(refA, current);
+              }
+              if (refB) {
+                cleanupB.current = applyRef(refB, current);
+              }
+            }
+          },
+          [refA, refB]
+        );
+      }
+      function applyRef(refA, current) {
+        if (typeof refA === "function") {
+          const cleanup = refA(current);
+          if (typeof cleanup === "function") {
+            return cleanup;
+          } else {
+            return () => refA(null);
+          }
+        } else {
+          refA.current = current;
+          return () => {
+            refA.current = null;
+          };
+        }
+      }
+      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-merged-ref.js.map
+
+      /***/
+    },
+
+    /***/ 7046: /***/ (
+      module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      module.exports = __webpack_require__(8113);
+
+      /***/
+    },
+
+    /***/ 8113: /***/ (module, exports, __webpack_require__) => {
       "use strict";
       /* __next_internal_client_entry_do_not_use__  cjs */
       Object.defineProperty(exports, "__esModule", {
@@ -45,17 +258,17 @@
       const _react = /*#__PURE__*/ _interop_require_wildcard._(
         __webpack_require__(6466)
       );
-      const _resolvehref = __webpack_require__(5475);
-      const _islocalurl = __webpack_require__(6475);
-      const _formaturl = __webpack_require__(9998);
-      const _utils = __webpack_require__(3084);
-      const _addlocale = __webpack_require__(3921);
-      const _routercontextsharedruntime = __webpack_require__(7726);
-      const _useintersection = __webpack_require__(4806);
-      const _getdomainlocale = __webpack_require__(3219);
-      const _addbasepath = __webpack_require__(5498);
-      const _usemergedref = __webpack_require__(3255);
-      const _erroronce = __webpack_require__(1685);
+      const _resolvehref = __webpack_require__(2399);
+      const _islocalurl = __webpack_require__(6687);
+      const _formaturl = __webpack_require__(4802);
+      const _utils = __webpack_require__(9208);
+      const _addlocale = __webpack_require__(9845);
+      const _routercontextsharedruntime = __webpack_require__(3466);
+      const _useintersection = __webpack_require__(4581);
+      const _getdomainlocale = __webpack_require__(8319);
+      const _addbasepath = __webpack_require__(4126);
+      const _usemergedref = __webpack_require__(6155);
+      const _erroronce = __webpack_require__(8305);
       const prefetched = new Set();
       function prefetch(router, href, as, options) {
         if (false) {
@@ -434,140 +647,7 @@
       /***/
     },
 
-    /***/ 1685: /***/ (__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
-
-      /***/
-    },
-
-    /***/ 3219: /***/ (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__(1059);
-      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
-
-      /***/
-    },
-
-    /***/ 3255: /***/ (module, exports, __webpack_require__) => {
-      "use strict";
-
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "useMergedRef", {
-        enumerable: true,
-        get: function () {
-          return useMergedRef;
-        },
-      });
-      const _react = __webpack_require__(6466);
-      function useMergedRef(refA, refB) {
-        const cleanupA = (0, _react.useRef)(null);
-        const cleanupB = (0, _react.useRef)(null);
-        // NOTE: In theory, we could skip the wrapping if only one of the refs is non-null.
-        // (this happens often if the user doesn't pass a ref to Link/Form/Image)
-        // But this can cause us to leak a cleanup-ref into user code (previously via `<Link legacyBehavior>`),
-        // and the user might pass that ref into ref-merging library that doesn't support cleanup refs
-        // (because it hasn't been updated for React 19)
-        // which can then cause things to blow up, because a cleanup-returning ref gets called with `null`.
-        // So in practice, it's safer to be defensive and always wrap the ref, even on React 19.
-        return (0, _react.useCallback)(
-          (current) => {
-            if (current === null) {
-              const cleanupFnA = cleanupA.current;
-              if (cleanupFnA) {
-                cleanupA.current = null;
-                cleanupFnA();
-              }
-              const cleanupFnB = cleanupB.current;
-              if (cleanupFnB) {
-                cleanupB.current = null;
-                cleanupFnB();
-              }
-            } else {
-              if (refA) {
-                cleanupA.current = applyRef(refA, current);
-              }
-              if (refB) {
-                cleanupB.current = applyRef(refB, current);
-              }
-            }
-          },
-          [refA, refB]
-        );
-      }
-      function applyRef(refA, current) {
-        if (typeof refA === "function") {
-          const cleanup = refA(current);
-          if (typeof cleanup === "function") {
-            return cleanup;
-          } else {
-            return () => refA(null);
-          }
-        } else {
-          refA.current = current;
-          return () => {
-            refA.current = null;
-          };
-        }
-      }
-      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-merged-ref.js.map
-
-      /***/
-    },
-
-    /***/ 4610: /***/ (
+    /***/ 8272: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -582,7 +662,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(7002);
       /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(9056);
+        __webpack_require__(7046);
       /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_link__WEBPACK_IMPORTED_MODULE_1__
@@ -613,115 +693,45 @@
       /***/
     },
 
-    /***/ 4806: /***/ (module, exports, __webpack_require__) => {
+    /***/ 8305: /***/ (__unused_webpack_module, exports) => {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
         value: true,
       });
-      Object.defineProperty(exports, "useIntersection", {
+      Object.defineProperty(exports, "errorOnce", {
         enumerable: true,
         get: function () {
-          return useIntersection;
+          return errorOnce;
         },
       });
-      const _react = __webpack_require__(6466);
-      const _requestidlecallback = __webpack_require__(805);
-      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;
-          }
+      let errorOnce = (_) => {};
+      if (false) {
+      } //# sourceMappingURL=error-once.js.map
+
+      /***/
+    },
+
+    /***/ 8319: /***/ (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__(6063);
+      const basePath =
+        /* unused pure expression or super */ null && (false || "");
+      function getDomainLocale(path, locale, locales, domainLocales) {
+        if (false) {
+        } else {
+          return false;
         }
-        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({ rootRef, rootMargin, disabled }) {
-        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?.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" ||
@@ -733,17 +743,7 @@
         });
         Object.assign(exports.default, exports);
         module.exports = exports.default;
-      } //# sourceMappingURL=use-intersection.js.map
-
-      /***/
-    },
-
-    /***/ 9056: /***/ (
-      module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      module.exports = __webpack_require__(1557);
+      } //# sourceMappingURL=get-domain-locale.js.map
 
       /***/
     },
@@ -753,7 +753,7 @@
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(1182)
+      __webpack_exec__(2156)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for script-HASH.js
@@ -1,13 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [1209],
   {
-    /***/ 15: /***/ (module, __unused_webpack_exports, __webpack_require__) => {
-      module.exports = __webpack_require__(5258);
-
-      /***/
-    },
-
-    /***/ 6853: /***/ (
+    /***/ 2895: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -22,7 +16,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(7002);
       /* harmony import */ var next_script__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(15);
+        __webpack_require__(3489);
       /* harmony import */ var next_script__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_script__WEBPACK_IMPORTED_MODULE_1__
@@ -55,7 +49,17 @@
       /***/
     },
 
-    /***/ 9264: /***/ (
+    /***/ 3489: /***/ (
+      module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      module.exports = __webpack_require__(3102);
+
+      /***/
+    },
+
+    /***/ 9884: /***/ (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -63,7 +67,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/script",
         function () {
-          return __webpack_require__(6853);
+          return __webpack_require__(2895);
         },
       ]);
       if (false) {
@@ -77,7 +81,7 @@
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(9264)
+      __webpack_exec__(9884)
     );
     /******/ 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],
   {
-    /***/ 1593: /***/ (
-      module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      module.exports = __webpack_require__(372);
-
-      /***/
-    },
-
-    /***/ 4550: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/withRouter",
-        function () {
-          return __webpack_require__(8527);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
-
-    /***/ 8527: /***/ (
+    /***/ 69: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -43,7 +16,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(7002);
       /* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(1593);
+        __webpack_require__(2027);
       /* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_router__WEBPACK_IMPORTED_MODULE_1__
@@ -61,13 +34,40 @@
 
       /***/
     },
+
+    /***/ 2027: /***/ (
+      module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      module.exports = __webpack_require__(1536);
+
+      /***/
+    },
+
+    /***/ 2268: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/withRouter",
+        function () {
+          return __webpack_require__(69);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(4550)
+      __webpack_exec__(2268)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for 4951-HASH.js

Diff too large to display

Diff for 8034-HASH.js
failed to diff
Diff for 9565-HASH.js

Diff too large to display

Diff for main-HASH.js

Diff too large to display

Diff for app-page-exp..ntime.dev.js

Diff too large to display

Diff for app-page-exp..time.prod.js

Diff too large to display

Diff for app-page-tur..ntime.dev.js

Diff too large to display

Diff for app-page-tur..time.prod.js

Diff too large to display

Diff for app-page-tur..ntime.dev.js

Diff too large to display

Diff for app-page-tur..time.prod.js

Diff too large to display

Diff for app-page.runtime.dev.js

Diff too large to display

Diff for app-page.runtime.prod.js

Diff too large to display

Commit: 822cbf0

Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

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

Additional Suggestion:

The comment references a function startPPRRefresh that no longer exists; the code actually calls startPPRNavigation.

View Details
📝 Patch Details
diff --git a/packages/next/src/client/components/segment-cache/navigation.ts b/packages/next/src/client/components/segment-cache/navigation.ts
index cd7d12cccd..02d943fcdd 100644
--- a/packages/next/src/client/components/segment-cache/navigation.ts
+++ b/packages/next/src/client/components/segment-cache/navigation.ts
@@ -218,8 +218,8 @@ export function refresh(
 ): SuccessfulNavigationResult | MPANavigationResult {
   // A refresh is a special case of a navigation where all the dynamic data
   // on the current router is re-fetched. Most of the logic is handled within
-  // the ppr-navigations module. The main difference here is that we call
-  // startPPRRefresh instead of startPPRNavigation.
+  // the ppr-navigations module. We model refreshes as a special case of
+  // navigation by calling startPPRNavigation.
   const now = Date.now()
   const shouldScroll = true
   const accumulation: NavigationRequestAccumulation = {

Analysis

Outdated comment references non-existent function startPPRRefresh

What fails: Comment at line 222 of packages/next/src/client/components/segment-cache/navigation.ts incorrectly states "we call startPPRRefresh instead of startPPRNavigation", but the function startPPRRefresh does not exist and the code actually calls startPPRNavigation.

How to reproduce:

grep -r "startPPRRefresh" packages/next/src --include="*.ts" --include="*.tsx"

Result: Only one match found - the misleading comment. The function is not defined anywhere in the codebase.

Expected: Comment should accurately document that refreshes are modeled as a special case of navigation by calling startPPRNavigation, not referencing a non-existent function.

This is a documentation issue that could confuse future maintainers about the actual function being called.

Fix on Vercel

@acdlite acdlite force-pushed the refactor-navigate-and-refresh branch from a0db05f to 9f0b224 Compare December 2, 2025 19:14
@ijjk
Copy link
Member

ijjk commented Dec 2, 2025

Tests Passed

@acdlite acdlite force-pushed the refactor-navigate-and-refresh branch 2 times, most recently from 467eb40 to 6ba9cba Compare December 2, 2025 20:57
@acdlite acdlite force-pushed the refactor-navigate-and-refresh branch 2 times, most recently from 3b64ac6 to 311a877 Compare December 4, 2025 00:35
@acdlite acdlite force-pushed the refactor-navigate-and-refresh branch 3 times, most recently from 77722d5 to 820760e Compare December 4, 2025 01:50
Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

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

Additional Suggestion:

The comment on line 223 claims the code calls startPPRRefresh instead of startPPRNavigation, but this is incorrect - the code actually calls startPPRNavigation. This misleading comment was not updated when the startPPRRefresh function was removed during the refactoring to consolidate navigation and refresh operations.

View Details
📝 Patch Details
diff --git a/packages/next/src/client/components/segment-cache/navigation.ts b/packages/next/src/client/components/segment-cache/navigation.ts
index c5455c55cf..5b71c148ba 100644
--- a/packages/next/src/client/components/segment-cache/navigation.ts
+++ b/packages/next/src/client/components/segment-cache/navigation.ts
@@ -219,8 +219,8 @@ export function refresh(
 ): SuccessfulNavigationResult | MPANavigationResult {
   // A refresh is a special case of a navigation where all the dynamic data
   // on the current router is re-fetched. Most of the logic is handled within
-  // the ppr-navigations module. The main difference here is that we call
-  // startPPRRefresh instead of startPPRNavigation.
+  // the ppr-navigations module. The main difference here is that we pass
+  // shouldRefreshDynamicData: true to indicate this is a refresh operation.
   const now = Date.now()
   const shouldScroll = true
   const accumulation: NavigationRequestAccumulation = {

Analysis

Outdated comment misleads about navigation function call in refresh operation

What fails: The comment in refresh() function (lines 220-223 of packages/next/src/client/components/segment-cache/navigation.ts) incorrectly states that the code calls startPPRRefresh instead of startPPRNavigation, but startPPRRefresh does not exist and the code actually calls startPPRNavigation with shouldRefreshDynamicData: true.

How to reproduce: Examine the code at line 223 in packages/next/src/client/components/segment-cache/navigation.ts - the comment contradicts the actual implementation at line 231.

Result: The misleading comment suggests a separate startPPRRefresh function exists when it doesn't, causing confusion for future developers reading this code.

Expected: The comment should accurately reflect that startPPRNavigation is called with shouldRefreshDynamicData: true to indicate a refresh operation, not that a separate startPPRRefresh function is called. This aligns with the consolidation of navigation and refresh operations mentioned in the refactoring.

Fix applied: Updated comment to state: "The main difference here is that we pass shouldRefreshDynamicData: true to indicate this is a refresh operation." instead of claiming a separate startPPRRefresh function is called.

Fix on Vercel

@acdlite acdlite force-pushed the refactor-navigate-and-refresh branch 6 times, most recently from ad7995b to 74d3575 Compare December 5, 2025 16:42
@acdlite acdlite force-pushed the refactor-navigate-and-refresh branch from 74d3575 to 43c4e6a Compare December 5, 2025 18:57
@acdlite acdlite marked this pull request as ready for review December 5, 2025 21:27
@acdlite acdlite force-pushed the refactor-navigate-and-refresh branch from 43c4e6a to 1d86aaf Compare December 8, 2025 03:41
@acdlite acdlite force-pushed the refactor-navigate-and-refresh branch from 1d86aaf to 4e663c1 Compare December 8, 2025 03:57
This is a refactor of the ppr-navigations module to support navigating
to a new tree and refreshing shared layouts within the same navigation.

Rather than modeling navigations and refreshes/revalidations as
separate operations, a refresh is considered a special case of a
navigation where any existing dynamic data is disregarded, and new
data is fetched from the server. The rest of the tree is diffed as
usual, and the routing behavior is the same.

Previously, the ppr-navigations module would revert to the "create"
path whenever it was missing dynamic data (the CacheNode) for a
shared segment. Now, the FlightRouterState alone is used to diff
the old and new trees.

When dynamic data is missing, any shared segment (one that exists in
both the old and new trees) is considered to be part of a "refresh", and
any segment that is present only in the new tree is considered to be
part of a "navigation". There are subtle distinctions in behavior
between refreshes and navigations. For example, refreshed page segments
aren't scrolled to, but navigated page segments are.
@acdlite acdlite force-pushed the refactor-navigate-and-refresh branch from 4e663c1 to 822cbf0 Compare December 10, 2025 01:47
@acdlite
Copy link
Contributor Author

acdlite commented Dec 11, 2025

Approved as part of stack in #86878. I'll land them separately to preserve bisectability.

@acdlite acdlite merged commit e9a03ac into vercel:canary Dec 11, 2025
278 of 281 checks passed
icyJoseph pushed a commit that referenced this pull request Dec 17, 2025
This is a refactor of the ppr-navigations module to support navigating
to a new tree and refreshing shared layouts within the same navigation.

Rather than modeling navigations and refreshes/revalidations as separate
operations, a refresh is considered a special case of a navigation where
any existing dynamic data is disregarded, and new data is fetched from
the server. The rest of the tree is diffed as usual, and the routing
behavior is the same.

Previously, the ppr-navigations module would revert to the "create" path
whenever it was missing dynamic data (the CacheNode) for a shared
segment. Now, the FlightRouterState alone is used to diff the old and
new trees.

When dynamic data is missing, any shared segment (one that exists in
both the old and new trees) is considered to be part of a "refresh", and
any segment that is present only in the new tree is considered to be
part of a "navigation". There are subtle distinctions in behavior
between refreshes and navigations. For example, refreshed page segments
aren't scrolled to, but navigated page segments are.
@github-actions github-actions bot added the locked label Jan 5, 2026
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jan 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants