Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concept: test mode for Playwright and similar integration tools #52520

Merged
merged 6 commits into from
Aug 14, 2023

Conversation

dvoytenko
Copy link
Member

@dvoytenko dvoytenko commented Jul 10, 2023

An experimental test mode that enables integration tests to mock server-side fetch requests in Playwright tests.

For explanation on how this works, see the next-playwright/README.md.

Copy link
Member

@codybrouwers codybrouwers left a comment

Choose a reason for hiding this comment

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

Looks great Dima! My only concern is about using a local server. I think this means that this setup would only work for local testing right? That means how we use Playwright currently by running the tests against the Vercel preview deployments would not be properly mocked.

Would it be possible to instead of running a local server, to run the same logic in a Next.js api endpoint so that you could run the tests against anywhere the app is deployed?

Something like this where @next/playwright exports handlers for all the supported HTTP methods:

// app/api/next-test/route.ts
export * from '@next/playwright'

This would probably also mean requiring a config field in next.config.js to specify what the route name is for mocks.

packages/next-playwright/README.md Outdated Show resolved Hide resolved
@dvoytenko
Copy link
Member Author

Looks great Dima! My only concern is about using a local server. I think this means that this setup would only work for local testing right? That means how we use Playwright currently by running the tests against the Vercel preview deployments would not be properly mocked.

Would it be possible to instead of running a local server, to run the same logic in a Next.js api endpoint so that you could run the tests against anywhere the app is deployed?

Something like this where @next/playwright exports handlers for all the supported HTTP methods:

// app/api/next-test/route.ts
export * from '@next/playwright'

This would probably also mean requiring a config field in next.config.js to specify what the route name is for mocks.

I kept it intentionally local-only for now. The concept could definitely be expanded to a remote case, but request interception is quite dangerous, so I'd rather first confirm that local case makes sense. So, currently this is for "integration testing" only and not for e2e.

Copy link
Contributor

@mrmckeb mrmckeb left a comment

Choose a reason for hiding this comment

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

This is looking great @dvoytenko! Well designed and I'm sure this is something that people are going to be excited about.

packages/next-playwright/src/next-fixture.ts Outdated Show resolved Hide resolved
packages/next-playwright/src/next-fixture.ts Outdated Show resolved Hide resolved
packages/next-playwright/src/next-fixture.ts Outdated Show resolved Hide resolved
packages/next-playwright/README.md Outdated Show resolved Hide resolved
packages/next-playwright/tsconfig.json Outdated Show resolved Hide resolved
import { test } from '@next/playwright'

