Skip to content

Fix: Nextra Not Working — Pages Not Rendering, Sidebar Missing, or MDX Components Broken

FixDevs ·

Quick Answer

How to fix Nextra documentation site issues — Next.js integration, _meta.json sidebar configuration, custom MDX components, search setup, theme customization, and static export.

The Problem

Nextra pages render as blank or show a 404:

GET /docs/getting-started → 404 Not Found

Or the sidebar doesn’t show your pages:

Sidebar is empty even though MDX files exist in the pages directory

Or MDX components don’t render:

<Callout type="warning">Important note</Callout>
<!-- Renders as plain text instead of a styled callout -->

Or search returns no results:

Search box appears but typing returns "No results found"

Why This Happens

Nextra is a Next.js-based documentation framework that turns MDX files into a documentation site. It uses file-system routing with metadata files for navigation:

  • Nextra 3+ uses App Router — Nextra 3 moved from Pages Router to App Router. The file structure, configuration, and theme setup changed significantly. Using Nextra 2 patterns with Nextra 3 (or vice versa) causes pages to not render.
  • _meta.json controls the sidebar — each directory needs a _meta.json file that defines the order and titles of pages in the sidebar. Without it, pages exist but don’t appear in navigation. The file maps filenames (without extension) to display titles.
  • MDX components must be provided via mdx-components.tsx — Nextra’s built-in components (Callout, Tabs, Steps) need to be registered. In Nextra 3, this happens through the mdx-components.tsx file at the project root.
  • Search requires Flexsearch setup — Nextra’s built-in search uses Flexsearch to index page content at build time. If the search configuration is missing or the build step that generates the index fails, search returns empty results.

Fix 1: Set Up Nextra 3 (App Router)

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

const withNextra = nextra({
  // Nextra options
});

export default withNextra({
  // Next.js config
});
// mdx-components.tsx — project root (required for App Router)
import { useMDXComponents as getDocsMDXComponents } from 'nextra-theme-docs';

const docsComponents = getDocsMDXComponents();

export function useMDXComponents(components) {
  return {
    ...docsComponents,
    ...components,
  };
}
// app/layout.tsx
import { Footer, Layout, Navbar } from 'nextra-theme-docs';
import { Head } from 'nextra/components';
import { getPageMap } from 'nextra/page-map';

export const metadata = {
  title: { template: '%s — My Docs' },
  description: 'Documentation for My Project',
};

export default async function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <Head />
      <body>
        <Layout
          navbar={<Navbar logo={<span style={{ fontWeight: 800 }}>My Docs</span>} />}
          pageMap={await getPageMap()}
          docsRepositoryBase="https://github.com/user/repo/tree/main"
          footer={<Footer>MIT {new Date().getFullYear()} © My Project</Footer>}
        >
          {children}
        </Layout>
      </body>
    </html>
  );
}
# Directory structure
app/
├── layout.tsx
├── page.mdx              # Home page
├── docs/
│   ├── _meta.ts           # Sidebar config for docs/
│   ├── page.mdx           # /docs
│   ├── getting-started/
│   │   ├── _meta.ts
│   │   ├── page.mdx       # /docs/getting-started
│   │   └── installation/
│   │       └── page.mdx   # /docs/getting-started/installation
│   └── api-reference/
│       ├── _meta.ts
│       └── page.mdx

Fix 2: Sidebar Configuration with _meta

// app/docs/_meta.ts
export default {
  'getting-started': 'Getting Started',
  'api-reference': 'API Reference',
  'guides': 'Guides',
  '---': {
    type: 'separator',
    title: 'Advanced',
  },
  'architecture': 'Architecture',
  'contributing': 'Contributing',
  // External link
  'github': {
    title: 'GitHub',
    href: 'https://github.com/user/repo',
    newWindow: true,
  },
};

// app/docs/getting-started/_meta.ts
export default {
  index: 'Overview',           // page.mdx in this directory
  installation: 'Installation',
  configuration: 'Configuration',
  'quick-start': 'Quick Start',
};

