How to use msw with Nextjs 13.2.1 (Error: Cannot access worker.start on the server.)
Asked Answered
G

1

6

I'm integrating a mock API which basically send a response object with a set of messages that are supposed be shown in the UI(Chatbox) along with Username, user pic and so on.

I'm having some trouble setting up the msw 1.1.0 with Next JS 13.2.1 (Experimental app directory with layouts) because of the breaking changes. However this is my code.

My code is set up like this so far

src/app/(user)/live-view/page.tsx

import React from "react";
import ThreadPostList from "../../components/threadList";
 import { worker } from '../../../mocks/browser';

if (process.env.NODE_ENV === 'development') {
  worker.start();
}

async function getMessages() {
  const response = await fetch("http://localhost:3000/api/messages");
  // const data = await response.json();
  return response;
}


async function LiveViewPage() {
  const messages = await getMessages();
  const convertedMessages = Object.keys(messages);
  // console.log(convertedMessages, "ConvertedMessages");
  
  Object.values(messages).forEach(val => console.log(val));

  
  return (
    <div className="border-t border-gray-200 py-4 divide-y divede-gray-200">
      <ThreadPostList />

    
     
    </div>
  );
}

export default LiveViewPage;

And my Msw mock API is under

src/mocks/handler.ts

import { rest } from "msw";
import { UserWithMessages } from "./types";

const mockData: UserWithMessages[] = [
  {
    user: {
      id: "user1",
      name: "John Doe",
      profilePic: "https://example.com/user1.jpg",
    },
    messages: [
      {
        id: "msg1",
        senderId: "user1",
        text: "Hey, how are you?",
        createdAt: new Date("2022-01-01T12:00:00Z"),
      },
      {
        id: "msg2",
        senderId: "user1",
        text: "Did you get my email?",
        createdAt: new Date("2022-01-02T12:00:00Z"),
      },
    ],
  },
  {
    user: {
      id: "admin",
      name: "Admin",
      profilePic: "https://example.com/admin.jpg",
    },
    messages: [],
  },
];


export const handlers = [
  rest.get<UserWithMessages[]>("/api/messages", (req, res, ctx) => {
    return res(ctx.status(200), ctx.json(mockData));
  }),
];

And finnaly my browser.ts is under

src/mocks/browser.ts

"use client"

import { setupWorker } from 'msw';
import { handlers } from './handlers';

export const worker = setupWorker(...handlers);

when I run the server it's throwing an error

Error: Cannot access worker.start on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.

Where and How do I start my worker? Do I have to create a file under mocks and call the server there and import that component in my page.tsx?Any help would be appreciated thanks

Gethsemane answered 2/3, 2023 at 11:0 Comment(0)
C
4

I've been fighting with getting Mock Service Worker to run consistently with NextJS within the app directory structure since it moved into stable release. I've had some level of success, but I suspect it isn't the ideal solution. We are still waiting on a legitimate example implementation from the NextJS team.

We can't call the worker on a server-rendered component because of the exact error you are seeing. Instead, I've moved my fetch calls into a shared service that is just typescript:

./services/api-service.ts

async function initMocks() {
  if (typeof window === 'undefined') {
    const { server } = await import('./src/mocks/server')
    await server.listen({ onUnhandledRequest: 'bypass' })
  } else {
    const { worker } = await import('./src/mocks/browser')
    await worker.start({ onUnhandledRequest: 'bypass' })
  }
}

// Note the change in ENV var name here
// https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables#bundling-environment-variables-for-the-browser
if (process.env.NEXT_PUBLIC_MOCK_APIS === 'enabled') {
  initMocks()
}

export const makeApiRequest = async (path: string) => {
  try {
    const resp = await fetch(path)
    if (resp.ok) {
      const result = await resp.json()
      return result
    }
  } catch (err: any) {
    // handle errors
  }
}

I don't know why, but implementing MSW at a lower level like this, and outside of an actual react component has been more successful for me than trying to initialize it at the page or layout level, as it's implemented in the NextJS pages directory examples.

This would change your page component to look more like this at the top:

src/app/(user)/live-view/page.tsx

import React from "react";
import ThreadPostList from "../../components/threadList";
import { makeApiRequest} from './services/api-service';

async function getMessages() {
  const response = await makeApiRequest("http://localhost:3000/api/messages");
  return response;
}

One more thing I notice here is that your handler is catching on a relative path, which might trip things up on the server side as well. The server handler lacks the context of a root URL and I've had better success with either full paths in there or path matching. So either:

export const handlers = [
  rest.get<UserWithMessages[]>("http://localhost:3000/api/messages", (req, res, ctx) => {
    return res(ctx.status(200), ctx.json(mockData));
  }),
];

or:

export const handlers = [
  rest.get<UserWithMessages[]>("*/api/messages", (req, res, ctx) => {
    return res(ctx.status(200), ctx.json(mockData));
  }),
];

The latter might come in handy if you want to use MSW to run Jest tests or something in a deployed environment or somewhere other than your machine.

Technically, in your problem and in any app that follows the best practices outlined in the NextJS documentation, we wouldn't need the browser implementation of MSW at all since all data would be fetched in server components and used to hydrate the client. However, I left it in here to provide some consistency when comparing to previous implementations.

At the time I wrote this I'm working with [email protected] and [email protected]

Cathleencathlene answered 9/6, 2023 at 13:32 Comment(5)
If you uses fetch out of the SSC itself, does it still keep catching the responses?Saransk
It worked for me by the way!Saransk
@Saransk It works for both client and server side components. The top lines in api-service.ts check whether we are in a browser (client) environment or not. If not, we start the node server worker. Otherwise, run the client service worker.Cathleencathlene
Well, I'm not sure about it but it is not working stable, from time to time it breaks and sends the results back wrongly: ⨯ src/app/news/page.tsx (20:16) @ categories ⨯ TypeError: Cannot destructure property 'data' of '(intermediate value)' as it is undefined. at News (./src/app/news/page.tsx:27:19), it is annoying.Saransk
This will only provide static handlers, right? It's not possible to modify the handlers during tests, and the deployed code will include the envvar check (although not, I think, the MSW libraries or your imports of .src/mocks/...)?Dessiatine

© 2022 - 2024 — McMap. All rights reserved.