test('/product/shoe', async ({ page, next }) => {
next.mockFetchJson('http://my-db/product/shoe', {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could URL support paths without the full URL? When applicable.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm. Not sure I see a value immediately. Would that be for a local API route access?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ping on this to clarify.

packages/next-playwright/src/next-worker-fixture.ts Outdated Show resolved Hide resolved
@philwolstenholme
Copy link
Contributor

philwolstenholme commented Jul 12, 2023

Rather than build something custom or specific to Next (e.g. next.mockFetchJson) an alternative idea would be to integrate with Mock Service Worker which already has a large userbase/community and is widely-liked. An upcoming major release uses the Request and Response API that developers will be familiar with from Fetch to write mocked APIs.

Investigation into using MSW with the app directory is happening in the MSW repo, but it's looking like support from Next.js would be required to overcome Next's patching of fetch affecting MSW: mswjs/msw#1644.

@dvoytenko
Copy link
Member Author

dvoytenko commented Jul 13, 2023

Rather than build something custom or specific to Next (e.g. next.mockFetchJson) an alternative idea would be to integrate with Mock Service Worker which already has a large userbase/community and is widely-liked. An upcoming major release uses the Request and Response API that developers will be familiar with from Fetch to write mocked APIs.

Investigation into using MSW with the app directory is happening in the MSW repo, but it's looking like support from Next.js would be required to overcome Next's patching of fetch affecting MSW: mswjs/msw#1644.

I'll remove the mockFetch API from this PR - that's just an example. The goal of this PR is to enable other APIs. I think this API directly enables mswjs/msw#1644. With these changes it should be fairly straightforward to layer MSW on top.

@dvoytenko
Copy link
Member Author

Rather than build something custom or specific to Next (e.g. next.mockFetchJson) an alternative idea would be to integrate with Mock Service Worker which already has a large userbase/community and is widely-liked. An upcoming major release uses the Request and Response API that developers will be familiar with from Fetch to write mocked APIs.

Investigation into using MSW with the app directory is happening in the MSW repo, but it's looking like support from Next.js would be required to overcome Next's patching of fetch affecting MSW: mswjs/msw#1644.

PTAL the prototype for MSW. I described the example in the README.md, or see the implementation in the msw.ts.

Copy link
Member

@timneutkens timneutkens left a comment

Choose a reason for hiding this comment

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

This looks good to me, left a few comments but they're all minor stuff 👍

@philwolstenholme
Copy link
Contributor

@kettanaito have you seen this? Looks like it could partially allow MSW to be used with Next's app router, although via a test mode so it wouldn't address the use case of using MSW with development mode for offline development, mocking a yet-to-be-created backend API, etc. What are your thoughts as the MSW maintainer?

import {
type RequestHandler,
type MockedResponse,
MockedRequest,
Copy link
Contributor

Choose a reason for hiding this comment

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

This is generally an internal constructor so beware that it's going to be removed in the next major release. Prefer regular Fetch API Request and Response instances when that happens.

referrer,
referrerPolicy,
} = request
const mockedRequest = new MockedRequest(new URL(request.url), {
Copy link
Contributor

Choose a reason for hiding this comment

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

You should be able to pass the request instance directly as the value of the second argument to MockedRequest. Afaik, the RequestInit and Request objects are compatible.

})
let isPassthrough = false
let mockedResponse: MockedResponse | undefined
await handleRequest(
Copy link
Contributor

Choose a reason for hiding this comment

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

I understand the intention here but you're tapping into deep internals of MSW. Functions like handleRequest are not meant for consumption so they may change and disappear at any moment without triggering a major release bump.

I'd really rather Next exposed us the correct way to initiate MSW in Node.js (it should work in the browser without issues already) instead of reverting the request flow here. It'd be easier for both teams.

Copy link
Member Author

Choose a reason for hiding this comment

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

Isn't this API publicly exported? I agree that this API is not convenient, but maybe MSW could export a better API for such cases?

Copy link
Contributor

Choose a reason for hiding this comment

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

@dvoytenko, this kind of API has a place to be, agreed. We haven't explored this kind of programmatic usage of MSW, and I dare say it's still rather low-level intention-wise. We may consider treating this as a public API in the future, but with a clear indication that it's rather advanced usage most people won't need.

@kettanaito
Copy link
Contributor

kettanaito commented Jul 17, 2023

@philwolstenholme, thanks for mentioning me here.

I've looked through some of the proposed packages, and while I can see some practical use of these abstractions, I think they approach the problem from the wrong angle. They disregard most of the MSW's benefits by tapping into "next.onFetch()" directly instead of letting the framework/browser do its thing and relying on the worker to intercept that traffic. The same is true about Node.js, although these changes don't address that in any way.

And you can see how the request flow is twisted, requiring you to tap into deep internals like MockedRequest and handleRequest, which is not a reliable way to build abstractions over MSW. The thing is, if we follow the regular request flow, no such abstractions may even be necessary, and that's the beauty of this whole thing.

Overall, things like route.fulfill is precisely what I'm encouraging both framework authors and framework users to avoid. As far as I remember, MSW works well in the browser while developing Next.js projects, no changes are needed here to develop or test it, including in Playwright. Enable it within the app, load that app during the test, and, perhaps, introduce a fixture or two for quicker access to window.msw to do things like runtime handlers and others.

The problem of Node.js support is far more complicated and requires some guidance from the Next.js team. From what I've seen, Next.js is forcefully restores the native fetch, cutting any possibility for tools like Nock or MSW to work. I trust the team has the best intentions in mind when doing that, but there isn't any way for third-parties to patch that same fetch, and so both user bases are deprived of a familiar experience.

@dvoytenko
Copy link
Member Author

@philwolstenholme, thanks for mentioning me here.

I've looked through some of the proposed packages, and while I can see some practical use of these abstractions, I think they approach the problem from the wrong angle. They disregard most of the MSW's benefits by tapping into "next.onFetch()" directly instead of letting the framework/browser do its thing and relying on the worker to intercept that traffic. The same is true about Node.js, although these changes don't address that in any way.

I want to make sure we pursue the same kind of test. I think what mswjs/msw#1644 (comment) is trying to achieve is use Next as a test runner, or at least running in the same VM as Next. That could certainly be useful. But:

  1. Next workers get in a way, while not giving much value to a test. So, I wonder, if Next could allow workers to be disabled for testing purposes.
  2. Indeed, Next sometimes restores the native fetch (only in next dev IIUC). Maybe Next could stop doing that (I think the restoration was due to patching from React DEV). Or maybe a test with MSW should patch fetch before Next does? Or alternatively, maybe test should use Next's instrumentation?

What this PR is trying to solve for is an integration test. Here, the test runs in a separate VM from both the server (next dev or next start) and client (playwright). Among the nuances here:

  1. The Next is running in its most standalone form: with workers, precompiled to avoid test flakiness, etc. The only exception - it intercepts external fetches.
  2. This test intercepts both server and client fetches at the same time, using the same (more or less) API. Including the same MSW handlers.
  3. Playwright executes tests in parallel, thus I'm not sure how can a global fetch interceptor distinguish between different tests. In other words, it'd seem, if used naively, specific test handlers would collide via MSW? I expect the same issue would be with Nock and other similar APIs.

@dvoytenko
Copy link
Member Author

I've added #53409 for a broader discussion on Next as a test runner, to keep it separate from this specific exploration.

packages/next/package.json Outdated Show resolved Hide resolved
@ijjk
Copy link
Member

ijjk commented Aug 1, 2023

Allow CI Workflow Run

  • approve CI run for commit: 5037604

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

@dvoytenko dvoytenko marked this pull request as ready for review August 2, 2023 23:33
@dvoytenko dvoytenko force-pushed the dimavoytenko/next-playwright branch 3 times, most recently from 7b1818f to 29ac9bb Compare August 9, 2023 18:15
ztanner
ztanner previously approved these changes Aug 9, 2023
Copy link
Member

@ztanner ztanner left a comment

Choose a reason for hiding this comment

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

some small nits. This is very cool, nice work!

@dvoytenko
Copy link
Member Author

@ztanner responded. ptal. I changed "install" API slightly.

@dvoytenko dvoytenko requested review from ijjk and ztanner August 10, 2023 16:44
@dvoytenko dvoytenko force-pushed the dimavoytenko/next-playwright branch 2 times, most recently from 411afbd to 15f8e52 Compare August 11, 2023 23:53
@ijjk
Copy link
Member

ijjk commented Aug 12, 2023

Stats from current PR

Default Build (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary dvoytenko/next.js dimavoytenko/next-playwright Change
buildDuration 42.5s 39.8s -2.7s
buildDurationCached 17.1s 16s -1.1s
nodeModulesSize 138 MB 138 MB ⚠️ +84.4 kB
nextStartRea..uration (ms) 928ms 900ms -28ms
Client Bundles (main, webpack) Overall decrease ✓
vercel/next.js canary dvoytenko/next.js dimavoytenko/next-playwright Change
146-HASH.js gzip 26 kB 26 kB ⚠️ +1 B
3f784ff6-HASH.js gzip 50.5 kB 50.5 kB -1 B
957.HASH.js gzip 181 B 181 B
framework-HASH.js gzip 45.2 kB 45.2 kB
main-app-HASH.js gzip 223 B 224 B ⚠️ +1 B
main-HASH.js gzip 28.6 kB 28.6 kB -13 B
webpack-HASH.js gzip 1.7 kB 1.7 kB -2 B
Overall change 152 kB 152 kB -14 B
Legacy Client Bundles (polyfills)
vercel/next.js canary dvoytenko/next.js dimavoytenko/next-playwright Change
polyfills-HASH.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages Overall increase ⚠️
vercel/next.js canary dvoytenko/next.js dimavoytenko/next-playwright Change
_app-HASH.js gzip 194 B 195 B ⚠️ +1 B
_error-HASH.js gzip 182 B 181 B -1 B
amp-HASH.js gzip 504 B 506 B ⚠️ +2 B
css-HASH.js gzip 322 B 322 B
dynamic-HASH.js gzip 2.5 kB 2.5 kB ⚠️ +1 B
edge-ssr-HASH.js gzip 256 B 256 B
head-HASH.js gzip 349 B 349 B
hooks-HASH.js gzip 369 B 369 B
image-HASH.js gzip 4.3 kB 4.29 kB -6 B
index-HASH.js gzip 256 B 255 B -1 B
link-HASH.js gzip 2.61 kB 2.62 kB ⚠️ +4 B
routerDirect..HASH.js gzip 311 B 311 B
script-HASH.js gzip 384 B 383 B -1 B
withRouter-HASH.js gzip 306 B 308 B ⚠️ +2 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Overall change 12.9 kB 12.9 kB ⚠️ +1 B
Client Build Manifests
vercel/next.js canary dvoytenko/next.js dimavoytenko/next-playwright Change
_buildManifest.js gzip 484 B 484 B
Overall change 484 B 484 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary dvoytenko/next.js dimavoytenko/next-playwright Change
index.html gzip 511 B 512 B ⚠️ +1 B
link.html gzip 523 B 526 B ⚠️ +3 B
withRouter.html gzip 504 B 506 B ⚠️ +2 B
Overall change 1.54 kB 1.54 kB ⚠️ +6 B
Edge SSR bundle Size Overall increase ⚠️
vercel/next.js canary dvoytenko/next.js dimavoytenko/next-playwright Change
edge-ssr.js gzip 90.4 kB 90.4 kB ⚠️ +14 B
page.js gzip 150 kB 150 kB ⚠️ +9 B
Overall change 240 kB 240 kB ⚠️ +23 B
Middleware size Overall decrease ✓
vercel/next.js canary dvoytenko/next.js dimavoytenko/next-playwright Change
middleware-b..fest.js gzip 625 B 621 B -4 B
middleware-r..fest.js gzip 151 B 149 B -2 B
middleware.js gzip 22.7 kB 22.7 kB ⚠️ +1 B
edge-runtime..pack.js gzip 1.83 kB 1.83 kB
Overall change 25.3 kB 25.2 kB -5 B

Diffs

Diff for page.js

Diff too large to display

Diff for middleware-b..-manifest.js
@@ -7,96 +7,96 @@ self.__BUILD_MANIFEST = {
     "static/BUILD_ID/_ssgManifest.js",
   ],
   rootMainFiles: [
-    "static/chunks/webpack-3a300bf4b86e8076.js",
-    "static/chunks/3f784ff6-ba8bd3a7fc446932.js",
-    "static/chunks/146-1b3d8bb61b72cc53.js",
-    "static/chunks/main-app-4c8dbecd557a4a56.js",
+    "static/chunks/webpack-84e2fed1c10027c9.js",
+    "static/chunks/3c4a14c2-6dfa75d5fd78bae0.js",
+    "static/chunks/385-c0cff367da9b8af0.js",
+    "static/chunks/main-app-780a3e80fbef591a.js",
   ],
   pages: {
     "/": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
-      "static/chunks/pages/index-5a9e4ec00bfafbc2.js",
+      "static/chunks/main-3e624884144f0f0b.js",
+      "static/chunks/pages/index-73df98d3a1be236b.js",
     ],
     "/_app": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
-      "static/chunks/pages/_app-91d8c3ff9c7bf801.js",
+      "static/chunks/main-3e624884144f0f0b.js",
+      "static/chunks/pages/_app-23093975f6ced067.js",
     ],
     "/_error": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
-      "static/chunks/pages/_error-08a44b197424cae9.js",
+      "static/chunks/main-3e624884144f0f0b.js",
+      "static/chunks/pages/_error-4ca4370d0bbe83ba.js",
     ],
     "/amp": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
-      "static/chunks/pages/amp-c60b5b8d65dfefd0.js",
+      "static/chunks/main-3e624884144f0f0b.js",
+      "static/chunks/pages/amp-505f7f9211f38807.js",
     ],
     "/css": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
+      "static/chunks/main-3e624884144f0f0b.js",
       "static/css/ded6b86ab9cc0a1f.css",
-      "static/chunks/pages/css-518c25e89b2cd519.js",
+      "static/chunks/pages/css-23408853b5e40486.js",
     ],
     "/dynamic": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
-      "static/chunks/pages/dynamic-016bb68f063d0de6.js",
+      "static/chunks/main-3e624884144f0f0b.js",
+      "static/chunks/pages/dynamic-4bdfb291d10e0ef9.js",
     ],
     "/edge-ssr": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
-      "static/chunks/pages/edge-ssr-3757a8c74450da23.js",
+      "static/chunks/main-3e624884144f0f0b.js",
+      "static/chunks/pages/edge-ssr-ccab9dbcc96e4bb5.js",
     ],
     "/head": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
