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
Support Next.js 13 (App Router) #1644
Comments
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. |
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 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:
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, hi 👋 Thank you for reaching out!
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).
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:
Right now, the second one doesn't work due to the lack of
I have two concerns regarding this workaround:
It would be really nice to have |
We already have 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. |
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. |
@sebmarkbage, do you know if the // ./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 {
"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 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 You can reproduce this behavior in here: https://github.com/mswjs/examples-new/pull/7 What can be going wrong here? |
I strongly suspect Next.js is trying to resolve the Moreover, it then fails on a bunch of other 4th-party imports ( Do I have to mark the imported module in |
Hi 👋 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 😄 |
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 |
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 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:
|
You can create a client side only component that you include in your root "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, that would work but the main thing we're trying to solve here is the server-side integration. |
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? |
Our team absolutely loves msw and we really want to use it on a green field we started last week |
Module resolution in
|
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 I then two more console logs, both with pid Finally I saw a console log with I made a bunch of |
@kettanaito Next.js maintainer here, I did not verify your use case but what I think is going on there is that:
|
Hi, @feedthejim. Thanks for the info. I've tried logging 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? |
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 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 behaviorThe instrumentation hook run as a part of the Node.js server of Next and executes its Current behaviorNothing gets printed. I suspected that patching
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:
It seems that the hook shares at least 1 process with the layout and the page ( 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 |
Exactly the same results - 13.4.9 |
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) |
Does anyone created an issue on the NextJS repo? Would love to have the link so I can +1 |
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 Update: This was running nextjs 13.4.9 in classic mode (as apposed to app mode) so maybe not as much help here. |
In app mode (https://github.com/Jaesin/with-msw-app), I added some logging and patched
13-15 repeat for subsequent requests. It looks to me that the third process that handles all of the requests is instantiating
Running in prod mode:
MSW v1.2.2 |
Thank you for doing all that research @Jaesin! |
This comment was marked as outdated.
This comment was marked as outdated.
Regarding the I believe folks have suggested how to fix this in |
UpdatesI've shared my findings on the Next.js and MSW integration: https://twitter.com/kettanaito/status/1749496339556094316 Opened a pull request to add it as an example: mswjs/examples#101 TL;DRIt's not there quite yet. The first step has been done but Next.js remains incompatible with MSW due to the lack of means to, once again, run an arbitrary async code once for both server-side and client-side parts of Next.js. |
For anybody curious, I've got a somewhat functioning example of Next.js 14 + MSW right here: mswjs/examples#101. If you read the tweet I posed above, you understand why it's not a full integration example I'm yet ready to recommend to everyone. But it can get you going. If you find something to help fix pending issues (see the pull request's comments), please let me know. |
Hi @kettanaito – Just wanted to say that I love this package and have been using it for unit testing react components for the best part of three years. I'm currently working on a new project that uses Next 14 so have been eagerly following this discussion because it would be great to be able to use msw again. In the mean time, I wondered if you had any suggestions for what the next best option might be for mocking network responses for unit tests? Our stack is vitest + react testing library (moved over from Cypress which is also having issues with Next 14) and we use react query for data fetching. |
Playwright is supported to my knowledge and has build in network mocking. You might want to give that a try. |
You don't run the entire app during unit testing, so you can choose tools like Vitest and React Testing Library + MSW to test individual units of your application. Highly recommend browsing through the Examples. For E2E testing, you can use Playwright. Just bear in mind its mocking API wouldn't be able to intercept server-side requests your app makes (which makes sense; no E2E testing tools can do that). Once we land the end-to-end integration of Next + MSW, you'd be able to intercept and mock both server-side and client-side requests in E2E tests. |
On my side, I chose to implement a small mock server in combination with Playwright. I can run the tests in local and in the CI with a dedicated build. |
@noreiller, a great strategy 👍 You can also use MSW as a standalone HTTP server. May be useful if you consider using it "natively" in Next.js but cannot as of now. Look into https://github.com/mswjs/http-middleware. |
@kettanaito Thanks. I took a look at https://github.com/mswjs/http-middleware before implementing it but I didn't want to have another server to handle. 😝 |
Great advise, thank you! |
Thanks @kettanaito – I was under the impression that msw was having issues running in unit test environments because that is primarily what I've used the tool for, but of course, vitest / jest has no dependency on Next.js so I've given it a go this morning and it was really simple to set up. |
Any news here? |
To summarize my findings after trying out various methods, it appears that mocking through Express seems to be the most effective approach. (link) I modified an example provided by kettanaito and tested it. I preferred not to alter webpack, so I revised it to check for the presence of the window object. This approach allows us to update data upon server load, and upon navigating to a different page and calling the API again, the data updates can be observed through mocking. Similarly, mocking works well on the client-side as well. However, it's important to note that the worker and server do not share memory. If data is updated at server mount, the client cannot detect it, and vice versa. Therefore, if you only want to test the CSR part in Next.js without modifying webpack, checking for the window object seems like a good approach.(component link) Based on the current observations, creating an Express server and integrating it with msw for mocking appears to be the best solution. |
Has anyone else tried next's experimental proxy? https://github.com/vercel/next.js/tree/canary/packages/next/src/experimental/testmode/playwright I was able to get RSC fetches AND suspense streaming to work correctly with MSW with some fanangling, and for frontend mocking, I used the approach from https://github.com/valendres/playwright-msw where the service worker is installed into the browser by playwright itself, and not by a nextjs integration. With the above approach, I could share the same set of handlers for RSC and CSR. It only works with playwright, but it allows to fully mock a nextjs application. It does need some custom code, the code from |
Did you have any example about it? |
Nothing complete, it was just me playing around. If I ever come back to it, I will try to make an example repo, or possibly try to submit a PR to |
We had to use MWS as a stand-alone http server. |
I have found a way to mock API requests without using
This approach is very simple. First, define a handler that catches This approach does not conflict with monkey patching by Next.js for the fetch API because it does not use |
@mizdra - how does that mock responses for your endpoints? Presumably you'd have to add/remove the real endpoints each time (Next, afaik, will only give you one response if you have such a setup - and I believe it prefers static routes to the catchall routes (i.e. the catchall is a fallback, not an override)). It also seems like this would only work for routes defined in your application (e.g. in your example, myapp.com/api/my-endpoint), excluding the ability to fetch from e.g. https://jsonplaceholder.com/ or real external endpoints. Edit: As I tried to play with the example, I also couldn't get the fetch call to reload correctly (I think perhaps this relates to Next.js' caching - but using the second argument to Next's fetch and then |
Hi there |
@chrisb2244 Your understanding is correct. My approach cannnot mock external API requests. However, if you configure your project to request
This is because I forgot to add
I am sorry. I did not understand what you are saying. Could you please explain in more detail? |
@mizdra - using your new example I can see the behaviour you describe.
Now it reloads for me - I didn't know about this mechanism for setting a global default, thank you.
I think you addressed this with the environment variable. I think if you wanted to do this, you could also pass headers on your requests (for example with Playwright, or similar) and then parse those to determine handling in |
That's right. You must take care to configure your API endpoints for mocking and any other API endpoints so that they do not conflict. One of msw's philosophies is that you can mock without changing the application code at all. msw makes use of monkey patches and ServiceWorker to intercept requests in order to achieve this. My approach avoids the problem of monkey patches in Next.js, but loses that philosophy. The user may have to rewrite the application code, such as #1644 (comment), to mock the request. This is an important tradeoff. |
Scope
Adds a new behavior
Compatibility
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:
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
andhttps
. 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: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.
The text was updated successfully, but these errors were encountered: