Learn-skills.dev web-meta-framework-docusaurus

Docusaurus 3.x documentation framework — site configuration, docs/blog plugins, sidebars, versioning, MDX, swizzling, and deployment

install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agents-inc/skills/web-meta-framework-docusaurus" ~/.claude/skills/neversight-learn-skills-dev-web-meta-framework-docusaurus && rm -rf "$T"
manifest: data/skills-md/agents-inc/skills/web-meta-framework-docusaurus/SKILL.md
source content

Docusaurus

Quick Guide: Docusaurus 3.x is a React-powered static site generator for documentation. Configure everything in

docusaurus.config.js
(ESM). Use
@docusaurus/preset-classic
for docs + blog + pages + sitemap in one preset. Sidebars can be fully autogenerated from filesystem structure using
_category_.json
and front matter
sidebar_position
. Customize theme components via swizzling (prefer
--wrap
over
--eject
). MDX is the default content format — use front matter for metadata, admonitions for callouts, and import React components directly in
.mdx
files. Version docs with
docusaurus docs:version
. Deploy the
build/
output to any static host.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md

(You MUST use

docusaurus.config.js
(or
.ts
) as the single source of truth for all site configuration — never scatter config across multiple files)

(You MUST use

@docusaurus/preset-classic
unless you have a specific reason to configure plugins individually — the preset bundles docs, blog, pages, sitemap, and theme)

(You MUST prefer

--wrap
over
--eject
when swizzling — wrapping preserves upstream updates, ejecting creates a maintenance burden)

(You MUST use front matter

sidebar_position
and
_category_.json
for sidebar ordering in autogenerated sidebars — do not fight the filesystem-driven convention)

(You MUST NOT mix versioned and unversioned docs in the same plugin instance — use separate plugin instances for different doc sets)

</critical_requirements>


Auto-detection: Docusaurus, docusaurus.config.js, docusaurus.config.ts, @docusaurus/preset-classic, @docusaurus/core, sidebars.js, docs:version, docusaurus build, docusaurus start, docusaurus deploy, docusaurus swizzle, MDX, category.json, sidebar_position, @site, @theme, @theme-original, plugin-content-docs, plugin-content-blog

When to use:

  • Configuring
    docusaurus.config.js
    (site metadata, presets, plugins, theme, navbar, footer)
  • Setting up or modifying sidebar structure (autogenerated or manual)
  • Adding versioned documentation
  • Customizing theme components via swizzling
  • Writing MDX content with Docusaurus-specific features (admonitions, tabs, code blocks)
  • Creating custom pages with React components
  • Configuring the blog plugin
  • Setting up search (Algolia DocSearch or local)
  • Deploying Docusaurus to static hosting
  • Configuring i18n / localization

When NOT to use:

  • General React component patterns (Docusaurus uses React internally but this skill covers Docusaurus APIs, not React fundamentals)
  • CSS/styling approaches not specific to Docusaurus theming (general CSS patterns are a separate concern)
  • Git hooks, linting, or formatting setup (separate from documentation framework concerns)
  • Content that belongs in the docs themselves, not the site framework
  • VitePress — Vue-based, different config format and plugin system
  • Nextra — Next.js-based, uses
    _meta.json
    not
    _category_.json
    , different routing model
  • Starlight — Astro-based, uses
    astro.config.mjs
    and content collections, different architecture entirely

Examples

Other resources:

  • Quick Reference — CLI commands, front matter fields, config option tables

<philosophy>

Philosophy

Docusaurus is an opinionated documentation framework that trades flexibility for convention. It makes strong decisions about routing (filesystem-based), content format (MDX), and structure (docs + blog + pages) so you can focus on writing content rather than building infrastructure.

Core principles:

  1. Convention over configuration — filesystem structure drives routing and sidebar generation; fight this and you fight the framework
  2. Preset-first
    preset-classic
    bundles the common plugin set; only decompose into individual plugins when you need multiple docs instances or unusual setups
  3. Content as data — front matter is the metadata layer;
    sidebar_position
    ,
    slug
    ,
    tags
    ,
    custom_edit_url
    all live in the document, not in external config
  4. Swizzle, don't fork — customize theme components via the swizzle CLI; wrapping preserves upstream compatibility, ejecting creates a snapshot you must maintain
  5. Static output
    docusaurus build
    produces a static site; there is no server runtime, no SSR in production, no API routes
</philosophy>
<patterns>

Core Patterns

Pattern 1: docusaurus.config.js Structure

The config file is the single entry point. It uses ESM (

export default
) and configures site metadata, presets (which bundle plugins + theme), and theme-level settings like navbar and footer.

