Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/shuding/nextra/llms.txt

Use this file to discover all available pages before exploring further.

nextra-theme-blog exports a focused set of components designed for content-first blog sites. Unlike the docs theme, there is no configuration object — every component is controlled through props. This page documents each named export from the package and shows how they compose into a full blog UI.

Imports

All components are available as named exports:
import {
  Layout,
  Navbar,
  Footer,
  PostCard,
  ThemeSwitch,
  Comments,
  useMDXComponents
} from 'nextra-theme-blog'

Renders a site header that auto-generates navigation links from the pageMap for all top-level pages, then renders any custom children after them.

Props

pageMap
PageMapItem[]
required
The full page map returned by getPageMap() from nextra/page-map. <Navbar> uses normalizePages internally to extract the top-level navigation items and render them as links.
import { getPageMap } from 'nextra/page-map'
const pageMap = await getPageMap()
// pass to: <Navbar pageMap={pageMap} />
children
ReactNode
Extra elements rendered after the auto-generated page links — useful for adding an RSS link, a theme toggle button, or an external link icon.
<Navbar pageMap={pageMap}>
  <ThemeSwitch />
  <a href="/rss.xml">RSS</a>
</Navbar>

Example

app/layout.tsx
import { Navbar } from 'nextra-theme-blog'
import { ThemeSwitch } from 'nextra-theme-blog'
import { getPageMap } from 'nextra/page-map'

export default async function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <Navbar pageMap={await getPageMap()}>
          <ThemeSwitch />
        </Navbar>
        {children}
      </body>
    </html>
  )
}

PostCard

Renders a single blog post preview card showing the post title (as a link), description with an optional “Read More” link, and the formatted publication date. Use this component in a post listing page to iterate over all posts from getPageMap.

Props

post
object
required
An object containing the post’s route and frontmatter.
readMore
string
default:"'Read More →'"
Text of the “read more” link appended after the description. Set to an empty string or undefined to suppress the link.

Example — post listing page

app/posts/page.tsx
import { PostCard } from 'nextra-theme-blog'
import { getPageMap } from 'nextra/page-map'

export default async function PostsPage() {
  const pageMap = await getPageMap('/posts')

  // Keep only MdxFile entries that have a date in frontmatter
  const posts = pageMap
    .filter(item => 'frontMatter' in item && item.frontMatter?.date)
    .sort(
      (a, b) =>
        new Date(b.frontMatter.date).getTime() -
        new Date(a.frontMatter.date).getTime()
    )

  return (
    <div>
      <h1>All Posts</h1>
      {posts.map(post => (
        <PostCard key={post.route} post={post} readMore="Continue reading →" />
      ))}
    </div>
  )
}
<PostCard> uses next-view-transitions’s <Link> internally, so navigating to a post will trigger a View Transition animation if the browser supports it and <Layout> is used.

ThemeSwitch

A client-side dark/light mode toggle button that uses next-themes under the hood. Renders a sun or moon icon depending on the currently resolved theme.
import { ThemeSwitch } from 'nextra-theme-blog'
ThemeSwitch takes no props — it reads and sets the theme entirely through next-themesuseTheme hook.

Example

import { ThemeSwitch } from 'nextra-theme-blog'

// Render inside Navbar or anywhere in your layout:
<Navbar pageMap={pageMap}>
  <ThemeSwitch />
</Navbar>
Unlike the docs theme’s ThemeSwitch, the blog ThemeSwitch is a simple icon button that toggles between 'dark' and 'light' — it does not have a 'system' option dropdown.

Comments

Integrates Cusdis — an open-source, privacy-focused comment system — into a blog post page. The component embeds the Cusdis iframe and automatically syncs the iframe’s theme with the page’s active color scheme.
import { Comments } from 'nextra-theme-blog'
<Comments> is a client component ('use client'). It must be used inside the client boundary — either in a client component or a page that does not need to be a Server Component.

Props

appId
string
required
Your Cusdis application ID from the Cusdis dashboard. The component logs a console warning and renders nothing when this prop is missing.
host
string
default:"'https://cusdis.com'"
The Cusdis host URL. Override this when self-hosting Cusdis on your own domain.

Example

app/posts/[slug]/page.tsx
import { Comments } from 'nextra-theme-blog'

export default function PostPage() {
  return (
    <article>
      {/* post content */}
      <Comments appId="your-cusdis-app-id" />
    </article>
  )
}

useMDXComponents

Returns the default MDX component map used by the blog theme. Pass this to your mdx-components.tsx file to apply the blog’s prose styles to all MDX content.
import { useMDXComponents } from 'nextra-theme-blog'

Usage

mdx-components.tsx
import { useMDXComponents as getBlogComponents } from 'nextra-theme-blog'
import type { MDXComponents } from 'mdx/types'

export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    ...getBlogComponents(components),
    // add or override individual components here
  }
}

ReadingTime type

The ReadingTime type is exported from the package for use with reading-time display utilities. It is automatically injected into each post’s frontMatter when the remark-reading-time plugin is configured.
import type { ReadingTime } from 'nextra-theme-blog'
text
string
Human-readable reading time, e.g. '3 min read'.
minutes
number
Decimal minutes, e.g. 3.2.
time
number
Reading time in milliseconds, e.g. 192000.
words
number
Total word count of the post, e.g. 640.

Display example

app/posts/[slug]/page.tsx
import type { ReadingTime } from 'nextra-theme-blog'

function ReadingTimeDisplay({ readingTime }: { readingTime: ReadingTime }) {
  return (
    <span className="text-sm text-gray-500">
      {readingTime.text} · {readingTime.words} words
    </span>
  )
}

Full post listing example

The following ties all blog components together in a realistic post listing page:
app/page.tsx
import { PostCard, ThemeSwitch } from 'nextra-theme-blog'
import { getPageMap } from 'nextra/page-map'

export default async function HomePage() {
  const pageMap = await getPageMap('/posts')

  const posts = pageMap
    .filter(item => 'frontMatter' in item && item.frontMatter?.date)
    .sort(
      (a, b) =>
        new Date(b.frontMatter.date).getTime() -
        new Date(a.frontMatter.date).getTime()
    )

  return (
    <>
      <h1>Latest Posts</h1>
      {posts.length === 0 && <p>No posts yet.</p>}
      {posts.map(post => (
        <PostCard key={post.route} post={post} />
      ))}
    </>
  )
}

useTheme()

Re-exported directly from next-themes. Returns the current theme state and a setter for toggling between themes in client components.
import { useTheme } from 'nextra-theme-blog'
'use client'
import { useTheme } from 'nextra-theme-blog'

export function ThemeIndicator() {
  const { resolvedTheme } = useTheme()
  return <span>Current theme: {resolvedTheme}</span>
}
useTheme is a convenience re-export. You can also import it directly from next-themes — the behaviour is identical. The <ThemeSwitch> component uses this hook internally to toggle between 'dark' and 'light'.

Build docs developers (and LLMs) love