-      "static/chunks/pages/head-fcfeabb0945b0179.js",
+      "static/chunks/main-3e624884144f0f0b.js",
+      "static/chunks/pages/head-f21c43059c8bbd7a.js",
     ],
     "/hooks": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
-      "static/chunks/pages/hooks-88ec926ebc8afbca.js",
+      "static/chunks/main-3e624884144f0f0b.js",
+      "static/chunks/pages/hooks-7616f499d44fca80.js",
     ],
     "/image": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
-      "static/chunks/pages/image-a0e5ca3c2cfaf2d3.js",
+      "static/chunks/main-3e624884144f0f0b.js",
+      "static/chunks/pages/image-a2752f53838301ec.js",
     ],
     "/link": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
-      "static/chunks/pages/link-bf2ae3d845d7b091.js",
+      "static/chunks/main-3e624884144f0f0b.js",
+      "static/chunks/pages/link-ff0bde2677856bd6.js",
     ],
     "/routerDirect": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
-      "static/chunks/pages/routerDirect-1eba4a3229eda770.js",
+      "static/chunks/main-3e624884144f0f0b.js",
+      "static/chunks/pages/routerDirect-cec833fa9cf12265.js",
     ],
     "/script": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
-      "static/chunks/pages/script-e8f78c780ec0a0af.js",
+      "static/chunks/main-3e624884144f0f0b.js",
+      "static/chunks/pages/script-8716ecf7b7f48d24.js",
     ],
     "/withRouter": [
-      "static/chunks/webpack-3a300bf4b86e8076.js",
+      "static/chunks/webpack-84e2fed1c10027c9.js",
       "static/chunks/framework-114cbddc60b679b8.js",
-      "static/chunks/main-b616ec7b50556b98.js",
-      "static/chunks/pages/withRouter-acfbd1015ce713b8.js",
+      "static/chunks/main-3e624884144f0f0b.js",
+      "static/chunks/pages/withRouter-492ac907cdffd546.js",
     ],
   },
   ampFirstPages: [],
Diff for middleware-r..-manifest.js
@@ -1,2 +1,2 @@
 self.__REACT_LOADABLE_MANIFEST =
-  '{"dynamic.js -> ../components/hello":{"id":6957,"files":["static/chunks/957.ad2a32717f99d799.js"]}}';
+  '{"dynamic.js -> ../components/hello":{"id":3440,"files":["static/chunks/440.4ecfcf525bb65d08.js"]}}';
Diff for middleware.js
@@ -2,7 +2,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [826],
   {
-    /***/ 3522: /***/ (
+    /***/ 4705: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -22,7 +22,7 @@
       __webpack_require__.r(middleware_namespaceObject);
       __webpack_require__.d(middleware_namespaceObject, {
         default: () => middleware,
-      }); // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/globals.js
+      }); // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/globals.js
 
       async function registerInstrumentation() {
         if (
@@ -92,7 +92,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         // Eagerly fire instrumentation hook to make the startup faster.
         void ensureInstrumentationRegistered();
       }
-      enhanceGlobals(); //# sourceMappingURL=globals.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/error.js
+      enhanceGlobals(); //# sourceMappingURL=globals.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/error.js
 
       class PageSignatureError extends Error {
         constructor({ page }) {
@@ -119,7 +119,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
   Read more: https://nextjs.org/docs/messages/middleware-parse-user-agent
   `);
         }
-      } //# sourceMappingURL=error.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/utils.js
+      } //# sourceMappingURL=error.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/utils.js
 
       /**
        * Converts a Node.js IncomingHttpHeaders object to a Headers object. Any
@@ -254,7 +254,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
             }
           );
         }
-      } //# sourceMappingURL=utils.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/fetch-event.js
+      } //# sourceMappingURL=utils.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/fetch-event.js
 
       const responseSymbol = Symbol("response");
       const passThroughSymbol = Symbol("passThrough");
@@ -300,7 +300,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
             page: this.sourcePage,
           });
         }
-      } //# sourceMappingURL=fetch-event.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/i18n/detect-domain-locale.js
+      } //# sourceMappingURL=fetch-event.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/i18n/detect-domain-locale.js
 
       function detectDomainLocale(domainItems, hostname, detectedLocale) {
         if (!domainItems) return;
@@ -326,7 +326,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
             return item;
           }
         }
-      } //# sourceMappingURL=detect-domain-locale.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/remove-trailing-slash.js
+      } //# sourceMappingURL=detect-domain-locale.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/remove-trailing-slash.js
 
       /**
        * Removes the trailing slash for a given route or page path. Preserves the
@@ -336,7 +336,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
        *   - `/` -> `/`
        */ function removeTrailingSlash(route) {
         return route.replace(/\/$/, "") || "/";
-      } //# sourceMappingURL=remove-trailing-slash.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/parse-path.js
+      } //# sourceMappingURL=remove-trailing-slash.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/parse-path.js
 
       /**
        * Given a path this function will find the pathname, query and hash and return
@@ -364,7 +364,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
           query: "",
           hash: "",
         };
-      } //# sourceMappingURL=parse-path.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/add-path-prefix.js
+      } //# sourceMappingURL=parse-path.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/add-path-prefix.js
 
       /**
        * Adds the provided prefix to the given path. It first ensures that the path
@@ -375,7 +375,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         }
         const { pathname, query, hash } = parsePath(path);
         return "" + prefix + pathname + query + hash;
-      } //# sourceMappingURL=add-path-prefix.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/add-path-suffix.js
+      } //# sourceMappingURL=add-path-prefix.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/add-path-suffix.js
 
       /**
        * Similarly to `addPathPrefix`, this function adds a suffix at the end on the
@@ -387,7 +387,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         }
         const { pathname, query, hash } = parsePath(path);
         return "" + pathname + suffix + query + hash;
-      } //# sourceMappingURL=add-path-suffix.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/path-has-prefix.js
+      } //# sourceMappingURL=add-path-suffix.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/path-has-prefix.js
 
       /**
        * Checks if a given path starts with a given prefix. It ensures it matches
@@ -401,7 +401,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         }
         const { pathname } = parsePath(path);
         return pathname === prefix || pathname.startsWith(prefix + "/");
-      } //# sourceMappingURL=path-has-prefix.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/add-locale.js
+      } //# sourceMappingURL=path-has-prefix.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/add-locale.js
 
       /**
        * For a given path and a locale, if the locale is given, it will prefix the
@@ -420,7 +420,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         }
         // Add the locale prefix to the path.
         return addPathPrefix(path, "/" + locale);
-      } //# sourceMappingURL=add-locale.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/format-next-pathname-info.js
+      } //# sourceMappingURL=add-locale.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/format-next-pathname-info.js
 
       function formatNextPathnameInfo(info) {
         let pathname = addLocale(
@@ -444,7 +444,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
             ? addPathSuffix(pathname, "/")
             : pathname
           : removeTrailingSlash(pathname);
-      } //# sourceMappingURL=format-next-pathname-info.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/get-hostname.js
+      } //# sourceMappingURL=format-next-pathname-info.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/get-hostname.js
 
       /**
        * Takes an object with a hostname property (like a parsed URL) and some
@@ -464,7 +464,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
           hostname = parsed.hostname;
         } else return;
         return hostname.toLowerCase();
-      } //# sourceMappingURL=get-hostname.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/i18n/normalize-locale-path.js
+      } //# sourceMappingURL=get-hostname.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/i18n/normalize-locale-path.js
 
       /**
        * For a pathname that may include a locale from a list of locales, it
@@ -494,7 +494,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
           pathname,
           detectedLocale,
         };
-      } //# sourceMappingURL=normalize-locale-path.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/remove-path-prefix.js
+      } //# sourceMappingURL=normalize-locale-path.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/remove-path-prefix.js
 
       /**
        * Given a path and a prefix it will remove the prefix when it exists in the
@@ -528,7 +528,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         // If the path without the prefix doesn't start with a `/` we need to add it
         // back to the path to make sure it's a valid path.
         return "/" + withoutPrefix;
-      } //# sourceMappingURL=remove-path-prefix.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/get-next-pathname-info.js
+      } //# sourceMappingURL=remove-path-prefix.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/get-next-pathname-info.js
 
       function getNextPathnameInfo(pathname, options) {
         var _options_nextConfig;
@@ -579,7 +579,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
               : info.pathname;
         }
         return info;
-      } //# sourceMappingURL=get-next-pathname-info.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/next-url.js
+      } //# sourceMappingURL=get-next-pathname-info.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/next-url.js
 
       const REGEX_LOCALHOST_HOSTNAME =
         /(?!^https?:\/\/)(127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}|\[::1\]|localhost)/;
@@ -806,8 +806,8 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         }
       } //# sourceMappingURL=next-url.js.map
 
-      // EXTERNAL MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/compiled/@edge-runtime/cookies/index.js
-      var _edge_runtime_cookies = __webpack_require__(2698); // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/cookies.js // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/request.js
+      // EXTERNAL MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/compiled/@edge-runtime/cookies/index.js
+      var _edge_runtime_cookies = __webpack_require__(5327); // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/cookies.js // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/request.js
       //# sourceMappingURL=cookies.js.map
 
       const INTERNALS = Symbol("internal request");
@@ -884,7 +884,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         get url() {
           return this[INTERNALS].url;
         }
-      } //# sourceMappingURL=request.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/response.js
+      } //# sourceMappingURL=request.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/response.js
 
       const response_INTERNALS = Symbol("internal response");
       const REDIRECTS = new Set([301, 302, 303, 307, 308]);
@@ -983,7 +983,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
             headers,
           });
         }
-      } //# sourceMappingURL=response.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/relativize-url.js
+      } //# sourceMappingURL=response.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/relativize-url.js
 
       /**
        * Given a URL as a string and a base URL it will make the URL relative
@@ -996,7 +996,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         return relative.protocol + "//" + relative.host === origin
           ? relative.toString().replace(origin, "")
           : relative.toString();
-      } //# sourceMappingURL=relativize-url.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/client/components/app-router-headers.js
+      } //# sourceMappingURL=relativize-url.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/client/components/app-router-headers.js
 
       const RSC = "RSC";
       const ACTION = "Next-Action";
@@ -1018,7 +1018,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         [NEXT_ROUTER_STATE_TREE],
         [NEXT_ROUTER_PREFETCH],
       ];
-      const NEXT_RSC_UNION_QUERY = "_rsc"; //# sourceMappingURL=app-router-headers.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/internal-utils.js
+      const NEXT_RSC_UNION_QUERY = "_rsc"; //# sourceMappingURL=app-router-headers.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/internal-utils.js
 
       const INTERNAL_QUERY_NAMES = [
         "__nextFallback",
@@ -1046,7 +1046,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
           }
         }
         return isStringUrl ? instance.toString() : instance;
-      } //# sourceMappingURL=internal-utils.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/app-paths.js
+      } //# sourceMappingURL=internal-utils.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/shared/lib/router/utils/app-paths.js
 
       /**
        * Normalizes an app route so it represents the actual request path. Essentially
@@ -1097,7 +1097,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
        * Since this function is used on full urls it checks `?` for searchParams handling.
        */ function normalizeRscPath(pathname, enabled) {
         return enabled ? pathname.replace(/\.rsc($|\?)/, "$1") : pathname;
-      } //# sourceMappingURL=app-paths.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/lib/constants.js
+      } //# sourceMappingURL=app-paths.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/lib/constants.js
 
       const NEXT_QUERY_PARAM_PREFIX = "nxtP";
       const PRERENDER_REVALIDATE_HEADER = "x-prerender-revalidate";
@@ -1235,7 +1235,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         metadata: "__next_metadata__",
         metadataRoute: "__next_metadata_route__",
         metadataImageMeta: "__next_metadata_image_meta__",
-      }; //# sourceMappingURL=constants.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/adapters/reflect.js
+      }; //# sourceMappingURL=constants.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/adapters/reflect.js
 
       class ReflectAdapter {
         static get(target, prop, receiver) {
@@ -1254,7 +1254,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         static deleteProperty(target, prop) {
           return Reflect.deleteProperty(target, prop);
         }
-      } //# sourceMappingURL=reflect.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/adapters/headers.js
+      } //# sourceMappingURL=reflect.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/adapters/headers.js
 
       /**
        * @internal
@@ -1435,7 +1435,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         [Symbol.iterator]() {
           return this.entries();
         }
-      } //# sourceMappingURL=headers.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/adapters/request-cookies.js
+      } //# sourceMappingURL=headers.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/spec-extension/adapters/request-cookies.js
 
       /**
        * @internal
@@ -1568,7 +1568,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
             },
           });
         }
-      } //# sourceMappingURL=request-cookies.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/api-utils/index.js
+      } //# sourceMappingURL=request-cookies.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/api-utils/index.js
 
       /**
        * Parse cookies from the `headers` of request
@@ -1579,7 +1579,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
           if (!cookie) {
             return {};
           }
-          const { parse: parseCookieFn } = __webpack_require__(3554);
+          const { parse: parseCookieFn } = __webpack_require__(3340);
           return parseCookieFn(
             Array.isArray(cookie) ? cookie.join("; ") : cookie
           );
@@ -1638,7 +1638,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         if (SYMBOL_CLEARED_COOKIES in res) {
           return res;
         }
-        const { serialize } = __webpack_require__(3554);
+        const { serialize } = __webpack_require__(3340);
         const previous = res.getHeader("Set-Cookie");
         res.setHeader(`Set-Cookie`, [
           ...(typeof previous === "string"
@@ -1735,7 +1735,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
             });
           },
         });
-      } //# sourceMappingURL=index.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/async-storage/draft-mode-provider.js
+      } //# sourceMappingURL=index.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/async-storage/draft-mode-provider.js
 
       class DraftModeProvider {
         constructor(previewProps, req, cookies, mutableCookies) {
@@ -1788,7 +1788,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
             expires: new Date(0),
           });
         }
-      } //# sourceMappingURL=draft-mode-provider.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/async-storage/request-async-storage-wrapper.js
+      } //# sourceMappingURL=draft-mode-provider.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/async-storage/request-async-storage-wrapper.js
 
       function getHeaders(headers) {
         const cleaned = HeadersAdapter.from(headers);
@@ -1873,10 +1873,10 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
         },
       }; //# sourceMappingURL=request-async-storage-wrapper.js.map
 
-      // EXTERNAL MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/client/components/async-local-storage.js
-      var async_local_storage = __webpack_require__(6650); // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/client/components/request-async-storage.js
+      // EXTERNAL MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/client/components/async-local-storage.js
+      var async_local_storage = __webpack_require__(7705); // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/client/components/request-async-storage.js
       const requestAsyncStorage = (0,
-      async_local_storage /* createAsyncLocalStorage */.P)(); //# sourceMappingURL=request-async-storage.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/adapter.js
+      async_local_storage /* createAsyncLocalStorage */.P)(); //# sourceMappingURL=request-async-storage.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/adapter.js
 
       class NextRequestHint extends NextRequest {
         constructor(params) {
@@ -2138,14 +2138,14 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
           response: finalResponse,
           waitUntil: Promise.all(event[waitUntilSymbol]),
         };
-      } //# sourceMappingURL=adapter.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/exports/next-response.js // CONCATENATED MODULE: ./middleware.js
+      } //# sourceMappingURL=adapter.js.map // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/esm/server/web/exports/next-response.js // CONCATENATED MODULE: ./middleware.js
 
       // This file is for modularized imports for next/server to get fully-treeshaking.
       //# sourceMappingURL=next-response.js.map
 
       async function middleware() {
         return NextResponse.next();
-      } // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/build/webpack/loaders/next-middleware-loader.js?absolutePagePath=private-next-root-dir%2Fmiddleware.js&page=%2Fmiddleware&rootDir=%2Ftmp%2Fnext-statscPpsDi%2Fstats-app&matchers=&preferredRegion=&middlewareConfig=e30%3D!
+      } // CONCATENATED MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/dist/build/webpack/loaders/next-middleware-loader.js?absolutePagePath=private-next-root-dir%2Fmiddleware.js&page=%2Fmiddleware&rootDir=%2Ftmp%2Fnext-statscPpsDi%2Fstats-app&matchers=&preferredRegion=&middlewareConfig=e30%3D!
 
       const mod = { ...middleware_namespaceObject };
       const handler = mod.middleware || mod.default;
@@ -2169,7 +2169,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
       /***/
     },
 
-    /***/ 2698: /***/ (module) => {
+    /***/ 5327: /***/ (module) => {
       "use strict";
 
       var __defProp = Object.defineProperty;
@@ -2562,7 +2562,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
       /***/
     },
 
-    /***/ 3554: /***/ (module) => {
+    /***/ 3340: /***/ (module) => {
       "use strict";
       var __dirname = "/";
 
@@ -2692,7 +2692,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
       /***/
     },
 
-    /***/ 6650: /***/ (
+    /***/ 7705: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -2738,7 +2738,7 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`;
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
-    /******/ var __webpack_exports__ = __webpack_exec__(3522);
+    /******/ var __webpack_exports__ = __webpack_exec__(4705);
     /******/ (_ENTRIES =
       typeof _ENTRIES === "undefined" ? {} : _ENTRIES).middleware_middleware =
       __webpack_exports__;
Diff for edge-ssr.js

Diff too large to display

Diff for _buildManifest.js
@@ -1,28 +1,28 @@
 self.__BUILD_MANIFEST = {
   __rewrites: { afterFiles: [], beforeFiles: [], fallback: [] },
-  "/": ["static\u002Fchunks\u002Fpages\u002Findex-5a9e4ec00bfafbc2.js"],
-  "/_error": ["static\u002Fchunks\u002Fpages\u002F_error-08a44b197424cae9.js"],
-  "/amp": ["static\u002Fchunks\u002Fpages\u002Famp-c60b5b8d65dfefd0.js"],
+  "/": ["static\u002Fchunks\u002Fpages\u002Findex-73df98d3a1be236b.js"],
+  "/_error": ["static\u002Fchunks\u002Fpages\u002F_error-4ca4370d0bbe83ba.js"],
+  "/amp": ["static\u002Fchunks\u002Fpages\u002Famp-505f7f9211f38807.js"],
   "/css": [
     "static\u002Fcss\u002Fded6b86ab9cc0a1f.css",
-    "static\u002Fchunks\u002Fpages\u002Fcss-518c25e89b2cd519.js",
+    "static\u002Fchunks\u002Fpages\u002Fcss-23408853b5e40486.js",
   ],
   "/dynamic": [
-    "static\u002Fchunks\u002Fpages\u002Fdynamic-016bb68f063d0de6.js",
+    "static\u002Fchunks\u002Fpages\u002Fdynamic-4bdfb291d10e0ef9.js",
   ],
   "/edge-ssr": [
-    "static\u002Fchunks\u002Fpages\u002Fedge-ssr-3757a8c74450da23.js",
+    "static\u002Fchunks\u002Fpages\u002Fedge-ssr-ccab9dbcc96e4bb5.js",
   ],
-  "/head": ["static\u002Fchunks\u002Fpages\u002Fhead-fcfeabb0945b0179.js"],
-  "/hooks": ["static\u002Fchunks\u002Fpages\u002Fhooks-88ec926ebc8afbca.js"],
-  "/image": ["static\u002Fchunks\u002Fpages\u002Fimage-a0e5ca3c2cfaf2d3.js"],
-  "/link": ["static\u002Fchunks\u002Fpages\u002Flink-bf2ae3d845d7b091.js"],
+  "/head": ["static\u002Fchunks\u002Fpages\u002Fhead-f21c43059c8bbd7a.js"],
+  "/hooks": ["static\u002Fchunks\u002Fpages\u002Fhooks-7616f499d44fca80.js"],
+  "/image": ["static\u002Fchunks\u002Fpages\u002Fimage-a2752f53838301ec.js"],
+  "/link": ["static\u002Fchunks\u002Fpages\u002Flink-ff0bde2677856bd6.js"],
   "/routerDirect": [
-    "static\u002Fchunks\u002Fpages\u002FrouterDirect-1eba4a3229eda770.js",
+    "static\u002Fchunks\u002Fpages\u002FrouterDirect-cec833fa9cf12265.js",
   ],
-  "/script": ["static\u002Fchunks\u002Fpages\u002Fscript-e8f78c780ec0a0af.js"],
+  "/script": ["static\u002Fchunks\u002Fpages\u002Fscript-8716ecf7b7f48d24.js"],
   "/withRouter": [
-    "static\u002Fchunks\u002Fpages\u002FwithRouter-acfbd1015ce713b8.js",
+    "static\u002Fchunks\u002Fpages\u002FwithRouter-492ac907cdffd546.js",
   ],
   sortedPages: [
     "\u002F",
Diff for _app-HASH.js
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [888],
   {
-    /***/ 7108: /***/ function (
+    /***/ 6392: /***/ function (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/_app",
         function () {
-          return __webpack_require__(7858);
+          return __webpack_require__(7798);
         },
       ]);
       if (false) {
@@ -24,7 +24,7 @@
       return __webpack_require__((__webpack_require__.s = moduleId));
     };
     /******/ __webpack_require__.O(0, [774, 179], function () {
-      return __webpack_exec__(7108), __webpack_exec__(8272);
+      return __webpack_exec__(6392), __webpack_exec__(2866);
     });
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for _error-HASH.js
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [820],
   {
-    /***/ 2665: /***/ function (
+    /***/ 630: /***/ function (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/_error",
         function () {
-          return __webpack_require__(6647);
+          return __webpack_require__(8693);
         },
       ]);
       if (false) {
@@ -24,7 +24,7 @@
       return __webpack_require__((__webpack_require__.s = moduleId));
     };
     /******/ __webpack_require__.O(0, [888, 774, 179], function () {
-      return __webpack_exec__(2665);
+      return __webpack_exec__(630);
     });
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for amp-HASH.js
@@ -1,17 +1,17 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [216],
   {
-    /***/ 1571: /***/ function (
+    /***/ 1158: /***/ function (
       module,
       __unused_webpack_exports,
       __webpack_require__
     ) {
-      module.exports = __webpack_require__(2689);
+      module.exports = __webpack_require__(6691);
 
       /***/
     },
 
-    /***/ 4273: /***/ function (
+    /***/ 8255: /***/ function (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -19,7 +19,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/amp",
         function () {
-          return __webpack_require__(8260);
+          return __webpack_require__(2860);
         },
       ]);
       if (false) {
@@ -28,7 +28,7 @@
       /***/
     },
 
-    /***/ 2689: /***/ function (module, exports, __webpack_require__) {
+    /***/ 6691: /***/ function (module, exports, __webpack_require__) {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -44,8 +44,8 @@
       const _react = /*#__PURE__*/ _interop_require_default._(
         __webpack_require__(959)
       );
-      const _ampcontext = __webpack_require__(8128);
-      const _ampmode = __webpack_require__(9939);
+      const _ampcontext = __webpack_require__(9031);
+      const _ampmode = __webpack_require__(1358);
       function useAmp() {
         // Don't assign the context value to a variable to save bytes
         return (0, _ampmode.isInAmpMode)(
@@ -67,7 +67,7 @@
       /***/
     },
 
-    /***/ 8260: /***/ function (
+    /***/ 2860: /***/ function (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -84,7 +84,7 @@
         /* harmony export */
       });
       /* harmony import */ var next_amp__WEBPACK_IMPORTED_MODULE_0__ =
-        __webpack_require__(1571);
+        __webpack_require__(1158);
       /* harmony import */ var next_amp__WEBPACK_IMPORTED_MODULE_0___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_amp__WEBPACK_IMPORTED_MODULE_0__
@@ -108,7 +108,7 @@
       return __webpack_require__((__webpack_require__.s = moduleId));
     };
     /******/ __webpack_require__.O(0, [888, 774, 179], function () {
-      return __webpack_exec__(4273);
+      return __webpack_exec__(8255);
     });
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for css-HASH.js
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [706],
   {
-    /***/ 2094: /***/ function (
+    /***/ 2714: /***/ function (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/css",
         function () {
-          return __webpack_require__(4405);
+          return __webpack_require__(3041);
         },
       ]);
       if (false) {
@@ -18,7 +18,7 @@
       /***/
     },
 
-    /***/ 4405: /***/ function (
+    /***/ 3041: /***/ function (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -28,7 +28,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(1527);
       /* harmony import */ var _css_module_css__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(262);
+        __webpack_require__(1885);
       /* harmony import */ var _css_module_css__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           _css_module_css__WEBPACK_IMPORTED_MODULE_1__
@@ -48,7 +48,7 @@
       /***/
     },
 
-    /***/ 262: /***/ function (module) {
+    /***/ 1885: /***/ function (module) {
       // extracted by mini-css-extract-plugin
       module.exports = { helloWorld: "css_helloWorld__aUdUq" };
 
@@ -61,7 +61,7 @@
       return __webpack_require__((__webpack_require__.s = moduleId));
     };
     /******/ __webpack_require__.O(0, [774, 888, 179], function () {
-      return __webpack_exec__(2094);
+      return __webpack_exec__(2714);
     });
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for dynamic-HASH.js
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [739],
   {
-    /***/ 8484: /***/ function (
+    /***/ 9816: /***/ function (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/dynamic",
         function () {
-          return __webpack_require__(6928);
+          return __webpack_require__(4020);
         },
       ]);
       if (false) {
@@ -18,7 +18,7 @@
       /***/
     },
 
-    /***/ 6361: /***/ function (module, exports, __webpack_require__) {
+    /***/ 1133: /***/ function (module, exports, __webpack_require__) {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -45,7 +45,7 @@
         __webpack_require__(959)
       );
       const _loadable = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(9460)
+        __webpack_require__(2304)
       );
       const isServerSide = "object" === "undefined";
       // Normalize loader to return the module as form { default: Component } for `React.lazy`.
@@ -146,7 +146,7 @@
       /***/
     },
 
-    /***/ 9472: /***/ function (
+    /***/ 5689: /***/ function (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -173,7 +173,7 @@
       /***/
     },
 
-    /***/ 9460: /***/ function (
+    /***/ 2304: /***/ function (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -215,7 +215,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       const _react = /*#__PURE__*/ _interop_require_default._(
         __webpack_require__(959)
       );
-      const _loadablecontext = __webpack_require__(9472);
+      const _loadablecontext = __webpack_require__(5689);
       function resolve(obj) {
         return obj && obj.default ? obj.default : obj;
       }
@@ -450,7 +450,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       /***/
     },
 
-    /***/ 6928: /***/ function (
+    /***/ 4020: /***/ function (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -466,7 +466,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(1527);
       /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(5620);
+        __webpack_require__(4438);
       /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_dynamic__WEBPACK_IMPORTED_MODULE_1__
@@ -475,11 +475,11 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       const DynamicHello = next_dynamic__WEBPACK_IMPORTED_MODULE_1___default()(
         () =>
           __webpack_require__
-            .e(/* import() */ 957)
-            .then(__webpack_require__.bind(__webpack_require__, 6957)),
+            .e(/* import() */ 440)
+            .then(__webpack_require__.bind(__webpack_require__, 3440)),
         {
           loadableGenerated: {
-            webpack: () => [/*require.resolve*/ 6957],
+            webpack: () => [/*require.resolve*/ 3440],
           },
         }
       );
@@ -506,12 +506,12 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       /***/
     },
 
-    /***/ 5620: /***/ function (
+    /***/ 4438: /***/ function (
       module,
       __unused_webpack_exports,
       __webpack_require__
     ) {
-      module.exports = __webpack_require__(6361);
+      module.exports = __webpack_require__(1133);
 
       /***/
     },
@@ -522,7 +522,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       return __webpack_require__((__webpack_require__.s = moduleId));
     };
     /******/ __webpack_require__.O(0, [774, 888, 179], function () {
-      return __webpack_exec__(8484);
+      return __webpack_exec__(9816);
     });
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for edge-ssr-HASH.js
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [800],
   {
-    /***/ 9835: /***/ function (
+    /***/ 7423: /***/ function (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/edge-ssr",
         function () {
-          return __webpack_require__(5727);
+          return __webpack_require__(5365);
         },
       ]);
       if (false) {
@@ -18,7 +18,7 @@
       /***/
     },
 
-    /***/ 5727: /***/ function (
+    /***/ 5365: /***/ function (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -48,7 +48,7 @@
       return __webpack_require__((__webpack_require__.s = moduleId));
     };
     /******/ __webpack_require__.O(0, [888, 774, 179], function () {
-      return __webpack_exec__(9835);
+      return __webpack_exec__(7423);
     });
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for head-HASH.js
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [645],
   {
-    /***/ 3365: /***/ function (
+    /***/ 4278: /***/ function (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/head",
         function () {
-          return __webpack_require__(7566);
+          return __webpack_require__(2218);
         },
       ]);
       if (false) {
@@ -18,7 +18,7 @@
       /***/
     },
 
-    /***/ 7566: /***/ function (
+    /***/ 2218: /***/ function (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -34,7 +34,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(1527);
       /* harmony import */ var next_head__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(2022);
+        __webpack_require__(4469);
       /* harmony import */ var next_head__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_head__WEBPACK_IMPORTED_MODULE_1__
@@ -68,12 +68,12 @@
       /***/
     },
 
-    /***/ 2022: /***/ function (
+    /***/ 4469: /***/ function (
       module,
       __unused_webpack_exports,
       __webpack_require__
     ) {
-      module.exports = __webpack_require__(4778);
+      module.exports = __webpack_require__(6647);
 
       /***/
     },
@@ -84,7 +84,7 @@
       return __webpack_require__((__webpack_require__.s = moduleId));
     };
     /******/ __webpack_require__.O(0, [774, 888, 179], function () {
-      return __webpack_exec__(3365);
+      return __webpack_exec__(4278);
     });
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for hooks-HASH.js
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [757],
   {
-    /***/ 5315: /***/ function (
+    /***/ 5046: /***/ function (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/hooks",
         function () {
-          return __webpack_require__(4692);
+          return __webpack_require__(7075);
         },
       ]);
       if (false) {
@@ -18,7 +18,7 @@
       /***/
     },
 
-    /***/ 4692: /***/ function (
+    /***/ 7075: /***/ function (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -79,7 +79,7 @@
       return __webpack_require__((__webpack_require__.s = moduleId));
     };
     /******/ __webpack_require__.O(0, [774, 888, 179], function () {
-      return __webpack_exec__(5315);
+      return __webpack_exec__(5046);
     });
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for image-HASH.js
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [358],
   {
-    /***/ 1552: /***/ function (
+    /***/ 4070: /***/ function (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/image",
         function () {
-          return __webpack_require__(9773);
+          return __webpack_require__(8213);
         },
       ]);
       if (false) {
@@ -18,7 +18,7 @@
       /***/
     },
 
-    /***/ 1657: /***/ function (module, exports, __webpack_require__) {
+    /***/ 226: /***/ function (module, exports, __webpack_require__) {
       "use strict";
       /* __next_internal_client_entry_do_not_use__  cjs */
       Object.defineProperty(exports, "__esModule", {
@@ -37,15 +37,15 @@
       );
       const _reactdom = __webpack_require__(422);
       const _head = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(4778)
+        __webpack_require__(6647)
       );
-      const _getimgprops = __webpack_require__(6609);
-      const _imageconfig = __webpack_require__(2262);
-      const _imageconfigcontext = __webpack_require__(5786);
-      const _warnonce = __webpack_require__(601);
-      const _routercontext = __webpack_require__(1038);
+      const _getimgprops = __webpack_require__(9819);
+      const _imageconfig = __webpack_require__(103);
+      const _imageconfigcontext = __webpack_require__(1106);
+      const _warnonce = __webpack_require__(2909);
+      const _routercontext = __webpack_require__(8703);
       const _imageloader = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(4746)
+        __webpack_require__(4254)
       );
       // This is replaced by webpack define plugin
       const configEnv = {
@@ -370,7 +370,7 @@
       /***/
     },
 
-    /***/ 6609: /***/ function (
+    /***/ 9819: /***/ function (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -386,9 +386,9 @@
           return getImgProps;
         },
       });
-      const _warnonce = __webpack_require__(601);
-      const _imageblursvg = __webpack_require__(8880);
-      const _imageconfig = __webpack_require__(2262);
+      const _warnonce = __webpack_require__(2909);
+      const _imageblursvg = __webpack_require__(6721);
+      const _imageconfig = __webpack_require__(103);
       const VALID_LOADING_VALUES =
         /* unused pure expression or super */ null && [
           "lazy",
@@ -757,7 +757,7 @@
       /***/
     },
 
-    /***/ 8880: /***/ function (__unused_webpack_module, exports) {
+    /***/ 6721: /***/ function (__unused_webpack_module, exports) {
       "use strict";
       /**
        * A shared function, used on both client and server, to generate a SVG blur placeholder.
@@ -812,7 +812,7 @@
       /***/
     },
 
-    /***/ 6715: /***/ function (
+    /***/ 981: /***/ function (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -839,11 +839,11 @@
         },
       });
       const _interop_require_default = __webpack_require__(8266);
-      const _getimgprops = __webpack_require__(6609);
-      const _warnonce = __webpack_require__(601);
-      const _imagecomponent = __webpack_require__(1657);
+      const _getimgprops = __webpack_require__(9819);
+      const _warnonce = __webpack_require__(2909);
+      const _imagecomponent = __webpack_require__(226);
       const _imageloader = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(4746)
+        __webpack_require__(4254)
       );
       const unstable_getImgProps = (imgProps) => {
         (0, _warnonce.warnOnce)(
@@ -875,7 +875,7 @@
       /***/
     },
 
-    /***/ 4746: /***/ function (__unused_webpack_module, exports) {
+    /***/ 4254: /***/ function (__unused_webpack_module, exports) {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -910,7 +910,7 @@
       /***/
     },
 
-    /***/ 9773: /***/ function (
+    /***/ 8213: /***/ function (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -931,8 +931,8 @@
 
       // EXTERNAL MODULE: ./node_modules/.pnpm/[email protected]/node_modules/react/jsx-runtime.js
       var jsx_runtime = __webpack_require__(1527);
-      // EXTERNAL MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/image.js
-      var next_image = __webpack_require__(1577);
+      // EXTERNAL MODULE: ./node_modules/.pnpm/[email protected][email protected]/node_modules/next/image.js
+      var next_image = __webpack_require__(73);
       var image_default = /*#__PURE__*/ __webpack_require__.n(next_image); // CONCATENATED MODULE: ./pages/nextjs.png
       /* harmony default export */ var nextjs = {
         src: "/_next/static/media/nextjs.cae0b805.png",
@@ -962,12 +962,12 @@
       /***/
     },
 
-    /***/ 1577: /***/ function (
+    /***/ 73: /***/ function (
       module,
       __unused_webpack_exports,
       __webpack_require__
     ) {
-      module.exports = __webpack_require__(6715);
+      module.exports = __webpack_require__(981);
 
       /***/
     },
@@ -978,7 +978,7 @@
       return __webpack_require__((__webpack_require__.s = moduleId));
     };
     /******/ __webpack_require__.O(0, [774, 888, 179], function () {
-      return __webpack_exec__(1552);
+      return __webpack_exec__(4070);
     });
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for index-HASH.js
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [405],
   {
-    /***/ 7410: /***/ function (
+    /***/ 4990: /***/ function (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/",
         function () {
-          return __webpack_require__(1464);
+          return __webpack_require__(9075);
         },
       ]);
       if (false) {
@@ -18,7 +18,7 @@
       /***/
     },
 
-    /***/ 1464: /***/ function (
+    /***/ 9075: /***/ function (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -44,7 +44,7 @@
       return __webpack_require__((__webpack_require__.s = moduleId));
     };
     /******/ __webpack_require__.O(0, [888, 774, 179], function () {
-      return __webpack_exec__(7410);
+      return __webpack_exec__(4990);
     });
     /******/ 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([
   [644],
   {
-    /***/ 1794: /***/ function (
+    /***/ 8959: /***/ function (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/link",
         function () {
-          return __webpack_require__(597);
+          return __webpack_require__(4375);
         },
       ]);
       if (false) {
@@ -18,7 +18,7 @@
       /***/
     },
 
-    /***/ 6804: /***/ function (module, exports) {
+    /***/ 4909: /***/ function (module, exports) {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -86,7 +86,7 @@
       /***/
     },
 
-    /***/ 5565: /***/ function (module, exports, __webpack_require__) {
+    /***/ 8406: /***/ function (module, exports, __webpack_require__) {
       "
Post job cleanup.
[command]/usr/bin/git version
git version 2.41.0
Temporarily overriding HOME='/home/runner/work/_temp/86d800df-47ca-4ba4-b7f8-7d6a163a065a' before making global git config changes
Adding repository directory to the temporary git global config as a safe directory
[command]/usr/bin/git config --global --add safe.directory /home/runner/work/next.js/next.js
[command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand
[command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :"
[command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader
http.https://github.com/.extraheader
[command]/usr/bin/git config --local --unset-all http.https://github.com/.extraheader
[command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :"
Cleaning up orphan processes
Commit: 4cd7a183960e55d1ddb12e7d81674ba2e320e53b

@kettanaito
Copy link
Contributor

@dvoytenko,

Next workers get in a way, while not giving much value to a test. So, I wonder, if Next could allow workers to be disabled for testing purposes.

I'm concerned testing purposes is only one surface of many people can employ API mocking. I wonder how this change can enable local development against mocks, for example. I suppose Next's workers is a new thing, I've not heard of them before, neither did they get in the way of anything. We can also compose workers, which is one route to consider to keep Next's internals and enable third-party workers in the browser, like that from MSW.

Indeed, Next sometimes restores the native fetch (only in next dev IIUC). Maybe Next could stop doing that (I think the restoration was due to patching from React DEV). Or maybe a test with MSW should patch fetch before Next does? Or alternatively, maybe test should use Next's instrumentation?

I'd like to see more confirmation that fetch patching is a dev-only thing. Next doesn't provide any surface to implement side-effects, like patching fetch before its internal store/restore logic of fetch. If it did, that could've been one way to approach it.

Regarding the instrumentation hook, you can read mswjs/msw#1644 (comment) to see why the instrumentation hook in its current shape doesn't give us the right means to integrate API mocking into Next.

What this PR is trying to solve for is an integration test. Here, the test runs in a separate VM from both the server (next dev or next start) and client (playwright).

As I (hopefully) said above, this is a noble effort. But it has very little to do with integrating MSW in Next. It may have some changes that would act as prerequisites to make that integration possible, but at its core, this change only addresses a specific way of testing. I don't believe that implementing or turning Next into a test runner solves the underlying core problem—Next has no means of establishing process-wide server-side side-effects, like module patches for API mocking.

With my very limited understanding, the actual changes that'd help are:

  1. Next shouldn't meddle with fetch. If it does, it should give third-parties the means to meddle with fetch and don't override their functionality, like it does with MSW by overriding global fetch with whichever state of that function Next stored when it was bootstrapped.
  2. Next should give the means to apply process-wide server-side side-effects. A very simple example of this is the means to introduce a global shared between server/client processes (Next has a few Node.js processes running concurrently, and they don't share the context, naturally). This is not possible in Next as of now, and this is perhaps the biggest blocker.

Once those are addressed, tools like MSW can safely work without having to rely on the testing capabilities of Next at all. We are talking about a broader integration here, and it's that nature that allowed MSW to be reused across the entire stack instead of being bound and partially supported for a single-purpose use cases.

@dvoytenko
Copy link
Member Author

@kettanaito I took the liberty to re-post your requirements in the #53409

@kodiakhq kodiakhq bot merged commit 88ad471 into vercel:canary Aug 14, 2023
90 of 93 checks passed
kodiakhq bot pushed a commit that referenced this pull request Aug 15, 2023
Followup on the #52520. The enhancements:

1. Both `fetch` and `http(s)` APIs are intercepted in the testmode.
2. The loopback mode that would allow the test-side to use any fetch-mocking library of their choice.
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 29, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
CI approved Approve running CI for fork locked type: next
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants