Fix: Nextra Not Working — Pages Not Rendering, Sidebar Missing, or MDX Components Broken
Part of: React & Frontend Errors
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 FoundOr the sidebar doesn’t show your pages:
Sidebar is empty even though MDX files exist in the pages directoryOr 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.jsoncontrols the sidebar — each directory needs a_meta.jsonfile 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 themdx-components.tsxfile 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.
The version split between Nextra 2 (Pages Router) and Nextra 3 (App Router) is the single largest source of confusion. Tutorials, Stack Overflow answers, and even the official changelog all mix examples from both eras. When you copy a Nextra 2 pages/_meta.json into a Nextra 3 app/ directory, the file is read but ignored — Nextra 3 expects _meta.ts (or _meta.tsx) and walks getPageMap() instead of pages/. Pages still build because Next.js compiles the MDX, but the sidebar tree shows up empty and routes that should live under /docs/... either 404 or render with no chrome at all.
The other silent failure mode is the MDX components contract. In Nextra 3 the file mdx-components.tsx at the project root must export a useMDXComponents function that merges the theme’s component map with any overrides you add. Forget that file and <Callout> resolves to a literal <callout> HTML element, which React renders as empty text. There is no console error because, as far as the MDX runtime is concerned, you simply asked for an unknown component.
From a production incident lens this matters because a docs site is usually the canonical place engineers and customers go when something else is broken. If /docs/getting-started 404s during an outage, the support load on Slack and email spikes the moment the blast radius reaches public users. Treat the docs site as tier-1 infrastructure: alert on its uptime, smoke-test the build in CI, and gate every Nextra upgrade behind a preview deployment that you actually click through.
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.mdxFix 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 devProject Structure
Related Resources
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:
- Run a full build:
npm run build— the search index is generated during build - In development, search may not index all pages until they’ve been visited
- 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@v4Production Incident: Docs Site Build Fails, Users Hit 404
A typical on-call page for a Nextra failure starts with synthetic monitoring catching a 404 on /docs or with a customer reporting that the public docs URL is dead. The first thing to do is separate two failure modes: a build that never produced output, and a build that produced output without the expected pages.
Run the build locally against the same commit your deploy pipeline used:
git checkout <deployed-sha>
npm ci
npm run build
ls -la out/docs/getting-started/If the build fails on the deployed SHA, your pipeline shipped a previous artifact and the next deploy will go fully dark unless you fix the underlying error first. If the build succeeds locally but the directory is empty, you have a content mismatch — usually a missing page.mdx rename or a _meta.ts that points at a slug that no longer exists.
Roll forward with a known-good artifact while you investigate. With Cloudflare Pages or Vercel, redeploy the last successful build from the dashboard. Do not try to fix Nextra under time pressure on main — the diagnostic loop is too long because every change requires a full Next.js compile. Once you have a static out/ directory deployed, debug at leisure in a branch.
The most common root cause in real incidents is a contributor moving a page from docs/getting-started.mdx to docs/getting-started/page.mdx (the Nextra 3 convention) without updating sibling _meta.ts files. The build doesn’t fail — it just drops the page from the sidebar and serves a 404 at the old URL. Add a CI check that runs next build plus a curl smoke test against every URL listed in your sitemap.
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.
Sitemap is missing pages after deploy — Nextra’s sitemap is generated from getPageMap() at build time. If a page is in the file tree but missing from _meta.ts, it may be excluded from the sitemap even though the route is reachable. Run next build locally and inspect out/sitemap.xml against your expected URLs. Add the missing entries to _meta.ts with explicit titles.
Search returns results from old content — the Flexsearch index is generated during npm run build and includes every MDX file in the build output. If you renamed or deleted a page but the old MDX file is still committed, it stays in the index. Run a clean build with rm -rf .next out first, then redeploy. For Cloudflare Pages, also purge the cache after the deploy completes.
MDX components render server-side but break on hydration — in Nextra 3, components like <Tabs> rely on client state. If you wrap them in a server-only context (e.g., a Server Component without 'use client'), hydration mismatches throw errors in production but only warnings in dev. Mark interactive MDX wrappers with 'use client' or move them inside a client boundary. See Fix: React Hydration Error for the underlying pattern.
For related documentation and content issues, see Fix: MDX Not Working, Fix: Docusaurus Not Working, and Fix: Astro Content Collections 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: 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.
Fix: Chakra UI Not Working — Provider Missing, Styles Not Applied, or Dark Mode Broken
How to fix Chakra UI v3 issues — ChakraProvider setup, color mode, theming, server-side rendering in Next.js, component styling, and common runtime errors.
Fix: Clerk Not Working — Auth Not Loading, Middleware Blocking, or User Data Missing
How to fix Clerk authentication issues — ClerkProvider setup, middleware configuration, useUser and useAuth hooks, server-side auth, webhook handling, and organization features.
Fix: Contentlayer Not Working — Content Not Generated, Types Missing, or Build Errors
How to fix Contentlayer and Contentlayer2 issues — content source configuration, document type definitions, MDX processing, computed fields, Next.js integration, and migration to alternatives.