// Hide a page from sidebar but keep it accessible
export default {
  'internal-page': {
    display: 'hidden',
  },
};

Fix 3: Built-In MDX Components

{/* app/docs/getting-started/page.mdx */}

import { Callout, Tabs, Steps, Cards, Card, FileTree } from 'nextra/components';

# Getting Started

## Prerequisites

<Callout type="info">
  You need Node.js 18+ installed on your machine.
</Callout>

<Callout type="warning">
  **Breaking change in v3:** The configuration format has changed. See the migration guide.
</Callout>

<Callout type="error">
  Do not use this in production without proper authentication.
</Callout>

## Installation

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

## Setup Steps

<Steps>
### Install dependencies

Run the install command for your package manager.

### Create config file

Create `my-config.ts` in the root of your project.

### Start the dev server

```bash
npm run dev

Project Structure

```

Fix 4: Theme Customization

// app/layout.tsx — theme configuration via Layout props
import { Layout, Navbar } from 'nextra-theme-docs';

<Layout
  navbar={
    <Navbar
      logo={
        <>
          <img src="/logo.svg" width={24} height={24} />
          <span style={{ marginLeft: '8px', fontWeight: 800 }}>My Docs</span>
        </>
      }
      projectLink="https://github.com/user/repo"
    />
  }
  sidebar={{ defaultMenuCollapseLevel: 1 }}
  toc={{ float: true, title: 'On This Page' }}
  editLink="Edit this page on GitHub"
  feedback={{ content: 'Question? Give us feedback' }}
  docsRepositoryBase="https://github.com/user/repo/tree/main"
>
  {children}
</Layout>
/* app/globals.css — custom styling */
:root {
  --nextra-primary-hue: 212;  /* Blue primary color */
  --nextra-primary-saturation: 100%;
}

/* Custom code block theme */
.nextra-code-block {
  border-radius: 8px;
}

Fix 5: Search Configuration

// next.config.mjs — enable search
import nextra from 'nextra';

const withNextra = nextra({
  search: {
    codeblocks: true,  // Index code blocks
  },
});

export default withNextra({});

Nextra’s search indexes content at build time using Flexsearch. If search returns empty:

  1. Run a full build: npm run build — the search index is generated during build
  2. In development, search may not index all pages until they’ve been visited
  3. Check that pages have text content — empty MDX files won’t appear in search

Fix 6: Static Export and Deployment

// next.config.mjs — static export for GitHub Pages, Cloudflare Pages, etc.
import nextra from 'nextra';

const withNextra = nextra({});

export default withNextra({
  output: 'export',
  images: { unoptimized: true },
  // For GitHub Pages subdirectory
  // basePath: '/my-repo',
});
# Build static site
npm run build

# Output is in the 'out' directory
# Deploy to any static host

# Cloudflare Pages
npx wrangler pages deploy out

# GitHub Pages — use GitHub Actions
# .github/workflows/deploy.yml
name: Deploy Docs
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      pages: write
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci && npm run build
      - uses: actions/upload-pages-artifact@v3
        with: { path: out }
      - uses: actions/deploy-pages@v4

Still Not Working?

Pages return 404 — in Nextra 3 (App Router), pages must be named page.mdx inside a directory, not filename.mdx directly. For example, /docs/getting-started requires app/docs/getting-started/page.mdx, not app/docs/getting-started.mdx.

Sidebar is empty — every directory that should appear in the sidebar needs a _meta.ts (or _meta.json) file. The keys in the meta file must match the subdirectory names (not file names). If using Nextra 2, the format is _meta.json in the pages/ directory.

MDX components render as plain text — the mdx-components.tsx file must exist at the project root and export useMDXComponents. Without it, custom components from nextra/components aren’t registered and render as undefined HTML elements.

Build fails with “Cannot find module ‘nextra-theme-docs’” — make sure both nextra and nextra-theme-docs are installed. If using Nextra 3, check you’re not accidentally importing from Nextra 2’s API paths. The import structure changed between versions.

For related documentation and content issues, see Fix: MDX Not Working and Fix: Astro 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