Skip to content

Fix: Docusaurus Not Working — Build Failing, Sidebar Not Showing, or Plugin Errors

FixDevs · (Updated: )

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/ folder

Or 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. If sidebars.js has 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.js is 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:

  1. 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.
  2. 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.
  3. Monitor the deploy, not just the build — Docusaurus generates a sitemap.xml and 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.
  4. 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-package
yarn add my-package
pnpm add my-package

Code Example

{`import { defineConfig } from 'my-package';

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@v4

Still 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 &lt;.

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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles