Skip to main content
Technology & EngineeringSeo Content387 lines

Nextra

"Nextra documentation framework: MDX-powered Next.js docs and blog sites, sidebar navigation, full-text search, i18n, themes (docs and blog), frontmatter configuration, and custom components."

Quick Summary29 lines
Nextra is an opinionated Next.js framework for content-heavy sites — primarily documentation and blogs. It eliminates boilerplate by mapping the filesystem to navigation, compiling MDX automatically, and providing built-in search, syntax highlighting, and dark mode. You write MDX files, organize them in directories, and Nextra generates a fully navigable documentation site with table of contents, breadcrumbs, and pagination. The framework offers two official themes (`nextra-theme-docs` and `nextra-theme-blog`) that handle layout, styling, and interactive features. Customization happens through `theme.config.tsx` and MDX component overrides rather than building from scratch.

## Key Points

- Organize `_meta.json` to control sidebar order and titles explicitly — do not rely on filesystem alphabetical ordering for user-facing navigation.
- Use Nextra's built-in `<Callout>`, `<Steps>`, `<Tabs>`, and `<Cards>` components before building custom ones; they handle dark mode, accessibility, and responsive design.
- Set `docsRepositoryBase` to enable "Edit this page" links, lowering the barrier for community contributions.
- Use frontmatter `searchable: false` for pages that should not appear in search results (changelogs, legal pages).
- Keep `_meta.json` files in every directory — without them, pages appear in alphabetical order with raw filenames as titles.
- Enable `defaultShowCopyCode` in the Nextra config so all code blocks get copy buttons without per-block annotation.
- Use the `filename` attribute on code blocks to show which file the code belongs to.
- Deploy on Vercel for zero-config static export, or use `next export` for hosting on any static file server.
- **Nesting pages too deeply**: More than 3 levels of nesting makes navigation unwieldy. Flatten the structure and use categories in `_meta.json` instead.
- **Skipping `_meta.json`**: Without it, the sidebar shows raw filenames in alphabetical order. Always define display titles and ordering.
- **Using the Pages Router for new projects and expecting App Router features**: Nextra v2 uses Pages Router. If you need App Router features, wait for Nextra v3 or consider fumadocs.
- **Overloading theme.config.tsx with business logic**: Keep it declarative. Complex logic should live in separate components or hooks, not inline in the config object.

## Quick Example

```bash
npm install nextra nextra-theme-docs
```

```
</Tab>
  <Tab>
```
skilldb get seo-content-skills/NextraFull skill: 387 lines
Paste into your CLAUDE.md or agent config

Nextra — Next.js Documentation Framework

Core Philosophy

Nextra is an opinionated Next.js framework for content-heavy sites — primarily documentation and blogs. It eliminates boilerplate by mapping the filesystem to navigation, compiling MDX automatically, and providing built-in search, syntax highlighting, and dark mode. You write MDX files, organize them in directories, and Nextra generates a fully navigable documentation site with table of contents, breadcrumbs, and pagination. The framework offers two official themes (nextra-theme-docs and nextra-theme-blog) that handle layout, styling, and interactive features. Customization happens through theme.config.tsx and MDX component overrides rather than building from scratch.

Setup

Docs Theme

npm install nextra nextra-theme-docs
// next.config.mjs
import nextra from "nextra";

const withNextra = nextra({
  theme: "nextra-theme-docs",
  themeConfig: "./theme.config.tsx",
  defaultShowCopyCode: true,
  latex: true,
  search: {
    codeblocks: false,
  },
});

export default withNextra({
  reactStrictMode: true,
});
// theme.config.tsx
import type { DocsThemeConfig } from "nextra-theme-docs";
import { useConfig } from "nextra-theme-docs";
import { useRouter } from "next/router";

const config: DocsThemeConfig = {
  logo: <span className="font-bold text-xl">My Docs</span>,
  project: {
    link: "https://github.com/org/repo",
  },
  docsRepositoryBase: "https://github.com/org/repo/tree/main/docs",
  useNextSeoProps() {
    const { asPath } = useRouter();
    if (asPath !== "/") {
      return { titleTemplate: "%s – My Docs" };
    }
    return { title: "My Docs" };
  },
  head: function Head() {
    const { frontMatter, title } = useConfig();
    return (
      <>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta
          property="og:title"
          content={frontMatter.title || title}
        />
        <meta
          property="og:description"
          content={
            frontMatter.description || "Documentation for My Project"
          }
        />
      </>
    );
  },
  sidebar: {
    defaultMenuCollapseLevel: 1,
    toggleButton: true,
    autoCollapse: true,
  },
  toc: {
    backToTop: true,
    float: true,
  },
  footer: {
    text: (
      <span>
        {new Date().getFullYear()} My Project. Built with Nextra.
      </span>
    ),
  },
  editLink: {
    text: "Edit this page on GitHub",
  },
  feedback: {
    content: "Question? Give us feedback →",
    labels: "feedback",
  },
  navigation: {
    prev: true,
    next: true,
  },
};

export default config;

Project Structure

docs/
├── pages/
│   ├── _meta.json
│   ├── index.mdx
│   ├── getting-started/
│   │   ├── _meta.json
│   │   ├── installation.mdx
│   │   └── configuration.mdx
│   ├── guides/
│   │   ├── _meta.json
│   │   ├── basics.mdx
│   │   └── advanced.mdx
│   └── api-reference/
│       ├── _meta.json
│       └── endpoints.mdx
├── theme.config.tsx
├── next.config.mjs
└── package.json

Key Techniques

Sidebar Navigation with _meta.json

// pages/_meta.json
{
  "index": {
    "title": "Introduction",
    "theme": {
      "layout": "full"
    }
  },
  "getting-started": {
    "title": "Getting Started",
    "type": "menu"
  },
  "guides": "Guides",
  "api-reference": "API Reference",
  "---": {
    "type": "separator"
  },
  "changelog": {
    "title": "Changelog",
    "href": "/changelog",
    "newWindow": false
  },
  "github": {
    "title": "GitHub",
    "href": "https://github.com/org/repo",
    "newWindow": true
  }
}
// pages/getting-started/_meta.json
{
  "installation": "Installation",
  "configuration": "Configuration",
  "quick-start": {
    "title": "Quick Start",
    "display": "hidden"
  }
}

MDX Frontmatter and Page Options

---
title: Installation Guide
description: How to install and set up the project
searchable: true
---

import { Callout, Steps, Tabs, Tab } from 'nextra/components'

# Installation

<Steps>
### Step 1: Install dependencies

<Tabs items={['npm', 'pnpm', 'yarn']}>
  <Tab>
    ```bash
    npm install my-library
    ```
  </Tab>
  <Tab>
    ```bash
    pnpm add my-library
    ```
  </Tab>
  <Tab>
    ```bash
    yarn add my-library
    ```
  </Tab>
</Tabs>

### Step 2: Configure

Create a config file in your project root:

```typescript filename="my-library.config.ts" {3-5} copy
export default {
  // highlight-start
  output: './dist',
  format: 'esm',
  target: 'es2022',
  // highlight-end
}

Step 3: Verify

<Callout type="info"> Run `npx my-library doctor` to verify your setup. </Callout> </Steps> ```

Internationalization (i18n)

// next.config.mjs
import nextra from "nextra";

const withNextra = nextra({
  theme: "nextra-theme-docs",
  themeConfig: "./theme.config.tsx",
});

export default withNextra({
  i18n: {
    locales: ["en", "es", "ja"],
    defaultLocale: "en",
  },
});
// theme.config.tsx — i18n config
const config: DocsThemeConfig = {
  i18n: [
    { locale: "en", text: "English" },
    { locale: "es", text: "Español" },
    { locale: "ja", text: "日本語" },
  ],
  // ...
};
pages/
├── getting-started.en.mdx
├── getting-started.es.mdx
└── getting-started.ja.mdx

Custom Components

// components/api-table.tsx
import React from "react";

interface ApiProp {
  name: string;
  type: string;
  default?: string;
  description: string;
  required?: boolean;
}

export function ApiTable({ props }: { props: ApiProp[] }) {
  return (
    <div className="overflow-x-auto my-6">
      <table className="w-full text-sm">
        <thead>
          <tr className="border-b border-gray-200 dark:border-gray-700">
            <th className="text-left py-2 pr-4">Prop</th>
            <th className="text-left py-2 pr-4">Type</th>
            <th className="text-left py-2 pr-4">Default</th>
            <th className="text-left py-2">Description</th>
          </tr>
        </thead>
        <tbody>
          {props.map((prop) => (
            <tr
              key={prop.name}
              className="border-b border-gray-100 dark:border-gray-800"
            >
              <td className="py-2 pr-4 font-mono text-xs">
                {prop.name}
                {prop.required && (
                  <span className="text-red-500 ml-1">*</span>
                )}
              </td>
              <td className="py-2 pr-4 font-mono text-xs text-purple-600 dark:text-purple-400">
                {prop.type}
              </td>
              <td className="py-2 pr-4 font-mono text-xs text-gray-500">
                {prop.default ?? "—"}
              </td>
              <td className="py-2 text-gray-700 dark:text-gray-300">
                {prop.description}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Blog Theme

npm install nextra nextra-theme-blog
// next.config.mjs
import nextra from "nextra";

const withNextra = nextra({
  theme: "nextra-theme-blog",
  themeConfig: "./theme.config.tsx",
});

export default withNextra({});
// theme.config.tsx
const config = {
  head: ({ title, meta }: { title: string; meta: Record<string, string> }) => (
    <>
      <meta name="author" content="Author Name" />
      {meta.description && (
        <meta name="description" content={meta.description} />
      )}
      {meta.tag && <meta name="keywords" content={meta.tag} />}
    </>
  ),
  footer: <small>CC BY-NC 4.0 {new Date().getFullYear()} Author Name</small>,
  readMore: "Read More →",
  darkMode: true,
};

export default config;

Best Practices

  • Organize _meta.json to control sidebar order and titles explicitly — do not rely on filesystem alphabetical ordering for user-facing navigation.
  • Use Nextra's built-in <Callout>, <Steps>, <Tabs>, and <Cards> components before building custom ones; they handle dark mode, accessibility, and responsive design.
  • Set docsRepositoryBase to enable "Edit this page" links, lowering the barrier for community contributions.
  • Use frontmatter searchable: false for pages that should not appear in search results (changelogs, legal pages).
  • Keep _meta.json files in every directory — without them, pages appear in alphabetical order with raw filenames as titles.
  • Enable defaultShowCopyCode in the Nextra config so all code blocks get copy buttons without per-block annotation.
  • Use the filename attribute on code blocks to show which file the code belongs to.
  • Deploy on Vercel for zero-config static export, or use next export for hosting on any static file server.

Anti-Patterns

  • Fighting the conventions: Nextra is opinionated by design. Trying to override the layout system extensively often results in fragile CSS hacks. If you need full layout control, use raw Next.js with MDX instead.
  • Nesting pages too deeply: More than 3 levels of nesting makes navigation unwieldy. Flatten the structure and use categories in _meta.json instead.
  • Skipping _meta.json: Without it, the sidebar shows raw filenames in alphabetical order. Always define display titles and ordering.
  • Using the Pages Router for new projects and expecting App Router features: Nextra v2 uses Pages Router. If you need App Router features, wait for Nextra v3 or consider fumadocs.
  • Overloading theme.config.tsx with business logic: Keep it declarative. Complex logic should live in separate components or hooks, not inline in the config object.
  • Ignoring built-in search: Nextra includes FlexSearch-based full-text search. Installing Algolia or another search provider without disabling the built-in one creates duplicate search experiences.
  • Not testing i18n fallbacks: If a translated page is missing, Nextra falls back to the default locale. Ensure fallback content makes sense and does not confuse users.
  • Large images in MDX files: Nextra does not automatically optimize images. Use next/image via a custom component or pre-optimize images before committing.

Install this skill directly: skilldb add seo-content-skills

Get CLI access →