// docusaurus.config.js — minimal production setup
export default {
  title: "My Docs",
  tagline: "Documentation for My Project",
  url: "https://docs.example.com",
  baseUrl: "/",
  onBrokenLinks: "throw",
  onBrokenMarkdownLinks: "throw",
  favicon: "img/favicon.ico",

  presets: [
    [
      "@docusaurus/preset-classic",
      {
        docs: {
          sidebarPath: "./sidebars.js",
          editUrl: "https://github.com/my-org/my-repo/edit/main/docs-site/",
          showLastUpdateTime: true,
        },
        blog: { showReadingTime: true },
        theme: { customCss: ["./src/css/custom.css"] },
      },
    ],
  ],

  themeConfig: {
    navbar: {
      title: "My Docs",
      items: [
        /* nav items */
      ],
    },
    footer: {
      style: "dark",
      links: [
        /* footer columns */
      ],
    },
    docs: { sidebar: { hideable: true, autoCollapseCategories: true } },
  },
};

Key gotcha:

onBrokenLinks: 'throw'
is essential for production — it fails the build on broken internal links rather than silently deploying dead links.

Full example: See examples/core.md for complete config with navbar, footer, and multi-instance docs setup.


Pattern 2: Sidebar Configuration

Docusaurus offers two sidebar strategies. Autogenerated sidebars derive structure from the filesystem and are the default recommendation. Manual sidebars give full control but require maintenance.

// sidebars.js — autogenerated (recommended)
export default {
  docs: [{ type: "autogenerated", dirName: "." }],
};

Control ordering and labels via front matter and

_category_.json
:

---
sidebar_position: 3
sidebar_label: Quick Start
---
// docs/guides/_category_.json
{
  "label": "Guides",
  "position": 2,
  "collapsible": true,
  "collapsed": false,
  "link": { "type": "generated-index", "title": "All Guides" }
}

Key gotcha: Without

sidebar_position
, items sort alphabetically by filename. Use number prefixes (
01-intro.md
) or front matter — but not both, as number prefixes are stripped from the URL slug.

Full example: See examples/core.md for manual sidebars, custom sidebar items generator, and multi-sidebar setups.


Pattern 3: Swizzling Theme Components

Swizzling lets you customize any theme component. Wrapping adds behavior around the original; ejecting gives you a full copy to modify.

# List all swizzlable components
npx docusaurus swizzle --list

# Wrap a component (SAFE — preserves upstream updates)
npx docusaurus swizzle @docusaurus/theme-classic Footer -- --wrap

# Eject a component (DANGEROUS — you own the snapshot)
npx docusaurus swizzle @docusaurus/theme-classic Footer -- --eject
// src/theme/Footer/index.js — wrapping example
import React from "react";
import Footer from "@theme-original/Footer";

export default function FooterWrapper(props) {
  return (
    <>
      <Footer {...props} />
      <div className="custom-banner">Custom content below footer</div>
    </>
  );
}

Key gotcha: The

@theme-original/
import is critical in wrappers — it references the original component. Using
@theme/
would create an infinite loop since your wrapper IS the
@theme/Footer
.

Full example: See examples/customization.md for swizzling safety levels, common swizzle targets, and CSS variable theming.


Pattern 4: MDX Content Features

Docusaurus uses MDX v3 — Markdown with embedded JSX. Key Docusaurus-specific features include admonitions, tabs, and code blocks with metadata.

---
title: My Document
description: SEO description for this page
sidebar_position: 1
tags: [getting-started, tutorial]
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

:::tip[Pro Tip]
Admonitions support `note`, `tip`, `info`, `warning`, `danger` types.
The bracket syntax `:::tip[Custom Title]` sets the title.
:::

<Tabs>
  <TabItem value="npm" label="npm" default>
    ```bash npm install my-package ```
  </TabItem>
  <TabItem value="yarn" label="yarn">
    ```bash yarn add my-package ```
  </TabItem>
</Tabs>

Key gotcha: MDX v3 is stricter than Markdown — unclosed HTML tags, bare

