msw icon indicating copy to clipboard operation
msw copied to clipboard

Support Next.js 13 (App Router)

Open kettanaito opened this issue 1 year ago • 100 comments

Scope

Adds a new behavior

Compatibility

  • [ ] This is a breaking change

Feature description

As of 1.2.2, MSW does not support the newest addition to Next.js being the App directory in Next.js 13. I'd like to use this issue to track the implementation of this support since many people are currently blocked by this.

Prior investigation

I looked into this a month ago and discovered that there's difficulty to establish any process-wide logic in Next.js 13 due to the way Next itself is implemented internally. Very briefly, Next keeps two Node.js processes while it runs:

  1. One process is persistent (opened once on the same port and running there).
  2. Another process spawns at a random port, does its job, and gets killed.

This process separation is evident when you issue a hot reload to any of the layout components.

Reloading a root layout

When a hot update is issued through the root layout component, we can see the persistent Node.js process update. I don't remember any changes to the intermittent process.

Reloading a nested layout

When a hot update is issued for a nested layout, i.e. a certain page's layout, the intermittent process gets killed, then a new one spawns at its place (on a different port), likely evaluates the update, and keeps pending.

What's the problem with the two processes then?

In Node.js, MSW works by patching global Node modules, like http and https. Those patches must persist in the process in order for the mocking to work. Since Next uses this fluctuating process structure, it introduces two main issues:

  1. We cannot establish the global module patches once since Next's functionality is split across two different, unrelated processes. Usually, the module patching should be done somewhere in your root layout since it's conceptually guaranteed to be the highest hot update point no matter where you are in your application. That's not the case in Next.js, as the processes evaluating the root layout and individual page layouts are completely different, and the module patches in one process do not affect the other.
  2. Since the page-oriented (fluctuating) process constantly gets killed and spawned at random ports, I don't see a way to establish module patching in it at all to support API mocking for server-side requests issued in individual pages (or their layouts).

What happens next?

MSW is blocked by the way Next.js is implemented internally. I don't like this state of things but that's what we get—patching Node globals is not the most reliable of things and it will, inevitably, differ across frameworks depending on how those frameworks are implemented.

I would pretty much like for the Next.js team to assist us in this. There's very little we can do on the MSW's side to resolve this. In fact, I believe there's nothing we can do until there's a way to establish a module patch in Node.js for all Next.js processes at the same time.

If you're blocked by this, reach out to the Next.js team on Twitter or other platforms, letting them know this issue is important. I hope that would help the team prioritize it and help us resolve it together. Thanks.

kettanaito avatar Jun 23 '23 09:06 kettanaito

It's also worth mentioning that this is not an issue with MSW. The library works in an extremely straightforward way in any browser or Node.js process. This issue is here simply to track the progress of this Next.js integration as many developers have voiced their concerns and questions about it.

kettanaito avatar Jun 23 '23 09:06 kettanaito

Does it need to have a persistent process for some reason or can it just repatch each time if there was a place to do it?

How did this work in Next.js Pages for API routes since _app.js doesn’t run for those?

A workaround is to just have a module that’s imported from every layout, page or custom route that uses data. Such as in a share api layer:


import “./patch-msw”;

export async function getData() {
  return fetch(…);
}

We’re considering a global place to inject it too but not sure the process can be guaranteed to live on forever. That’s pretty limited.

sebmarkbage avatar Jun 23 '23 13:06 sebmarkbage

@sebmarkbage, hi 👋 Thank you for reaching out!

Does it need to have a persistent process for some reason or can it just repatch each time if there was a place to do it?

