Skip to content

Conversation

@bgw
Copy link
Member

@bgw bgw commented Nov 29, 2025

#86338 switches cell storage to use bincode, but it uses a compatibility shim for storing TaskInputs using serde + pot. This PR completes the migration by switching TaskInput to use bincode as well.

Binary Size

https://www.npmjs.com/package/@next/swc-darwin-arm64/v/16.0.7

Unpacked Size: 126 MB

https://www.npmjs.com/package/@next/swc-darwin-arm64/v/16.1.0-canary.14 (prior to #86338)

Unpacked Size: 128 MB

https://www.npmjs.com/package/@next/swc-darwin-arm64/v/16.1.0-canary.15 (after #86338, but before #86631)

Unpacked Size: 109 MB

Benchmark Summary

next build is consistently faster across a variety of repositories when persistent caching is enabled, and the resulting cache size is consistently smaller.

Screenshot 2025-12-01 at 9.07.20 PM.png

cargo build runs only slightly faster, but there's potential for improving this further by removing unused serde macro derives.

Benchmarking Notes

Performance measurements (aside from cargo build) are taken using next build​. This should have proportional benefits for next dev​ too, but next build​ is more reproducible and easier to measure. canary refers to a8f7b5467485647def4c0f4479a0a83bc4a3673a.

These numbers are taken with persistent caching enabled with an empty cache, so it's the worst-case scenario for the serialization codepath. We don't care nearly as much about the performance of deserialization because it's less common.

cargo build -p next-swc-napi

This makes builds slightly faster. This branch doesn't clean up the serde derives yet, so there's a bunch of unused macro expansion here, so I think this number is actually pretty good.

~/next.js $ hyperfine -w 1 -r 5 -p 'git co a8f7b5467485647def4c0f4479a0a83bc4a3673a && cargo clean' -p 'git co bgw/bincode-task-inputs && cargo clean' 'cargo build -p next-swc-napi' 'cargo build -p next-swc-napi'
Benchmark 1: cargo build -p next-swc-napi
  Time (mean ± σ):     237.476 s ±  1.420 s    [User: 2001.820 s, System: 84.957 s]
  Range (min … max):   235.294 s … 238.862 s    5 runs

Benchmark 2: cargo build -p next-swc-napi
  Time (mean ± σ):     232.366 s ±  1.549 s    [User: 1945.355 s, System: 83.940 s]
  Range (min … max):   230.550 s … 234.485 s    5 runs

Summary
  cargo build -p next-swc-napi ran
    1.02 ± 0.01 times faster than cargo build -p next-swc-napi

next-site

Using ~/front/apps/next-site on macos with a M2 Pro. Nothing else was running on the machine.

hyperfine -p 'rm -rf .next' -w 2 -r 20 'TURBOPACK_PERSISTENT_CACHE=1 TURBO_ENGINE_IGNORE_DIRTY=1 pnpm next build --turbopack --experimental-build-mode=compile'
du -sh .next/cache/turbopack/
du -s .next/cache/turbopack/

Canary

Benchmark 1: TURBOPACK_PERSISTENT_CACHE=1 TURBO_ENGINE_IGNORE_DIRTY=1 pnpm next build --turbopack --experimental-build-mode=compile
  Time (mean ± σ):     14.999 s ±  0.258 s    [User: 84.137 s, System: 8.683 s]
  Range (min … max):   14.400 s … 15.509 s    20 runs
855M    .next/cache/turbopack/
1751640 .next/cache/turbopack/    (Note: macos measures size in 512B blocks)

This Branch

Benchmark 1: TURBOPACK_PERSISTENT_CACHE=1 TURBO_ENGINE_IGNORE_DIRTY=1 pnpm next build --turbopack --experimental-build-mode=compile
  Time (mean ± σ):     14.106 s ±  0.255 s    [User: 79.976 s, System: 8.421 s]
  Range (min … max):   13.829 s … 14.939 s    20 runs
767M    .next/cache/turbopack/
1571072 .next/cache/turbopack/    (Note: macos measures size in 512B blocks)

vercel-site

Using ~/front/apps/vercel-site on macos with a M2 Pro. Nothing else was running on the machine.

hyperfine -p 'rm -rf .next' -w 2 -r 20 'TURBOPACK_PERSISTENT_CACHE=1 TURBO_ENGINE_IGNORE_DIRTY=1 pnpm next build --turbopack --experimental-build-mode=compile'
du -sh .next/cache/turbopack/
du -s .next/cache/turbopack/

Canary

Benchmark 1: TURBOPACK_PERSISTENT_CACHE=1 TURBO_ENGINE_IGNORE_DIRTY=1 pnpm next build --turbopack --experimental-build-mode=compile
  Time (mean ± σ):     93.274 s ±  1.630 s    [User: 671.120 s, System: 57.888 s]
  Range (min … max):   91.111 s … 96.881 s    10 runs
4.3G    .next/cache/turbopack/
9002568 .next/cache/turbopack/    (Note: macos measures size in 512B blocks)

This Branch

Benchmark 1: TURBOPACK_PERSISTENT_CACHE=1 TURBO_ENGINE_IGNORE_DIRTY=1 pnpm next build --turbopack --experimental-build-mode=compile
  Time (mean ± σ):     88.694 s ±  0.963 s    [User: 633.801 s, System: 54.576 s]
  Range (min … max):   87.357 s … 90.037 s    10 runs
3.7G    .next/cache/turbopack/
7788472 .next/cache/turbopack/    (Note: macos measures size in 512B blocks)

shadcn/ui

Using a low-noise Linux machine: https://github.com/bgw/benchmark-scripts/

diff --git a/apps/v4/next.config.mjs b/apps/v4/next.config.mjs
index 7fa0f012..d1137d01 100644
--- a/apps/v4/next.config.mjs
+++ b/apps/v4/next.config.mjs
@@ -27,6 +27,7 @@ const nextConfig = {
   },
   experimental: {
     turbopackFileSystemCacheForDev: true,
+    turbopackFileSystemCacheForBuild: true,
   },
   redirects() {
     return [
cd ~/shadcn-ui/apps/v4
hyperfine -p 'rm -rf .next' -w 2 'pnpm next build --turbopack --experimental-build-mode=compile'
du -sh .next/cache/turbopack/
du -s .next/cache/turbopack/

Canary

Benchmark 1: pnpm next build --turbopack --experimental-build-mode=compile
  Time (mean ± σ):     62.772 s ±  0.939 s    [User: 163.153 s, System: 11.170 s]
  Range (min … max):   60.739 s … 64.220 s    10 runs
596M    .next/cache/turbopack/
609636  .next/cache/turbopack/    (Note: Linux measures size in 1024B blocks)

This Branch

Benchmark 1: pnpm next build --turbopack --experimental-build-mode=compile
  Time (mean ± σ):     59.017 s ±  1.014 s    [User: 151.359 s, System: 9.978 s]
  Range (min … max):   57.124 s … 60.343 s    10 runs
491M    .next/cache/turbopack/
502240  .next/cache/turbopack/    (Note: Linux measures size in 1024B blocks)

@ijjk
Copy link
Member

ijjk commented Nov 29, 2025

Stats from current PR

Default Build (Increase detected ⚠️)
General
vercel/next.js canary vercel/next.js bgw/bincode-task-inputs Change
buildDuration 18s 15.1s N/A
buildDurationCached 14s 11s N/A
nodeModulesSize 456 MB 456 MB
nextStartRea..uration (ms) 717ms 713ms N/A
Client Bundles (main, webpack) Overall increase ⚠️
vercel/next.js canary vercel/next.js bgw/bincode-task-inputs 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 53 kB ⚠️ +204 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 252 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 53 kB ⚠️ +204 B
Legacy Client Bundles (polyfills)
vercel/next.js canary vercel/next.js bgw/bincode-task-inputs 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 bgw/bincode-task-inputs 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 vercel/next.js bgw/bincode-task-inputs Change
_buildManifest.js gzip 735 B 738 B N/A
Overall change 0 B 0 B
Rendered Page Sizes
vercel/next.js canary vercel/next.js bgw/bincode-task-inputs Change
index.html gzip 524 B 523 B N/A
link.html gzip 539 B 539 B
withRouter.html gzip 521 B 520 B N/A
Overall change 539 B 539 B
Edge SSR bundle Size Overall increase ⚠️
vercel/next.js canary vercel/next.js bgw/bincode-task-inputs Change
edge-ssr.js gzip 126 kB 126 kB ⚠️ +189 B
page.js gzip 236 kB 236 kB N/A
Overall change 126 kB 126 kB ⚠️ +189 B
Middleware size
vercel/next.js canary vercel/next.js bgw/bincode-task-inputs 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.9 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 vercel/next.js bgw/bincode-task-inputs Change
app-page-exp...dev.js gzip 301 kB 301 kB
app-page-exp..prod.js gzip 155 kB 155 kB
app-page-tur...dev.js gzip 300 kB 300 kB
app-page-tur..prod.js gzip 155 kB 155 kB
app-page-tur...dev.js gzip 297 kB 297 kB N/A
app-page-tur..prod.js gzip 153 kB 153 kB
app-page.run...dev.js gzip 297 kB 297 kB N/A
app-page.run..prod.js gzip 153 kB 153 kB
app-route-ex...dev.js gzip 68.5 kB 68.5 kB
app-route-ex..prod.js gzip 47.3 kB 47.3 kB
app-route-tu...dev.js gzip 68.5 kB 68.5 kB
app-route-tu..prod.js gzip 47.3 kB 47.3 kB
app-route-tu...dev.js gzip 68.1 kB 68.1 kB
app-route-tu..prod.js gzip 47.1 kB 47.1 kB
app-route.ru...dev.js gzip 68.1 kB 68.1 kB
app-route.ru..prod.js gzip 47.1 kB 47.1 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 40.9 kB 40.9 kB
pages-api-tu..prod.js gzip 31 kB 31 kB
pages-api.ru...dev.js gzip 40.8 kB 40.8 kB
pages-api.ru..prod.js gzip 30.9 kB 30.9 kB
pages-turbo....dev.js gzip 50.3 kB 50.3 kB
pages-turbo...prod.js gzip 37.8 kB 37.8 kB
pages.runtim...dev.js gzip 50.3 kB 50.3 kB
pages.runtim..prod.js gzip 37.8 kB 37.8 kB
server.runti..prod.js gzip 59.8 kB 59.8 kB
Overall change 2.06 MB 2.06 MB
build cache
vercel/next.js canary vercel/next.js bgw/bincode-task-inputs Change
0.pack gzip 3.11 MB 3.1 MB N/A
index.pack gzip 93.7 kB 93.3 kB N/A
Overall change 0 B 0 B
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

Commit: 20e09ad

@ijjk
Copy link
Member

ijjk commented Nov 29, 2025

Tests Passed

@codspeed-hq
Copy link

codspeed-hq bot commented Nov 29, 2025

CodSpeed Performance Report

Merging #86631 will not alter performance

Comparing bgw/bincode-task-inputs (20e09ad) with canary (b35713c)

Summary

✅ 17 untouched
⏩ 3 skipped1

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@bgw bgw force-pushed the bgw/bincode-value-cells branch from 3a3f0d7 to 5fd1a80 Compare November 29, 2025 01:48
@bgw bgw force-pushed the bgw/bincode-task-inputs branch from c713be6 to b68b533 Compare November 29, 2025 01:48
@bgw bgw added the CI Bypass Graphite Optimization Ignore Graphite CI optimizations, run the full CI suite. https://graphite.dev/docs/stacking-and-ci label Nov 29, 2025 — with Graphite App
@bgw bgw force-pushed the bgw/bincode-value-cells branch from 5fd1a80 to 946e3ce Compare December 1, 2025 05:16
@bgw bgw force-pushed the bgw/bincode-task-inputs branch 2 times, most recently from 7002c09 to b6aa3f1 Compare December 2, 2025 05:46
@bgw bgw force-pushed the bgw/bincode-value-cells branch 2 times, most recently from 3872eab to f31698a Compare December 2, 2025 19:57
@bgw bgw force-pushed the bgw/bincode-task-inputs branch from b6aa3f1 to 6642516 Compare December 2, 2025 19:57
@bgw bgw marked this pull request as ready for review December 4, 2025 16:45
@bgw bgw changed the title WIP: Migrate TaskInput serialization to bincode Migrate TaskInput serialization to bincode Dec 4, 2025
@bgw bgw changed the title Migrate TaskInput serialization to bincode Turbopack: bincode: Migrate TaskInput serialization to bincode Dec 4, 2025
/// Bincode encoding must be deterministic and compatible with [`Eq`] comparisons. If two
/// `TaskInput`s compare equal they must also encode to the same bytes.
pub trait TaskInput:
Send + Sync + Clone + Debug + PartialEq + Eq + Hash + TraceRawVcs + Encode + Decode<()>
Copy link
Member

Choose a reason for hiding this comment

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

That requirement is a bit weird, we didn't require that before.

Copy link
Member Author

Choose a reason for hiding this comment

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

It was implicitly required by the #[turbo_task::function] macros, so this just makes it explicit. You can leave this off, but then some of the error messages you get from rustc are a bit more confusing.

Copy link
Member

Choose a reason for hiding this comment

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

hmm... not sure...
With this panicing Encode implementations you do get a runtime error instead of a compile time error when you use e. g. TransientState within a serializable turbo_tasks::value...

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 agree it's not great, but there's no change to that in this PR. Prior to this, all of the Transient* types implemented panicking versions of serialize/deserialize. Now they implement panicking versions of encode/decode.


/// A thin wrapper around [`Either`] that implements the traits required by [`TaskInput`].
#[derive(Clone, Debug, PartialEq, Eq, Hash, TraceRawVcs)]
pub struct EitherTaskInput<L, R>(pub Either<L, R>);
Copy link
Member

Choose a reason for hiding this comment

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

Since we own TaskInput couldn't we implement TaskInput on Either directly?

Copy link
Member Author

@bgw bgw Dec 4, 2025

Choose a reason for hiding this comment

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

We can't implement Encode or Decode on TaskInput.

You can do it if you drop the Encode/Decode requirement from TaskInput (like your comment above mentions), but then it's a useless (and confusing) implementation of TaskInput because you can't use it with any #[turbo_task::function] macro.

In theory, you could use it with a transient function if we fixed the macro, but that's a pretty niche use-case.

Copy link
Member

Choose a reason for hiding this comment

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

ok I see... I wonder why bincode doesn't implement Encode/Decode on Either...

Copy link
Member Author

Choose a reason for hiding this comment

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

We could add it, but I'm trying to keep the changes to the bincode crate minimal unless/until we can start getting changes upstreamed.

bgw added a commit that referenced this pull request Dec 4, 2025
…coder` or `TurboBincodeDecoder` (#86633)

**Example usage:** See `CachedTaskType`​ in `turbopack/crates/turbo-tasks/src/backend.rs`​ on #86631 for an example of how this is used. That needs to call the encoder/decoder function pointer stored on `native_fn.arg_meta.bincode` with a concrete `TurboBincodeEncoder`/`TurboBincodeDecoder`, so it uses this trait.

---

For `SharedReference` (value cell contents) and `TaskInput`s, we need to generate function pointers to functions that can encode/decode the type.

Those generated functions cannot contain generics, so they require `TurboBincodeEncoder` or `TurboBincodeDecoder<()>` as arguments. That's okay because we don't want to use multiple encoders/decoders anyways, because that would be really bad for binary size and compilation time.

However, `SharedReference` and `TaskInput`s are wrapped in other container types that need to be encoded/decoded (e.g. `TypedSharedReference`. In these cases, the wrapper types need to be able to implement `Encode`/`Decode`, but can't because of the fixed `Encoder`/`Decoder` implementations. So this provides a way to do that, by panicking at runtime if the type doesn't match.

This uses the `unty` crate which is part of the bincode project: https://github.com/bincode-org/unty
@bgw bgw force-pushed the bgw/bincode-task-inputs branch from 6642516 to 341a002 Compare December 4, 2025 22:33
@bgw bgw force-pushed the bgw/bincode-value-cells branch from f31698a to f3df104 Compare December 4, 2025 22:33
@bgw bgw force-pushed the bgw/bincode-task-inputs branch from 341a002 to 54b0277 Compare December 4, 2025 23:11
@bgw bgw force-pushed the bgw/bincode-value-cells branch from f3df104 to 3c1dec4 Compare December 4, 2025 23:11
@bgw bgw requested a review from a team December 4, 2025 23:13
@bgw bgw force-pushed the bgw/bincode-value-cells branch from 3c1dec4 to e02768f Compare December 4, 2025 23:57
@bgw bgw force-pushed the bgw/bincode-task-inputs branch from 54b0277 to f239b74 Compare December 4, 2025 23:57
@bgw bgw force-pushed the bgw/bincode-value-cells branch from e02768f to eff6ef5 Compare December 5, 2025 01:49
@bgw bgw force-pushed the bgw/bincode-task-inputs branch from f239b74 to 017d09c Compare December 5, 2025 01:50
@bgw bgw force-pushed the bgw/bincode-value-cells branch 2 times, most recently from b5738dd to ac1c898 Compare December 5, 2025 02:18
@bgw bgw force-pushed the bgw/bincode-task-inputs branch from 017d09c to 80d851d Compare December 5, 2025 02:18
@sokra sokra changed the base branch from bgw/bincode-value-cells to graphite-base/86631 December 5, 2025 06:36
sokra pushed a commit that referenced this pull request Dec 5, 2025
…86338)

This PR switches `turbo-tasks-backend` to use `bincode` instead of serde for cell contents. To keep PR size manageable, `TaskInput`s are still serialized using serde and a compatibility shim. The next PR fixes that.

See #86631 for benchmark numbers.
@sokra sokra force-pushed the bgw/bincode-task-inputs branch from 80d851d to b789bff Compare December 5, 2025 06:37
@sokra sokra force-pushed the graphite-base/86631 branch from ac1c898 to b35713c Compare December 5, 2025 06:37
@graphite-app graphite-app bot changed the base branch from graphite-base/86631 to canary December 5, 2025 06:38
@sokra sokra force-pushed the bgw/bincode-task-inputs branch from b789bff to 20e09ad Compare December 5, 2025 06:38
@sokra sokra merged commit 85c3f2e into canary Dec 5, 2025
282 of 289 checks passed
Copy link
Member

sokra commented Dec 5, 2025

Merge activity

@sokra sokra deleted the bgw/bincode-task-inputs branch December 5, 2025 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI Bypass Graphite Optimization Ignore Graphite CI optimizations, run the full CI suite. https://graphite.dev/docs/stacking-and-ci created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants