When you’re building documentation for an API or product that depends on backend data, you face a familiar challenge: how do you make your examples feel real without relying on a fragile staging environment or exposing production endpoints?
This post is not just a set of code snippets — it’s a deep-dive tutorial and understanding guide for using 🛠 Mock Service Worker (MSW) with 📚 Docusaurus. By the end, you’ll know not only how to set it up.
💡** Why Documentation Needs Realistic API Responses**
Have you ever followed an API tutorial, clicked “Run”, and immediately hit an error? ❌ Maybe the staging server was down. Maybe you didn’t have the right auth token. Maybe the data just looked… boring.
Your readers feel this too.
By mocking your APIs, you:
✨ Guarantee Consistency: The data in your examples always matches the docs.
🧪 Provide a Safe Sandbox: Readers can experiment with code samples without breaking anything.
⚡ Lower the Barrier to Entry: No auth keys, VPNs, or setup steps needed — just run npm start.
✈️ Enable Offline Work: Contributors can work from a plane, train, or anywhere without internet.
MSW intercepts requests at the network layer (Service Worker), which means your frontend doesn’t know the difference between real and fake APIs — perfect for interactive docs.
🏗 Step 1: Installing MSW — The Foundation
First, install MSW as a development dependency:
npm install msw --save-dev
This adds the core tooling you’ll need to spin up a Service Worker in development.
🗂 Step 2: Designing Your Mock Architecture
Before writing code, think about the architecture:
- Handlers: Define each endpoint you want to mock.
- Browser Worker: Bootstraps MSW in the browser.
- Node Server (Optional): Lets you reuse the same mocks in tests.
Create a folder structure like this:
my-docusaurus-app/
├── src/
│ ├── mocks/
│ │ ├── handlers.js ← endpoint definitions
│ │ └── browser.js ← bootstraps MSW
│ └── theme/
│ └── Root.js ← starts MSW in dev mode
This structure makes it clear to future contributors where the mocking logic lives.
✍️Step 3: Writing Your First Handler
Think of a handler as an API contract in miniature — it specifies which HTTP verb, which endpoint, and what response should come back.
// src/mocks/handlers.js
import { rest } from 'msw';
export const handlers = [
rest.get('/api/info', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
message: 'Hello from MSW! This is a stable, predictable response.'
})
);
}),
rest.post('/api/login', async (req, res, ctx) => {
const { username } = await req.json();
return res(
ctx.status(200),
ctx.json({ token: `fake-jwt-token-for-${username}` })
);
})
];
This makes it easy to reproduce login flows or fetch requests without a live backend.
🌐Step 4: Bootstrapping MSW in the Browser
Create src/mocks/browser.js
:
import { setupWorker } from 'msw';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
This file ties together all handlers and creates a single Service Worker entry point.
🏃 Step 5: Starting the Worker in Docusaurus
We want MSW to run automatically when the docs load in dev mode. Docusaurus provides a way to wrap the entire app via Root.js
.
// src/theme/Root.js
import React, { useEffect } from 'react';
export default function Root({ children }) {
useEffect(() => {
if (process.env.NODE_ENV === 'development') {
import('../mocks/browser').then(({ worker }) => {
worker.start({ onUnhandledRequest: 'bypass' });
});
}
}, []);
return <>{children}</>;
}
Why onUnhandledRequest: 'bypass'
? It ensures requests that aren’t mocked still hit the real API, which is useful if you’re mocking only part of your backend.
🎨Step 6: Making the Docs Interactive
Here’s an example React component that fetches data from our mocked endpoint:
import React, { useEffect, useState } from 'react';
export default function ExampleComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/info')
.then((res) => res.json())
.then(setData);
}, []);
return (
<div style={{ background: '#f8f8f8', padding: '1rem', borderRadius: '8px' }}>
<h4>Mocked API Response</h4>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
This creates a live component right in your documentation, so readers can see actual responses as they would appear in a real app.
🏗Handling Deployment on GitLab Pages (Dynamic URLs)
One challenge I faced after integrating MSW was deploying the docs on GitLab Pages. Each pipeline run generated a dynamic URL like:
https://myproject.gitlab.io/-/jobs/235345/artifacts/public/index.html
By default, MSW looks for mockServiceWorker.js
at the root (/mockServiceWorker.js
). On GitLab Pages with dynamic URLs, this broke because the Service Worker lived at:
https://myproject.gitlab.io/-/jobs/235345/artifacts/mockServiceWorker.js
which is dynamic for each pipeline.
The Fix
When calling worker.start()
, you can pass a serviceWorker.url
option to tell MSW exactly where to find the file:
worker.start({
serviceWorker: {
url: `${window.location.pathname.replace(/\/index\.html$/, '')}/mockServiceWorker.js`
}
});
This dynamically computes the correct URL based on the current page’s path, ensuring MSW is always found regardless of the pipeline run.
This small tweak made the GitLab Pages deployment reliable and future-proof, so every pipeline produced a fully functional, interactive documentation site.
Step 7: Common Pitfalls and How to Avoid Them
-
CORS Errors: Make sure your mocked URL exactly matches what
fetch
is requesting (including domain). -
Worker Not Starting: Forgetting to import
browser.js
inRoot.js
means MSW never runs. -
Production Builds Breaking: Guard MSW startup with
NODE_ENV
so it only runs locally. -
Dynamic Paths on CI/CD: Use
serviceWorker.url
to point MSW to the correct location in CI/CD deployments.
Beyond Development: Using MSW in Tests
One of MSW’s superpowers is that you can reuse these mocks in integration tests:
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
const server = setupServer(...handlers);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Now your tests, your local dev environment, and your documentation all speak the same language.
Conclusion
Mocking APIs in your Docusaurus site isn’t just a gimmick, it’s a professional touch that sets great documentation apart. By using MSW, you get production-like responses without production-like headaches. Your users learn faster, and your team spends less time troubleshooting docs.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.