It can certainly re-patch on every hot update. We are doing precisely that for Remix and Svelte examples. I had trouble doing that with Next due to those dual processes running (a Node.js patch on the root layout doesn't apply to the server-side logic of individual pages since those seem to be evaluated in a different process).

How did this work in Next.js Pages for API routes since _app.js doesn’t run for those?

I suppose it was fine because the main use case is to support client- and server-side development of Next apps, which came down to:

  • Intercepting requests in the browser (that's handled by the service worker);
  • Intercepting server-side requests made in getServerSideProps (and similar), and these abode by _app.js so it was enough to patch Node modules there to enable API mocking.

Right now, the second one doesn't work due to the lack of _app.js alternative in Next 13.

A workaround is to just have a module that’s imported from every layout

I have two concerns regarding this workaround:

  1. This implies the imported code has some deduplication built-in so that the Node globals aren't patched by each individual import. We do have this logic present but it's still worth mentioning.
  2. This has negative DX implications since the developer is, effectively, forced to collocate API mocking with individual resource-fetching areas (i.e. a specific page route), which degrades overall experience when compared to the very same setups in other frameworks.

We’re considering a global place to inject it too but not sure the process can be guaranteed to live on forever. That’s pretty limited.

It would be really nice to have _app.js, or similar—a designated place to establish client/server-side logic once and have it encapsulate all the future layouts/routes/etc. I understand this may be challenging based on Next's internals at the moment. If I can help somehow, just let me know.

kettanaito avatar Jun 23 '23 13:06 kettanaito

We already have instrumentation.ts https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation

There's an idea to expand that to include more features and to have different variants for the different module/scopes processes so that it can patch the module in the Server Components scoped, SSR scope and Client side scope.

Not sure if what is already there might be sufficient for your use case.

sebmarkbage avatar Jun 23 '23 17:06 sebmarkbage

Thanks, @sebmarkbage. At first glance, it looks like it could work. I will give it a try in a new Next example repository and let you know.

kettanaito avatar Jun 24 '23 09:06 kettanaito

@sebmarkbage, do you know if the instrumentation.ts hook supports ESM? It doesn't seem like it does:

// ./with-next/instrumentation.ts
export async function register() {
  // I've tried a regular top-level import, it matters not.
  const { server } = await import('./mocks/node')
  server.listen()
}

Module not found: Package path ./node is not exported from package /new-examples/examples/with-next/node_modules/msw (see exports field in /new-examples/examples/with-next/node_modules/msw/package.json)
> 1 | import { setupServer } from 'msw/node'
  2 | import { handlers } from './handlers'
  3 | 
  4 | export const server = setupServer(...handlers)

While with-next/node_modules/msw/package.json is:

{
  "exports": {
    "./node": {
      "browser": null,
      "types": "./lib/node/index.d.ts",
      "require": "./lib/node/index.js",
      "import": "./lib/node/index.mjs",
      "default": "./lib/node/index.mjs"
    },
  }
}

My first hunch was maybe the hook runs in the browser too, thus it's unable to resolve the exports['./node'].browser import field. But it doesn't seem to run in the browser.

This is really odd because MSW doesn't ship ESM exclusively, it comes as a dual CJS/ESM package, which means it has both the exports (for modern browsers) and root-level stubs like ./browser/package.json and ./node/package.json to support older ones.

You can reproduce this behavior in here: https://github.com/mswjs/examples-new/pull/7

What can be going wrong here?

kettanaito avatar Jun 26 '23 11:06 kettanaito

I strongly suspect Next.js is trying to resolve the browser field when encountering Node.js export paths in instrumentation.ts for some reason. If I provide a dummy stub for exports['./node'].browser, it will pass the module resolution.

Moreover, it then fails on a bunch of other 4th-party imports (msw -> @mswjs/interceptors) that have Node.js-specific export fields. This doesn't seem right.

Do I have to mark the imported module in instrumentation.ts as Node.js-oriented so Next.js knows about it? Looks redundant, given instrumentation.ts is for server instance bootstrapping. It shouldn't even be reaching out to the browser exports field.

kettanaito avatar Jun 26 '23 11:06 kettanaito

Hi :wave:

Firstly, thanks for the fantastic library.

I don't know if this is something that you're already aware of, but MSW doesn't appear to work with Next.js 13 full stop, not just with the app directory. It doesn't appear to work with the pages directory; the official example is also broken.

Is the issue with the pages directory encapsulated by this issue too?

Thanks 😄

louis-young avatar Jun 29 '23 11:06 louis-young

Hi, @louis-young. Thanks for reaching out.

I'm aware of the library not working with Next 13 in general (I believe there are also issues on Next 12; I suspect there were some architectural changes merged to Next 12 prior to Next 13 which may have caused this).

However, to reduce an already complex issue, I'd like to track the App router exclusively here and approach it first. As I understand, the App router is the main direction the Next is going to take, and pages remain there mainly for backward compatibility reasons (the App router is far too big an endeavor not to be the main thing in 1-2 releases, eventually).

kettanaito avatar Jun 29 '23 15:06 kettanaito

Thanks for getting back to me so quickly.

That's fair enough and a very reasonable and pragmatic approach, I just wanted to check that it was something that you were aware of.

Thanks again and keep up the great work 😄

louis-young avatar Jun 29 '23 15:06 louis-young

@louis-young We have msw working with the pages directory just fine. Make sure you have this code at the top of your pages component's index.tsx:

if (process.env.NEXT_PUBLIC_API_MOCKING === 'enabled') {
  // eslint-disable-next-line global-require
  require('@/mocks');
}

jmcpeak avatar Jun 30 '23 16:06 jmcpeak

You can create a client side only component that you include in your root layout.tsx.

"use client";
import { useEffect } from "react";

export const MSWComponent = () => {
  useEffect(() => {
    if (process.env.NEXT_PUBLIC_API_MOCKING === "enabled") {
      // eslint-disable-next-line global-require
      require("~/mocks");
    }
  }, []);

  return null;
};

edit: sorry I mistakenly assumed not working "full stop" meant client side too

darrylblake avatar Jul 01 '23 03:07 darrylblake

@darrylblake, that would work but the main thing we're trying to solve here is the server-side integration.

kettanaito avatar Jul 01 '23 12:07 kettanaito

Any news on this? Is there any ongoing task?

mcAnastasiou avatar Jul 04 '23 16:07 mcAnastasiou

Any news on this? Is there any ongoing task?

And to add on, can we help at all? Anything you can point us to look at or do need help from the Vercel team?

jmcpeak avatar Jul 04 '23 16:07 jmcpeak

Our team absolutely loves msw and we really want to use it on a green field we started last week

jmcpeak avatar Jul 04 '23 16:07 jmcpeak

Module resolution in instrumentation.ts

The latest ongoing task is to figure out why instrumentation.ts in Next.js tries to resolve browser export fields when the module is meant for Node only: https://github.com/mswjs/msw/issues/1644#issuecomment-1607241798.

If someone has the time to look into this, I'd be thankful. This import behavior doesn't seem right, and I don't believe it's related to MSW (we've tested out exports quite well).

Getting help from the Vercel team would be great since they know like no one else about the internals of their framework. But I understand and respect their limited availability, we're all doing our best effort here.

Minimal example

Even without the module resolution issue, what everybody can help with is the following:

  1. Create a new Next App directory project.
  2. Add the instrumentation.ts as specified in the docs.
  3. Simply print out the process id console.log(process.pid).
  4. Create a root layout and a page layout.
  5. Make a change to the root layout component (to trigger HMR). Does the console from instrumentation get printed to the terminal? If yes, remember the process id.
  6. Make a change to the page layout (to trigger HMR). Does the same message get printed? Are the process IDs the same?

If we can gather more details on how instrumentation.ts actually behaves across this split process structure of Next, we'd be unlocking new potential solutions to the problem at hand. Thanks!

kettanaito avatar Jul 05 '23 08:07 kettanaito

Even without the module resolution issue, what everybody can help with is the following:

  1. Create a new Next App directory project.
  2. Add the instrumentation.ts as specified in the docs.
  3. Simply print out the process id console.log(process.pid).
  4. Create a root layout and a page layout.
  5. Make a change to the root layout component (to trigger HMR). Does the console from instrumentation get printed to the terminal? If yes, remember the process id.
  6. Make a change to the page layout (to trigger HMR). Does the same message get printed? Are the process IDs the same?

I tried this in an existing project opted into instrumentation and found that HMR changes didn't trigger the console log.

I saw one console log with pid 28076 after the "compiled client and server successfully in XXX ms" event in the Next console output.

I then two more console logs, both with pid 28089 after the "compiling /instrumentation (client and server)" event and after some of my app's routes had also compiled.

Finally I saw a console log with pid undefined after the "compiling /src/middleware (client and server)" event.

I made a bunch of layout.tsx and page.tsx changes, but none of them seemed to prompt a console log.

philwolstenholme avatar Jul 06 '23 12:07 philwolstenholme

@kettanaito Next.js maintainer here, I did not verify your use case but what I think is going on there is that:

  • the instrumentation file gets compiled for the server, in which case it should look for the module/main field
  • but then it also gets compiled for the edge runtime, in which case the resolution order would be: edged, worker, browser, module, main (I think). So perhaps adding another field for edged would solve your error.

feedthejim avatar Jul 06 '23 13:07 feedthejim

Hi, @feedthejim. Thanks for the info. I've tried logging process.env.NEXT_RUNTIME in the register and it always prints nodejs. I assume that the function only runs for Node.js in my case since I don't have an edge function set up and I'm loading a page in the browser.

I see webpack in the stack trace. Are there any flags I can use to debug what webpack is trying to do regarding module resolution here?

kettanaito avatar Jul 06 '23 15:07 kettanaito

On instrumentation hook

@sebmarkbage, I don't think the instrumentation hook works in a way to implement Node.js module patching. An example below.

Consider the following root layout and a page component (both fetching data on the server because that's the point):

// app/layout.tsx
async function getRootData() {
  return fetch('https://api.example.com/root').catch(() => null)
}

export default async function RootLayout() {
  const data = await getRootData()
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  )
}
// app/page.tsx
async function getData() {
  const response = await fetch('https://example.com')
  return response.text()
}

export default async function Home() {
  const data = await getData()

  return <p>{data}</p>
}

And the instrumentation.ts hook as follows:

export async function register() {
  console.log('[i] %s %d', process.env.NEXT_RUNTIME, process.pid)

  globalThis.fetch = new Proxy(globalThis.fetch, {
    apply(target, thisArg, args) {
      console.log('[i] fetch', args)
      return Reflect.apply(target, thisArg, args)
    },
  })
}

Expected behavior

The instrumentation hook run as a part of the Node.js server of Next and executes its register function as promised. In the function, we're patching global fetch, which is a very rough emulation of what MSW will do in order to intercept your data fetching requests in server components. Then, I expect both fetch() calls in layout.tsx and page.tsx to print a console log statement that we've added in the instrumentation hook.

Current behavior

Nothing gets printed. I suspected that patching fetch in particular is problematic because Next itself patches fetch and that probably happens after the instrumentation hook runs. Can you please confirm that?

I do mention fetch specifically because global variables do get shared between the hook and the server components (while they are on the same process, more on that below).

On a relevant note, I've checked how this dual Node.js process architecture is handled with the instrumentation hook, and it seems like this:

[i] nodejs 5800
[layout] nodejs 5842
[page] nodejs 5842
[layout] nodejs 5800
[page] nodejs 5800

i comes from the instrumentation hook; layout and page logs come from respective server components.

It seems that the hook shares at least 1 process with the layout and the page (5800, this is random) while each of those server components also have the second Node.js process that is not shared with the hook (5842, also random).

I don't have the insight to say what is that random port used for. If by any chance Next evaluates the components as a part of that port process, then I don't see how MSW or any other third-party can establish any server-side side effects to enable features like API mocking—there's no shared process to apply those effects in.

Can we have something like entry.server.tsx in Remix?

kettanaito avatar Jul 06 '23 15:07 kettanaito

Even without the module resolution issue, what everybody can help with is the following:

  1. Create a new Next App directory project.
  2. Add the instrumentation.ts as specified in the docs.
  3. Simply print out the process id console.log(process.pid).
  4. Create a root layout and a page layout.
  5. Make a change to the root layout component (to trigger HMR). Does the console from instrumentation get printed to the terminal? If yes, remember the process id.
  6. Make a change to the page layout (to trigger HMR). Does the same message get printed? Are the process IDs the same?

I tried this in an existing project opted into instrumentation and found that HMR changes didn't trigger the console log.

I saw one console log with pid 28076 after the "compiled client and server successfully in XXX ms" event in the Next console output.

I then two more console logs, both with pid 28089 after the "compiling /instrumentation (client and server)" event and after some of my app's routes had also compiled.

Finally I saw a console log with pid undefined after the "compiling /src/middleware (client and server)" event.

I made a bunch of layout.tsx and page.tsx changes, but none of them seemed to prompt a console log.

Exactly the same results - 13.4.9

jmcpeak avatar Jul 07 '23 13:07 jmcpeak

Can we have something like entry.server.tsx in Remix?

https://remix.run/docs/en/main/file-conventions/entry.server

Should we start asking Vercel for a similar feature? (You know they don't want the folks over at Remix "one 'upping" them)

jmcpeak avatar Jul 07 '23 13:07 jmcpeak

Does anyone created an issue on the NextJS repo?

Would love to have the link so I can +1

watch-janick avatar Jul 10 '23 18:07 watch-janick

It seems for the dev server at least, https://github.com/vercel/next.js/blob/673107551c3466da6d68660b37198eee0a2c85f7/packages/next/src/server/dev/next-dev-server.ts#L1759 is restoring fetch to the un-patched original.

When running with yarn start, mocks come through for me using the with-msw example but upgraded to msw v1.2.2 except for the first page load. At this point the mock server hasn't started yet.

Update: This was running nextjs 13.4.9 in classic mode (as apposed to app mode) so maybe not as much help here.

Jaesin avatar Jul 11 '23 04:07 Jaesin

In app mode (https://github.com/Jaesin/with-msw-app), I added some logging and patched node_modules/next/dist/server/dev/next-dev-server.js to see what it changes as far as the global fetch.

  1. [Process 1] Starts.
  2. [Process 2] next.config is loaded.
  3. [Process 2] next-dev-server.js starts and backs up global fetch.
  4. [Process 2] src/instrumentation.ts loads and registers mocks right away.
  5. [Process 1] next.config is loaded.
  6. Incoming request.
  7. [Process 3] next.config is loaded.
  8. [Process 3] next-dev-server.js backs up global fetch (fresh nodejs copy).
  9. [Process 3] src/instrumentation.ts loads and registers mocks right away.
  10. [Process 3] next-dev-server.js Restores fetch from the msw patched fetch to the backed up version (fresh nodejs copy)
  11. [Process 3] page.tsx Loads url and fails to get mocked data.
  12. HMR
  13. Incoming request.
  14. [Process 3] next-dev-server.js Restores fetch from nextjs Patched version to the backup (fresh nodejs copy).
  15. [Process 3] page.tsx Loads url and fails to get mocked data. ...

13-15 repeat for subsequent requests.

It looks to me that the third process that handles all of the requests is instantiating next-dev-server before it initializes instrumentation and that is causing the default node fetch object to be backed up for restore before every request is handled.

>>> node_modules/.bin/next dev
Main process id: 96616
- warn Port 3000 is in use, trying 3001 instead.
- ready started server on 0.0.0.0:3001, url: http://localhost:3001
- info Loaded env from .../with-msw-app/.env.development
[next.config] File loaded. Process id: 96617.
- warn You have enabled experimental feature (instrumentationHook) in next.config.js.
- warn Experimental features are not covered by semver, and may cause unexpected or broken application behavior. Use at your own risk.

[next-dev-server.js] Backing up Fetch. Process ID: 96617. global.fetch PropertyDescriptors:
{
  length: { value: 0, writable: false, enumerable: false, configurable: true },
  name: { value: '', writable: false, enumerable: false, configurable: true }
}
- event compiled client and server successfully in 221 ms (20 modules)
- wait compiling...
- wait compiling /instrumentation (client and server)...
- event compiled client and server successfully in 321 ms (64 modules)
[instrumentation] File loaded. Process id: 96617.
[instrumentation][register] Registering mocks. Process ID: ${process.pid}
[instrumentation][register] API mocking enabled, starting.
Mocks initialized
[next.config] File loaded. Process id: 96616.

### Incoming Request ###

- wait compiling /page (client and server)...
- event compiled client and server successfully in 980 ms (495 modules)
[next.config] File loaded. Process id: 96620.
[next-dev-server.js] Backing up Fetch. Process ID: 96620. global.fetch PropertyDescriptors:
{
  length: { value: 0, writable: false, enumerable: false, configurable: true },
  name: { value: '', writable: false, enumerable: false, configurable: true }
}
- wait compiling /instrumentation (client and server)...
- event compiled successfully in 120 ms (273 modules)
[instrumentation] File loaded. Process id: 96620.
[instrumentation][register] Registering mocks. Process ID: ${process.pid}
[instrumentation][register] API mocking enabled, starting.
Mocks initialized
[next-dev-server.js] Restoring Fetch. Process ID: 96620. global.fetch PropertyDescriptors:
{
  length: { value: 2, writable: false, enumerable: false, configurable: true },
  name: { value: '', writable: false, enumerable: false, configurable: true },
  prototype: { value: {}, writable: true, enumerable: false, configurable: false },
  [Symbol(isPatchedModule)]: {
    value: true,
    writable: false,
    enumerable: true,
    configurable: true
  }
}
[next-dev-server.js] New global.fetch PropertyDescriptors:
{
  length: { value: 0, writable: false, enumerable: false, configurable: true },
  name: { value: '', writable: false, enumerable: false, configurable: true }
}
[Home] Process ID: 96620
[Home] Failed to load mock data

-  ┌ GET / 200 in 602ms
   │
   └──── GET http://my.backend/book 404 in 127ms (cache: MISS)

### HMR ###

- wait compiling...
- event compiled successfully in 234 ms (306 modules)

### Incoming Request ###

[next-dev-server.js] Restoring Fetch. Process ID: 96620. global.fetch PropertyDescriptors:
{
  length: { value: 2, writable: false, enumerable: false, configurable: true },
  name: { value: '', writable: false, enumerable: false, configurable: true },
  __nextGetStaticStore: {
    value: [Function (anonymous)],
    writable: true,
    enumerable: true,
    configurable: true
  },
  __nextPatched: { value: true, writable: true, enumerable: true, configurable: true }
}
[next-dev-server.js] New global.fetch PropertyDescriptors:
{
  length: { value: 0, writable: false, enumerable: false, configurable: true },
  name: { value: '', writable: false, enumerable: false, configurable: true }
}
[Home] Process ID: 96620
[Home] Failed to load mock data

-  ┌ GET / 200 in 187ms
   │
   └──── GET http://my.backend/book 404 in 88ms (cache: MISS)

### Incoming Request ###

[next-dev-server.js] Restoring Fetch. Process ID: 96620. global.fetch PropertyDescriptors:
{
  length: { value: 2, writable: false, enumerable: false, configurable: true },
  name: { value: '', writable: false, enumerable: false, configurable: true },
  __nextGetStaticStore: {
    value: [Function (anonymous)],
    writable: true,
    enumerable: true,
    configurable: true
  },
  __nextPatched: { value: true, writable: true, enumerable: true, configurable: true }
}
[next-dev-server.js] New global.fetch PropertyDescriptors:
{
  length: { value: 0, writable: false, enumerable: false, configurable: true },
  name: { value: '', writable: false, enumerable: false, configurable: true }
}
[Home] Process ID: 96620
[Home] Failed to load mock data

-  ┌ GET / 200 in 61ms
   │
   └──── GET http://my.backend/book 404 in 8ms (cache: MISS)

Running in prod mode:

  1. [Process 1] Starts.
  2. [Process 1] next.config is loaded.
  3. [Process 2] next.config is loaded.
  4. [Process 2] src/instrumentation.ts loads and registers mocks right away.
  5. [Process 3] next.config is loaded.
  6. [Process 3] src/instrumentation.ts loads and registers mocks right away.
  7. Incoming request.
  8. [Process 3] page.tsx Loads url and mocks load successfully!
  9. Incoming request.
  10. [Process 3] page.tsx Loads url and mocks load successfully! ...
>>> node_modules/.bin/next start --port 3001
Main process id: 90293
[next.config] File loaded. Process id: 90293.
- ready started server on 0.0.0.0:3001, url: http://localhost:3001
- info Loaded env from .../with-msw-app/.env.production
[next.config] File loaded. Process id: 90294.
- warn You have enabled experimental feature (instrumentationHook) in next.config.js.
- warn Experimental features are not covered by semver, and may cause unexpected or broken application behavior. Use at your own risk.

[instrumentation] File loaded. Process id: 90294.
[instrumentation][register] Registering mocks. Process ID: ${process.pid}
[instrumentation][register] API mocking enabled, starting.
Mocks initialized
[next.config] File loaded. Process id: 90295.
[instrumentation] File loaded. Process id: 90295.
[instrumentation][register] Registering mocks. Process ID: ${process.pid}
[instrumentation][register] API mocking enabled, starting.
Mocks initialized
[Home] Process ID: 90295
mocked
[Home] Process ID: 90295
mocked

MSW v1.2.2 Next.js v13.4.9.

Jaesin avatar Jul 12 '23 05:07 Jaesin

Thank you for doing all that research @Jaesin!

philwolstenholme avatar Jul 12 '23 07:07 philwolstenholme

Thank you for the colossal research, @Jaesin 👏

It seems to come down to Next.js meddling with the global fetch, restoring it to the version it snapshots before running all the subsequent logic, like instrumentation.ts. I know it comes as strange from a maintainer of a library that also meddles with the global fetch (alas, I wish this wasn't so) but I wish Next.js would respect the environment's fetch before trying to reset it to whichever version was at the moment the server recorded it.

This points to a much larger issue as no request interception tools would work in Next.js at the current state. All of them are patching request-issuing modules, fetch included. I hope there's an official next step from the Next.js team regarding this. I would love for MSW users and Next.js users to be happy.

kettanaito avatar Jul 12 '23 12:07 kettanaito

I'm eager to use msw with Next.js again. But for those who need to mock in Next.js in the meantime, here's my approach: I'm using Next.js' api routes feature to mock. It's about the same amount of work as using msw. This pattern works well if you're not already using the api routes feature.

My approach:

  1. Configure api routes that have the same structure as the real APIs, but return hard-coded mock data
  2. Configure my app to point to a different base URL using an environment variable

coryhouse avatar Jul 12 '23 13:07 coryhouse

I'm eager to use msw with Next.js again. But for those who need to mock in Next.js in the meantime, here's my approach: I'm using Next.js' api routes feature to mock. It's about the same amount of work as using msw. This pattern works well if you're not already using the api routes feature.

My approach:

  1. Configure api routes that have the same structure as the real APIs, but return hard-coded mock data
  2. Configure my app to point to a different base URL using an environment variable

This is a great approach! Only trouble I found was my Intel based mac couldn't keep up with compiling the API routes and page in dev (this may have been fixed with the recent performance improvements). We decided to go with an express server that returns the mock data

elliotwestlake avatar Jul 12 '23 16:07 elliotwestlake