{
characters, and
<
comparisons in text will cause build errors. Escape them with
\{
and
\<
or use code fences.

Full example: See examples/content.md for code block features, asset handling, and blog plugin configuration.


Pattern 5: Versioning

Docusaurus versions docs by snapshotting the entire

docs/
directory. The current working copy is always
current
(next/unreleased). Cut a release version when shipping.

# Snapshot current docs as version 1.0.0
npx docusaurus docs:version 1.0.0

This creates

versioned_docs/version-1.0.0/
and
versioned_sidebars/version-1.0.0-sidebars.json
. Configure version behavior in the docs plugin:

// In preset-classic docs options
docs: {
  lastVersion: 'current',
  versions: {
    current: { label: '2.0.0-beta', path: 'next', banner: 'unreleased' },
    '1.0.0': { label: '1.0.0', path: '1.0.0', banner: 'none' },
  },
  onlyIncludeVersions: ['current', '1.0.0'],
}

Key gotcha: Versioned docs are full copies, not diffs. Each version doubles the build time and output size. Use

onlyIncludeVersions
in development to speed up builds. Only version when you have actual API/feature differences — not for every release.

Full example: See examples/customization.md for version banner configuration and multi-version navigation.

</patterns>

<decision_framework>

Decision Framework

Sidebar Strategy

How should the sidebar be organized?
|-- Small docs site (< 30 pages)?
|   +-- Use autogenerated sidebar with sidebar_position front matter
|-- Large docs site with many sections?
|   |-- Sections map cleanly to directories? -> Autogenerated + _category_.json
|   +-- Need cross-directory grouping? -> Manual sidebar in sidebars.js
|-- Multiple independent doc sets (e.g., API + guides)?
|   +-- Use multiple docs plugin instances, each with its own sidebar
+-- Need to mix auto and manual?
    +-- Use autogenerated for most, with manual items for special entries

Swizzling Approach

Need to customize a theme component?
|-- Adding content around the component? -> Wrap (--wrap)
|-- Need to change the component's internal logic? -> Check safety level first
|   |-- Component marked "Safe"? -> Eject is acceptable
|   |-- Component marked "Unsafe"? -> Wrap if possible, eject only as last resort
|   +-- Component marked "Forbidden"? -> Do not swizzle — find another approach
+-- Just changing colors/spacing? -> Use CSS variables in custom.css (no swizzle needed)

Content Format

What kind of page am I creating?
|-- Documentation article? -> .md or .mdx file in docs/
|-- Blog post? -> .md or .mdx file in blog/
|-- Standalone page with custom layout?
|   |-- Mostly content? -> .mdx in src/pages/
|   +-- Mostly interactive/React? -> .tsx in src/pages/
+-- Need to embed React components in docs? -> Use .mdx with imports

</decision_framework>


<red_flags>

RED FLAGS

High Priority Issues:

  • Using
    onBrokenLinks: 'ignore'
    or
    'log'
    in production — broken links should fail the build (
    'throw'
    )
  • Ejecting theme components when wrapping would suffice — ejected components miss upstream bug fixes and feature additions
  • Using
    @theme/ComponentName
    import in a swizzle wrapper instead of
    @theme-original/ComponentName
    — creates an infinite import loop
  • Putting all sidebar config in
    sidebars.js
    manually when autogenerated would work — creates a maintenance burden that falls out of sync with actual docs

Medium Priority Issues:

  • Versioning every release instead of only when docs content actually changes — bloats build time and output size
  • Not setting
    onBrokenMarkdownLinks: 'warn'
    at minimum — silent broken links accumulate
  • Missing
    editUrl
    in docs plugin config — blocks "Edit this page" links that drive community contributions
  • Not using
    showLastUpdateTime: true
    — readers cannot tell how current a doc page is

Common Mistakes:

  • Bare
    {
    or
    <
    in MDX content causing build failures — escape with
    \{
    and
    \<
    or wrap in code fences
  • Forgetting to restart the dev server after changing
    docusaurus.config.js
    — config changes are not hot-reloaded
  • Using
    .md
    extension with JSX when
    format: 'detect'
    is configured — in detect mode, only
    .mdx
    files support JSX (default mode processes both)
  • Creating
    _category_.json
    with wrong field names (
    name
    instead of
    label
    ,
    order
    instead of
    position
    )
  • Importing from
    @docusaurus/
    packages directly in MDX — use
    @theme/
    or
    @site/
    aliases instead

Gotchas & Edge Cases:

  • baseUrl
    must end with
    /
    — omitting the trailing slash breaks asset resolution
  • Static assets in
    static/
    are served from root, not from
    baseUrl
    — use
    require()
    or
    useBaseUrl()
    for path-safe references
  • Blog authors are configured in
    blog/authors.yml
    , not in
    docusaurus.config.js
  • The
    slug
    front matter field overrides the URL path derived from the filename — useful for keeping clean URLs when renaming files
  • docs-only mode
    requires setting
    routeBasePath: '/'
    in the docs plugin AND
    blog: false
    in the preset
  • Custom pages in
    src/pages/
    use the file path as the route —
    src/pages/support.tsx
    becomes
    /support
  • Tabs component state is not shared between instances by default — use
    groupId
    prop to sync tab selection across the page

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md

(You MUST use

docusaurus.config.js
as the single source of truth for all site configuration)

(You MUST use

@docusaurus/preset-classic
unless you have a specific reason to configure plugins individually)

(You MUST prefer

--wrap
over
--eject
when swizzling — wrapping preserves upstream updates)

(You MUST use front matter

sidebar_position
and
_category_.json
for sidebar ordering — do not fight filesystem-driven conventions)

(You MUST NOT mix versioned and unversioned docs in the same plugin instance)

Failure to follow these rules will cause broken builds, unmaintainable theme overrides, and sidebar chaos.

</critical_reminders>