Fix: Docusaurus Not Working — Build Failing, Sidebar Not Showing, or Plugin Errors
Part of: React & Frontend Errors
Quick Answer
How to fix Docusaurus issues — docs and blog configuration, sidebar generation, custom theme components, plugin setup, MDX compatibility, search integration, and deployment.
The Problem
The Docusaurus build fails with a cryptic error:
npm run build
# Error: [ERROR] Docusaurus found broken links!
# Or: Error: Module not found: Can't resolve '@docusaurus/theme-common'Or the sidebar doesn’t show your docs:
Sidebar appears but is empty — docs exist in the docs/ folderOr MDX content renders as plain text:
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs>
<TabItem value="npm">npm install</TabItem>
</Tabs>
<!-- Renders as plain text instead of tabbed interface -->Or a custom page component doesn’t render:
404 — page not found for a page in src/pages/Why This Happens
Docusaurus is a React-based static site generator optimized for documentation. Its plugin-driven architecture requires specific configuration:
- Broken links are treated as build errors — Docusaurus checks all internal links during build. A typo in a markdown link or a reference to a deleted page fails the build. This is intentional to catch dead links early.
- Sidebar is auto-generated from the file structure or configured manually — by default, Docusaurus scans the
docs/directory and creates a sidebar. Ifsidebars.jshas a manual config that doesn’t match the actual files, pages are missing from navigation. - MDX components must be from
@theme/— Docusaurus provides built-in components via@theme/imports. Using raw HTML or components from other libraries without wrapping them in MDX causes rendering issues. docusaurus.config.jsis the central configuration — presets, plugins, themes, and site metadata are all configured here. Missing presets (like@docusaurus/preset-classic) remove core features.
The broken-link build failure exists because docs sites die a thousand cuts from dead internal links. Docusaurus treats every relative href as a contract. When a contributor renames a page and forgets to update an inbound link, the build fails before the deploy ships. That strictness is a feature: it forces link hygiene on the team. The cost is that low-discipline writing workflows (paste a snippet, hope the link still works) get blocked. The right response is not to set onBrokenLinks: 'ignore'; it’s to add a pre-commit hook that runs the build locally on docs PRs.
The sidebar invisibility usually comes from a mismatch between filesystem layout and sidebars.ts. Docusaurus does not warn when a manually declared item references a file that doesn’t exist — it silently drops it. So a sidebar entry like 'guides/advanced' with no docs/guides/advanced.md produces a sidebar that’s missing one item, with no log line. The diagnostic technique is to compare the file tree to the sidebar config programmatically before each release. A 20-line script that diffs the two catches the majority of cases.
MDX failures in Docusaurus v3 are also worth understanding deeply. Docusaurus migrated from MDX v1 to MDX v3 between v2 and v3, and the syntax rules tightened. JSX inside markdown now requires blank lines around it; expression brackets { and } in prose must be escaped; HTML-style comments inside JSX blocks throw. If you migrated docs written for MDX v1, expect to triage dozens of compile errors. The fix is repetitive but mechanical.
Production Incident Lens: When the Docs Deploy Fails Friday Night
The blast radius of a Docusaurus failure is the entire documentation site. For a developer-tools company, the docs site IS the support channel — if it’s down or shows a 24-hour-old version, every incoming question doubles. The failure mode is rarely “the site is unreachable.” It’s more often “we pushed at 5 PM, the build broke, the previous deploy is stale, and Monday morning customers see a missing v4 migration guide that we promised in the changelog.”
The on-call playbook for a docs site should treat Docusaurus like any production deploy:
- Build in CI on every PR, not only on merge — a broken link merged to main blocks every subsequent deploy until someone fixes it. Catching it in PR review is cheaper.
- Promote builds, don’t redeploy from source — keep the built
build/artifact as an immutable bundle. A revert is then a one-command operation: re-publish the previous artifact. Rebuilding from a previous commit can fail differently than the original deploy if dependencies have shifted. - Monitor the deploy, not just the build — Docusaurus generates a
sitemap.xmland a known set of routes. A simple post-deploy probe that fetches three canonical routes (/,/docs/intro,/blog) and asserts 200s catches the case where the build succeeded but the deploy uploaded a partial bundle. - Have a rollback rule — if the latest deploy 404s for any canonical route, revert to the last known good build automatically. Don’t wait for a human to notice.
The strict link checking is a gift here: most regressions are caught at build time, before the deploy ever runs. The cases that slip through are usually search index updates (Algolia indexed an old URL), CDN cache invalidation lag, or i18n route drift. Treat each of those as a separate runbook entry.
Fix 1: Set Up Docusaurus
npx create-docusaurus@latest my-docs classic --typescript
cd my-docs
npm start// docusaurus.config.ts
import type { Config } from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';
const config: Config = {
title: 'My Project',
tagline: 'Docs for My Project',
favicon: 'img/favicon.ico',
url: 'https://docs.myproject.com',
baseUrl: '/',
organizationName: 'my-org',
projectName: 'my-project',
// Broken link handling
onBrokenLinks: 'throw', // 'throw' | 'warn' | 'ignore'
onBrokenMarkdownLinks: 'warn',
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
'classic',
{
docs: {
sidebarPath: './sidebars.ts',
editUrl: 'https://github.com/my-org/my-project/tree/main/',
showLastUpdateTime: true,
showLastUpdateAuthor: true,
},
blog: {
showReadingTime: true,
editUrl: 'https://github.com/my-org/my-project/tree/main/',
blogSidebarCount: 'ALL',
},
theme: {
customCss: './src/css/custom.css',
},
} satisfies Preset.Options,
],
],
themeConfig: {
navbar: {
title: 'My Project',
logo: { alt: 'Logo', src: 'img/logo.svg' },
items: [
{ type: 'docSidebar', sidebarId: 'docs', position: 'left', label: 'Docs' },
{ to: '/blog', label: 'Blog', position: 'left' },
{ href: 'https://github.com/my-org/my-project', label: 'GitHub', position: 'right' },
],
},
footer: {
style: 'dark',
copyright: `Copyright © ${new Date().getFullYear()} My Project.`,
},
prism: {
theme: require('prism-react-renderer').themes.github,
darkTheme: require('prism-react-renderer').themes.dracula,
additionalLanguages: ['bash', 'typescript', 'json', 'yaml'],
},
colorMode: {
defaultMode: 'light',
respectPrefersColorScheme: true,
},
} satisfies Preset.ThemeConfig,
};
export default config;Fix 2: Sidebar Configuration
// sidebars.ts
import type { SidebarsConfig } from '@docusaurus/plugin-content-docs';
const sidebars: SidebarsConfig = {
docs: [
// Simple link
'intro',
// Category with items
{
type: 'category',
label: 'Getting Started',
collapsed: false, // Expanded by default
items: [
'getting-started/installation',
'getting-started/configuration',
'getting-started/quick-start',
],
},
// Auto-generated from directory
{
type: 'category',
label: 'Guides',
items: [
{ type: 'autogenerated', dirName: 'guides' },
],
},
// Category with link
{
type: 'category',
label: 'API Reference',
link: {
type: 'generated-index',
title: 'API Reference',
description: 'All API endpoints and types.',
},
items: [
'api/authentication',
'api/users',
'api/posts',
],
},
// External link
{
type: 'link',
label: 'GitHub',
href: 'https://github.com/my-org/my-project',
},
],
};
export default sidebars;# Docs directory structure
docs/
├── intro.md # /docs/intro
├── getting-started/
│ ├── installation.md # /docs/getting-started/installation
│ ├── configuration.md
│ └── quick-start.md
├── guides/
│ ├── _category_.json # Auto-generated category config
│ ├── basic-usage.md
│ └── advanced.md
└── api/
├── authentication.md
├── users.md
└── posts.md// docs/guides/_category_.json — category metadata
{
"label": "Guides",
"position": 3,
"collapsed": false,
"collapsible": true
}Fix 3: MDX Components
---
title: Installation Guide
sidebar_position: 1
tags: [getting-started, setup]
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import Admonition from '@theme/Admonition';
import CodeBlock from '@theme/CodeBlock';
# Installation
## System Requirements
:::info
Node.js 18 or higher is required.
:::
:::warning
**Breaking change:** Version 3.0 requires a new configuration format.
:::
:::danger
Never expose your API keys in client-side code.
:::
:::tip
Use `npx` to always run the latest version.
:::
## Install the Package
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install my-packageyarn add my-packagepnpm add my-packageCode Example
export default defineConfig({ // Options here });`}
## Fix 4: Custom Pages and Components
```typescript
// src/pages/index.tsx — custom homepage
import Layout from '@theme/Layout';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
export default function Home() {
const { siteConfig } = useDocusaurusContext();
return (
<Layout title="Home" description={siteConfig.tagline}>
<header style={{ textAlign: 'center', padding: '4rem 0' }}>
<h1>{siteConfig.title}</h1>
<p>{siteConfig.tagline}</p>
<Link
className="button button--primary button--lg"
to="/docs/intro"
>
Get Started
</Link>
</header>
</Layout>
);
}// src/components/FeatureCard.tsx — reusable component
export function FeatureCard({ title, description, icon }: {
title: string;
description: string;
icon: string;
}) {
return (
<div className="col col--4" style={{ padding: '1rem' }}>
<div style={{ textAlign: 'center' }}>
<span style={{ fontSize: '3rem' }}>{icon}</span>
<h3>{title}</h3>
<p>{description}</p>
</div>
</div>
);
}
// Use in MDX
import { FeatureCard } from '@site/src/components/FeatureCard';
<div className="row">
<FeatureCard icon="A" title="Fast" description="Blazing fast builds" />
<FeatureCard icon="B" title="Flexible" description="Highly customizable" />
<FeatureCard icon="C" title="Responsive" description="Mobile-first design" />
</div>Fix 5: Search Integration
# Option 1: Algolia DocSearch (free for open-source)
# Apply at: https://docsearch.algolia.com/// docusaurus.config.ts — Algolia search
themeConfig: {
algolia: {
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_SEARCH_API_KEY', // Public search-only key
indexName: 'my-project',
contextualSearch: true,
},
},# Option 2: Local search (no external service)
npm install @easyops-cn/docusaurus-search-local// docusaurus.config.ts — local search plugin
themes: [
[
'@easyops-cn/docusaurus-search-local',
{
hashed: true,
language: ['en'],
highlightSearchTermsOnTargetPage: true,
docsRouteBasePath: '/docs',
},
],
],Fix 6: Deployment
# Build static site
npm run build
# Test the build locally
npm run serve# GitHub Pages — built-in command
GIT_USER=your-username npm run deploy
# Or via GitHub Actions# .github/workflows/deploy.yml
name: Deploy Docusaurus
on:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npm run build
- uses: actions/upload-pages-artifact@v3
with: { path: build }
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4Still Not Working?
“Docusaurus found broken links” — this is a build error, not a warning. Fix the links or temporarily set onBrokenLinks: 'warn' in docusaurus.config.ts. Common causes: typos in markdown links, linking to deleted pages, or missing trailing slashes. Run npm run build to see all broken links at once.
Sidebar shows “undefined” for a category — the _category_.json file has invalid JSON or the label field is missing. Also check that sidebars.ts entries match actual file paths. 'getting-started/installation' expects docs/getting-started/installation.md to exist.
MDX compilation error — Docusaurus uses MDX v3. Some MDX v1/v2 syntax doesn’t work. Common issues: JSX expressions must be on their own line (not inline with markdown), { and } must be escaped in markdown text (use \{), and < in text must use <.
Custom CSS not applying — verify the CSS file path in docusaurus.config.ts matches: theme: { customCss: './src/css/custom.css' }. Use CSS custom properties to override theme values: --ifm-color-primary, --ifm-font-family-base, etc.
Deploy succeeds but the new content isn’t visible — your CDN is serving cached HTML. Docusaurus generates content-hashed assets but the entry HTML is stable. After every deploy, purge HTML caches (or set a short max-age on .html only and long max-age on hashed assets). Cloudflare Pages handles this automatically; older custom CDN setups don’t.
Algolia search returns 404s for valid pages — the DocSearch crawler indexes URLs but your latest deploy renamed pages without setting up redirects. Configure redirects in the Docusaurus client redirects plugin, or wait for the next nightly crawl and accept the gap.
Build memory exhaustion in CI — Docusaurus loads all docs into memory at build time. A docs site with 1,000+ pages can OOM on the GitHub Actions default runner. Bump the runner to a larger size, or set NODE_OPTIONS=--max-old-space-size=4096 before npm run build.
For related documentation issues, see Fix: Nextra Not Working, Fix: MDX Not Working, Fix: Vercel Deployment Failed, and Fix: Cloudflare Pages Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AutoAnimate Not Working — Transitions Not Playing, List Items Not Animating, or React State Changes Ignored
How to fix @formkit/auto-animate issues — parent ref setup, React useAutoAnimate hook, Vue directive, animation customization, disabling for specific elements, and framework integration.
Fix: Blurhash Not Working — Placeholder Not Rendering, Encoding Failing, or Colors Wrong
How to fix Blurhash image placeholder issues — encoding with Sharp, decoding in React, canvas rendering, Next.js image placeholders, CSS blur fallback, and performance optimization.
Fix: Embla Carousel Not Working — Slides Not Scrolling, Autoplay Not Starting, or Thumbnails Not Syncing
How to fix Embla Carousel issues — React setup, slide sizing, autoplay and navigation plugins, loop mode, thumbnail carousels, responsive breakpoints, and vertical scrolling.
Fix: Fumadocs Not Working — Pages Not Found, Search Not Indexing, or MDX Components Missing
How to fix Fumadocs documentation framework issues — Next.js App Router setup, content source configuration, sidebar generation, MDX components, search, OpenAPI integration, and custom themes.