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 philosophy guide for using š Mock Service Worker (MSW) with š Docusaurus. By the end, youāll know not only how to set it up, but why this pattern makes your documentation more professional, reliable, and maintainable.
š”** 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 Responseh4>
<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.
Why This Approach is Valuable
- Developer Happiness: No more debugging failing demos because staging is down.
- Reader Confidence: Readers trust docs that work exactly as described.
- Maintenance Simplicity: Change the mock once, and examples update everywhere.
- Education: This pattern teaches contributors how your API works in a safe environment.
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.