)
}
================================================
FILE: docs/app/_components/overview-page.tsx
================================================
import { useMDXComponents as getMDXComponents } from 'next-mdx-import-source-file'
import type { PageMapItem } from 'nextra'
import { Cards } from 'nextra/components'
import { getIndexPageMap, getPageMap } from 'nextra/page-map'
import type { FC } from 'react'
export const OverviewPage: FC<{
filePath: string
icons?: Record
pageMap?: PageMapItem[]
}> = async ({ filePath, icons, pageMap: $pageMap }) => {
const { h2: H2 } = getMDXComponents()
const currentRoute = filePath.replace('app', '').replace('/page.mdx', '')
const pageMap = $pageMap ?? (await getPageMap(currentRoute))
return getIndexPageMap(pageMap).map((pageItem, index) => {
if (!Array.isArray(pageItem)) {
return
{pageItem.title}
}
return (
{pageItem.map(item => {
const icon = item.frontMatter?.icon
const Icon = icons?.[icon]
if (icon && !Icon) {
throw new Error(
`Icon "${icon}" is defined in front matter but isn't provided`
)
}
return (
// @ts-expect-error -- fixme
}
/>
)
})}
)
})
}
================================================
FILE: docs/app/_meta.global.tsx
================================================
import type { MetaRecord } from 'nextra'
import { LinkArrowIcon } from 'nextra/icons'
import type { FC, ReactNode } from 'react'
import { useMDXComponents } from '../mdx-components'
// eslint-disable-next-line react-hooks/rules-of-hooks -- isn't react hook
const { code: Code } = useMDXComponents()
const ExternalLink: FC<{ children: ReactNode }> = ({ children }) => {
return (
<>
{children}
>
)
}
const FILE_CONVENTIONS: MetaRecord = {
_: {
type: 'separator',
title: 'Files'
},
'page-file': 'page.mdx',
'meta-file': '_meta.js',
_2: {
href: 'https://nextjs.org/docs/app/api-reference/file-conventions/page',
title: page.jsx
},
_3: {
href: 'https://nextjs.org/docs/app/api-reference/file-conventions/layout',
title: layout.jsx
},
_4: {
type: 'separator',
title: 'Top-Level Files'
},
'mdx-components-file': 'mdx-components.js',
_5: {
type: 'separator',
title: 'Top-Level Folders'
},
'content-directory': 'content',
'src-directory': 'src',
_6: {
href: 'https://nextjs.org/docs/app/getting-started/installation?utm_source=nextra.site&utm_medium=referral&utm_campaign=sidebar#create-the-app-directory',
title: app
},
_7: {
href: 'https://nextjs.org/docs/app/building-your-application/optimizing/static-assets?utm_source=nextra.site&utm_medium=referral&utm_campaign=sidebar',
title: public
}
}
const GUIDE: MetaRecord = {
markdown: '',
'syntax-highlighting': '',
link: '',
image: '',
ssg: '',
i18n: '',
'custom-css': '',
'static-exports': '',
search: {
items: {
index: '',
ai: {
title: Ask AI
}
},
theme: {
collapsed: false
}
},
'github-alert-syntax': '',
turbopack: '',
_: {
title: 'Deploying',
href: 'https://nextjs.org/docs/app/building-your-application/deploying?utm_source=nextra.site&utm_medium=referral&utm_campaign=sidebar'
}
}
const ADVANCED: MetaRecord = {
npm2yarn: '',
mermaid: '',
'tailwind-css': '',
latex: '',
table: '',
typescript: '',
remote: ''
}
const BLOG_THEME: MetaRecord = {
start: '',
'get-posts-and-tags': '',
// prettier-ignore
posts: <>/posts Page>,
// prettier-ignore
tags: <>/tags/:id Page>,
// prettier-ignore
rss: <>/rss.xml Route>
}
export default {
index: {
type: 'page',
display: 'hidden'
},
docs: {
type: 'page',
title: 'Documentation',
items: {
index: '',
'file-conventions': { items: FILE_CONVENTIONS },
guide: { items: GUIDE },
advanced: { items: ADVANCED },
'built-ins': '',
_: {
type: 'separator',
title: 'Themes'
},
'docs-theme': {
items: {
start: '',
'built-ins': {
items: {
layout: ''
}
}
}
},
'blog-theme': { items: BLOG_THEME },
'custom-theme': '',
__: {
type: 'separator',
title: 'More'
},
'about-link': {
title: 'About Nextra',
href: '/about'
},
'next.js-link': {
title: 'Next.js Docs',
href: 'https://nextjs.org?utm_source=nextra.site&utm_medium=referral&utm_campaign=sidebar'
},
'migration-from-v3': {
title: 'Migration from Nextra v3',
href: 'https://the-guild.dev/blog/nextra-4?utm_source=nextra.site&utm_campaign=sidebar&utm_content=sidebar_link#nextra-theme-docs-changes'
}
}
},
api: {
type: 'page'
},
versions: {
type: 'menu',
title: 'Versions',
items: {
_3: {
title: 'Nextra v3 Docs',
href: 'https://nextra-v2-7hslbun8z-shud.vercel.app'
},
_2: {
title: 'Nextra v2 Docs',
href: 'https://nextra-v2-oe0zrpzjp-shud.vercel.app'
}
}
},
blog: {
type: 'page',
theme: {
typesetting: 'article',
toc: false
}
},
about: {
type: 'page',
theme: {
typesetting: 'article'
}
},
showcase: {
type: 'page',
theme: {
copyPage: false,
typesetting: 'article',
layout: 'full',
timestamp: false,
toc: false
}
},
sponsors: {
type: 'page',
theme: {
copyPage: false,
typesetting: 'article',
layout: 'full',
timestamp: false,
toc: false
}
}
}
================================================
FILE: docs/app/about/page.mdx
================================================
---
sidebarTitle: About
description:
Learn about Nextra's history, team, and contributors, and explore how
open-source technologies power Nextra's features.
---
import { Image } from 'nextra/components'
import { cloneElement } from 'react'
export default function MdxLayout(props) {
return cloneElement(props.children, {
components: {
img: props =>
}
})
}
# About Nextra
Nextra was initially created by [Vercel](https://vercel.com) members
[Shu Ding](https://twitter.com/shuding_) and
[Paco Coursey](https://twitter.com/pacocoursey) in 2020. Since 2021,
[Yixuan Xu](https://twitter.com/yixuanxu94) contributed tremendously to the
project.
In 2022, [Dimitri Postolov](https://twitter.com/dimaMachina_) from
[The Guild](https://the-guild.dev) joined the core team to help with the
development of Nextra 2.
In 2024 Nextra 3 was released, current primary maintainer Dimitri Postolov fully
developed it, and [Oscar Xie](https://github.com/87xie)
[actively contributed](https://github.com/shuding/nextra/pulls?q=sort%3Aupdated-desc+is%3Apr+author%3A87xie+is%3Aclosed+created%3A%3C2024-10-03)
to this release.
In 2025 Nextra 4 with [App Router](https://nextjs.org/docs/app) support was
released, Dimitri Postolov fully developed it too.
## Team
Currently, the project is maintained by Dimitri Postolov. You can check out
[the full list of contributors](https://github.com/shuding/nextra/graphs/contributors)
on GitHub.
## Credits
Nextra is powered by these incredible open source projects:
- https://reactjs.org
- https://nextjs.org
- https://turbo.build
- https://mdxjs.com
- https://pnpm.io
- https://tailwindcss.com
- https://github.com/pacocoursey/next-themes
- https://github.com/shikijs/shiki
- https://github.com/cloudcannon/pagefind
- https://github.com/atomiks/rehype-pretty-code
- https://github.com/Brooooooklyn/simple-git
- https://github.com/francoismassart/eslint-plugin-tailwindcss
## Design assets
Feel free to use the Nextra logo and other assets in your project. But please
don't modify the logo, and don't use the logo to represent your project or
product.
| Name | Description | Preview |
| :---------: | :----------------------------------------------: | :-----------------------------------: |
| Icon | Useful for favicons, app icons, link icons, etc. |  |
| Logo | Full Nextra logo |  |
| Social Card | The Nextra social card |  |
## License
The Nextra project and themes are licensed under
[the MIT license](https://github.com/shuding/nextra/blob/main/LICENSE).
================================================
FILE: docs/app/api/[name]/page.tsx
================================================
import path from 'node:path'
import { generateApiReference } from '@components/generate-api-reference'
import type { ApiReference } from '@components/generate-api-reference'
import { useMDXComponents as getMDXComponents } from 'next-mdx-import-source-file'
import type { MdxFile } from 'nextra'
import { generateDefinition } from 'nextra/tsdoc'
import type { FC } from 'react'
type AllApiReference = ApiReference & { filePath?: string }
const API_REFERENCE: (
| AllApiReference
| { type: 'separator'; title: string; name: string }
)[] = [
{ type: 'separator', title: 'Types', name: '_' },
{
name: 'NextraConfig',
packageName: 'nextra',
isFlattened: false,
filePath: 'packages/nextra/src/server/schemas.ts'
},
{
name: 'MdxOptions',
code: `import type { NextraConfig } from 'nextra'
type $ = NonNullable
export default $`,
isFlattened: false,
filePath: 'packages/nextra/src/server/schemas.ts'
},
{ type: 'separator', title: 'Functions', name: '_2' },
{
name: 'nextra',
code: "export { default } from 'nextra'",
isFlattened: false
},
{
name: 'useMDXComponents',
packageName: 'nextra/mdx-components',
isFlattened: false
},
{ name: 'getPageMap', packageName: 'nextra/page-map' },
{ name: 'generateStaticParamsFor', packageName: 'nextra/pages' },
{ name: 'importPage', packageName: 'nextra/pages' },
{ name: 'compileMdx', packageName: 'nextra/compile' },
{ name: 'generateDefinition', packageName: 'nextra/tsdoc' },
{ name: 'proxy', packageName: 'nextra/locales' },
{ name: 'evaluate', packageName: 'nextra/evaluate' },
{ name: 'normalizePages', packageName: 'nextra/normalize-pages' }
]
const routes = API_REFERENCE.filter((o): o is AllApiReference => !('type' in o))
const separatorIndex = API_REFERENCE.findIndex(
o => 'title' in o && o.title === 'Functions'
)
const functionsIndex = routes.indexOf(
API_REFERENCE[separatorIndex + 1] as AllApiReference
)
export const generateStaticParams = () =>
routes.map(o => ({ name: o.name.toLowerCase() }))
// @ts-expect-error -- fixme
export const pageMap: (MdxFile & { title: string })[] = API_REFERENCE.map(o =>
'type' in o
? o
: {
name: o.name.toLowerCase(),
route: `/api/${o.name.toLowerCase()}`,
title: o.name
}
)
const Wrapper = getMDXComponents().wrapper
async function getReference(props: PageProps) {
const params = await props.params
const apiRefIndex = routes.findIndex(
o => o.name.toLowerCase() === params.name
)
const apiRef = routes[apiRefIndex]
if (!apiRef) {
throw new Error(`API reference not found for "${params.name}"`)
}
const isType = functionsIndex > apiRefIndex
const definition = generateDefinition({
code:
apiRef.code ??
`export { ${apiRef.name} as default } from '${apiRef.packageName}'`,
flattened: apiRef.isFlattened !== false
})
const result = await generateApiReference(apiRef, {
title: isType ? 'Type' : 'Function',
subtitle: isType ? 'Fields' : 'Signature',
definition
})
const filePath =
definition.filePath &&
path
.relative('..', definition.filePath)
.replace(/\.d.ts$/, '.ts')
.replace('/dist/', '/src/')
// Add edit on GitHub link to points on a source file
result.metadata.filePath = `https://github.com/shuding/nextra/tree/main/${apiRef.filePath ?? filePath}`
return result
}
export async function generateMetadata(props: PageProps) {
const { metadata } = await getReference(props)
return metadata
}
type PageProps = Readonly<{
params: Promise<{ name: string }>
}>
const Page: FC = async props => {
const {
default: MDXContent,
toc,
metadata,
sourceCode
} = await getReference(props)
return (
)
}
export default Page
================================================
FILE: docs/app/api/page.mdx
================================================
import { Callout, Cards } from 'nextra/components'
import { MDXRemote } from 'nextra/mdx-remote'
import { createIndexPage } from 'nextra/page-map'
import { pageMap } from './[name]/page'
# API
This API reference is automatically generated from the catch-all route file
`/api/[name]/page.tsx` using the new [Nextra ``
component](/docs/built-ins/tsdoc).
================================================
FILE: docs/app/blog/page.mdx
================================================
---
asIndexPage: true
description:
Stay updated with the latest news and updates from the Nextra team, including
new releases, features, and community highlights.
---
import { Link } from 'nextra-theme-docs'
# Blog
export function BlogPage() {
return [
{
route:
'https://the-guild.dev/blog/nextra-4?utm_source=nextra.site&utm_campaign=blog_page&utm_content=blog_link',
title: 'Nextra 4',
description:
'App Router support, Turbopack support, compiled by React Compiler, new Rust-powered search Pagefind, RSC i18n, server/client components, smallest bundle size EVER for a Nextra-powered website, GitHub Alert Syntax, new _meta.global file and more.',
date: '2024-01-13'
},
{
route:
'https://the-guild.dev/blog/nextra-3?utm_source=nextra.site&utm_campaign=blog_page&utm_content=blog_link',
title: 'Nextra 3 – Your Favourite MDX Framework, Now on 🧪 Steroids',
description:
'MDX 3, new i18n, new _meta files with JSX support, more powerful TOC, remote MDX, better bundle size, MathJax, new code block styles, shikiji, ESM-only and more.',
date: '2023-12-12'
},
{
route:
'https://the-guild.dev/blog/nextra-2?utm_source=nextra.site&utm_campaign=blog_page&utm_content=blog_link',
title: 'Nextra 2 – Next.js Static Site Generator',
description:
'Here are what the new version of Nextra 2 Framework includes.',
date: '2023-01-24'
}
].map(page => (
{page.title}
{page.description}{' '}
{page.date && Read more}
{page.date ? (
) : (
Coming soon!
)}
))
}
================================================
FILE: docs/app/docs/advanced/customize-the-cascade-layers/page.mdx
================================================
---
sidebarTitle: Customize Cascade Layers
---
import { Steps } from 'nextra/components'
# Customize the Cascade Layers
In some scenarios, you may need more control over the Nextra predefined CSS to
avoid unintended overrides of styles within cascade layers. Below is an example
of how `nextra-theme-docs` uses
[postcss-import](https://github.com/postcss/postcss-import) to place predefined
CSS into a specified cascade layer:
## Install `postcss-import`
Install `postcss-import` and add it to `postcss.config.mjs`:
```js filename="postcss.config.mjs"
export default {
plugins: {
'postcss-import': {}
// ... your other PostCSS plugins (e.g., `autoprefixer`, `cssnano`)
}
}
```
## Set up the cascade layers
In your CSS file (e.g. `styles.css`), import the `nextra-docs-theme` CSS and
specify the layers:
```css filename="styles.css"
@layer nextra, my-base;
@import 'nextra-theme-docs/dist/style.css' layer(nextra);
@layer my-base {
/* my base styles */
}
```
## Import your CSS file
Import your CSS file at the top-level layout of your application (e.g.
`app/layout.jsx`) to apply the styles.
```jsx filename="app/layout.jsx"
import '../path/to/your/styles.css'
export default async function RootLayout({ children }) {
// ... Your layout logic here
}
```
================================================
FILE: docs/app/docs/advanced/latex/page.mdx
================================================
---
icon: FormulaIcon
---
import { compileMdx } from 'nextra/compile'
import {
Callout,
MathJax,
MathJaxContext,
Steps,
Tabs
} from 'nextra/components'
import { MDXRemote } from 'nextra/mdx-remote'
{/* is unsupported in Metadata API https://nextjs.org/docs/app/api-reference/functions/generate-metadata#unsupported-metadata */}
# LaTeX
Nextra can use [KaTeX](https://katex.org) to pre-render LaTeX expressions
directly in MDX or [MathJax](https://mathjax.org) to dynamically render math in
the browser.
## Setup
### Enable the `latex` option
By default, LaTeX is disabled. To enable it, you need to set the `latex` option
in your `next.config.mjs` file:
```js filename="next.config.mjs" {4}
import nextra from 'nextra'
const withNextra = nextra({
latex: true
})
export default withNextra()
```
A value of `true{:js}` will use KaTeX as the math renderer. To explicitly
specify the renderer, you may instead provide an object
`{ renderer: 'katex' }{:js}` or `{ renderer: 'mathjax' }{:js}` as the value to
`latex: ...`.
When enabled, the required CSS and fonts will be automatically included in your
site, and you can start writing math expressions by enclosing inline math in
`$...$` or display math in a `math`-labeled fenced code block:
````mdx
```math
\int x^2
```
````
### Apply styles
This is applicable only to KaTeX as the math renderer.
1. Install the `katex` package
```bash npm2yarn
npm i katex
```
2. Import CSS in the root layout
```js filename="app/layout.jsx"
import 'katex/dist/katex.min.css'
```
Add `{:jsx}`
inside `` element in your root `layout` file since `{:jsx}`
[isn't supported with Next.js Metadata API](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#unsupported-metadata).
Alternatively, you can include `{:jsx}` directly in
your MDX file:
````mdx filename="katex.mdx"
# My page with single usage of KaTeX
```math
\int_2^3x^3\,\mathrm{d}x
```
````
## Example
For example, the following Markdown code:
````md filename="page.md"
The **Pythagorean equation** is $a=\sqrt{b^2 + c^2}$ and the quadratic formula:
```math
x=\frac{-b\pm\sqrt{b^2-4ac}}{2a}
```
````
will be rendered as:
The **Pythagorean equation** is $a=\sqrt{b^2 + c^2}$ and the quadratic formula:
```math
x=\frac{-b\pm\sqrt{b^2-4ac}}{2a}
```
You can still use [Markdown and MDX syntax](../guide/markdown) in the same line
as your LaTeX expression.
> [!TIP]
>
> If you want to display `$` in your content instead of rendering it as an
> equation, you can escape it with a backslash (`\`). For example `\$e = mc^2\$`
> will be rendered as \$e = mc^2\$.
## API
### KaTeX
`rehype-katex` is used to pre-render LaTeX expressions in your content. You can
pass supported [KaTeX options](https://katex.org/docs/options) via the `options`
key in your Nextra config. For example, to add a macro `\RR` that renders as
`\mathbb{R}` you could use the following configuration.
```js filename="next.config.mjs" {4-8}
const withNextra = nextra({
latex: {
renderer: 'katex',
options: {
macros: {
'\\RR': '\\mathbb{R}'
}
}
}
})
```
See [KaTeX's documentation](https://katex.org/docs/supported) for a list of
supported commands.
### MathJax
When MathJax is enabled (by setting `latex: { renderer: 'mathjax' }{:js}`) math
is rendered on page load via
[`better-react-mathjax`](https://github.com/fast-reflexes/better-react-mathjax)
instead of being pre-rendered. By default, **MathJax is served via the MathJax
CDN** instead of the files being directly included in your site.[^1]
[^1]:
This can be changed by setting
[`{ options: { src: ... } }{:js}`](https://github.com/fast-reflexes/better-react-mathjax#src-string--undefined)
in the Nextra config.
MathJax rendering is enabled by setting `renderer: 'mathjax'{:js}` in your
Nextra config.
```js filename="next.config.mjs" {3}
const withNextra = nextra({
latex: {
renderer: 'mathjax'
}
})
```
You can pass additional options to `better-react-mathjax` via the `options` key
in your Nextra config. The `config: ...` option sets the
[MathJax configuration](https://docs.mathjax.org/en/latest/options/index.html).
However, note that you can only pass serializable options to
`better-react-mathjax` via the `options` key in your Nextra config.[^2]
[^2]:
To pass non-serializable objects like Functions, you must use the
`{:jsx}` component directly in your source.
For example, to configure MathJax to render `\RR` as `\mathbb{R}` you could use
the following configuration.
```js filename="next.config.mjs" {4-12}
const withNextra = nextra({
latex: {
renderer: 'mathjax',
options: {
config: {
tex: {
macros: {
RR: '\\mathbb{R}'
}
}
}
}
}
})
```
#### MathJax CDN
By default, MathJax is served via the MathJax CDN. To serve files from another
location (including locally in your project), you must pass the `src: ...`
option to the latex config. See the
[better-react-mathjax documentation](https://github.com/fast-reflexes/better-react-mathjax#src-string--undefined)
for details about the `src` option. Additionally, you may need to copy the
MathJax distribution into your `/public` folder for it to be served locally.
## KaTeX vs. MathJax
With KaTeX, math is pre-rendered which means flicker-free and faster page loads.
However, KaTeX does not support all the features of MathJax, especially features
related to accessibility.
The following two examples show the same formula rendered with KaTeX (first) and
MathJax (second).
```math
\int_2^3x^3\,\mathrm{d}x
```
Because of MathJax's accessibility features, the second formula is
tab-accessible and has a context menu that helps screen readers reprocess math
for the visually impaired.
export async function MathJaxExample() {
const rawMdx = `~~~math
\\int_2^3x^3\\,\\mathrm{d}x
~~~`
const rawJs = await compileMdx(rawMdx, {
latex: {
renderer: 'mathjax',
options: {
config: {
tex: {
macros: {
RR: '\\mathbb{R}'
}
}
}
}
}
})
return (
)
}
================================================
FILE: docs/app/docs/advanced/mermaid/page.mdx
================================================
---
icon: DiagramIcon
---
import { compileMdx } from 'nextra/compile'
import { Mermaid } from 'nextra/components'
import { MDXRemote } from 'nextra/mdx-remote'
# Mermaid
Nextra supports [mermaid](https://mermaid.js.org) diagrams. Like in GitHub you
can use it in your Markdown files by using the `mermaid` code block language.
Out of the box, Nextra uses
[`@theguild/remark-mermaid`](https://npmjs.com/package/@theguild/remark-mermaid)
package that replaces the code block with the `` component.
## Example
export async function Demo() {
const mermaidCodeblock = `\`\`\`mermaid
graph TD;
subgraph AA [Consumers]
A[Mobile app];
B[Web app];
C[Node.js client];
end
subgraph BB [Services]
E[REST API];
F[GraphQL API];
G[SOAP API];
end
Z[GraphQL API];
A --> Z;
B --> Z;
C --> Z;
Z --> E;
Z --> F;
Z --> G;
\`\`\``
const rawJs = await compileMdx(`${mermaidCodeblock}
## Usage
~~~md filename="Markdown"
${mermaidCodeblock}
~~~
`)
return
}
================================================
FILE: docs/app/docs/advanced/npm2yarn/page.mdx
================================================
---
icon: TerminalIcon
---
import { compileMdx } from 'nextra/compile'
import { Tabs } from 'nextra/components'
import { MDXRemote } from 'nextra/mdx-remote'
# Npm2Yarn
Nextra uses
[`@theguild/remark-npm2yarn`](https://npmjs.com/package/@theguild/remark-npm2yarn)
package that replaces the code block that has `npm2yarn` metadata with
[`` and `` components](/docs/built-ins/tabs) from
`nextra/components`.
The chosen tab is saved in the local storage, which will be chosen in future
page renders.
## Example
export async function Page() {
const codeBlock = `\`\`\`sh npm2yarn
npm i -D @graphql-eslint/eslint-plugin
\`\`\``
const rawJs = await compileMdx(`${codeBlock}
## Usage
~~~md filename="Markdown" /npm2yarn/
${codeBlock}
~~~`)
return
}
================================================
FILE: docs/app/docs/advanced/page.mdx
================================================
---
asIndexPage: true
---
import {
CloudIcon,
DiagramIcon,
FormulaIcon,
TableIcon,
TailwindIcon
} from '@components/icons'
import { TerminalIcon, TypeScriptIcon } from 'nextra/icons'
import { OverviewPage } from '../../_components/overview-page'
# Advanced
================================================
FILE: docs/app/docs/advanced/remote/page.mdx
================================================
---
icon: CloudIcon
---
import fs from 'node:fs/promises'
import { compileMdx } from 'nextra/compile'
import { Steps } from 'nextra/components'
import { MDXRemote } from 'nextra/mdx-remote'
export async function Example() {
const filename = '/graphql-eslint/[[...slug]]/page.tsx'
const pageContent = await fs.readFile(
`../examples/swr-site/app/[lang]/${filename}`,
'utf8'
)
const rawJs = await compileMdx(
`~~~jsx filename="app${filename}" {27} showLineNumbers
${pageContent
.replace(
"lang: 'en',\n ...(route && { slug: route.split('/') })",
"slug: route.split('/')"
)
.trimEnd()}
~~~`,
{ defaultShowCopyCode: true }
)
return
}
# Remote Content
> [!NOTE]
>
> You can check out the
> [SWR i18n example](https://github.com/shuding/nextra/blob/main/examples/swr-site/app/%5Blang%5D/graphql-eslint/%5B%5B...slug%5D%5D/page.tsx)
> source code.
## Create `[[...slug]]/page.tsx` file
Create `[[...slug]]/page.tsx` file in `app/` directory with the following
content:
## Enhance `pageMap`
You need to modify `pageMap` list in `layout` file, to properly display sidebar
and mobile navigation.
```tsx filename="app/layout.tsx"
import { getPageMap } from 'nextra/page-map'
import { pageMap as graphqlEslintPageMap } from './graphql-eslint/[[...slug]]/page'
// ...
const pageMap = [...(await getPageMap()), graphqlEslintPageMap]
```
================================================
FILE: docs/app/docs/advanced/table/page.mdx
================================================
---
icon: TableIcon
---
# Rendering Tables
This guide covers different ways to render tables in MDX, including GFM syntax
and literal HTML tag.
## GFM syntax
In Markdown, it is preferable to write tables via
[GFM syntax](https://github.github.com/gfm/#tables-extension-).
```mdx filename="MDX"
| left | center | right |
| :----- | :----: | ----: |
| foo | bar | baz |
| banana | apple | kiwi |
```
will be rendered as:
| left | center | right |
| :----- | :----: | ----: |
| foo | bar | baz |
| banana | apple | kiwi |
## Literal HTML tables
If you try to render table with literal HTML elements `
{:js}`,
`{:js}`, `{:js}`, `
{:js}`, `
{:js}` and `
{:js}` – your
table will be unstyled because
[MDX](https://mdxjs.com/docs/using-mdx/#components) doesn't replace literal HTML
elements with components provided by `useMDXComponents(){:js}`[^1].
> [!TIP]
>
> Instead, use the [built-in `
` component](/docs/built-ins/table)
> available via `nextra/components`.
## Changing default behaviour
If you want to use standard HTML elements for your tables but have them styled
with components provided by `useMDXComponents(){:js}`[^1], you can do this by
configuring Nextra.
To achieve this, pass the `whiteListTagsStyling` option to the Nextra function,
including an array of tags you want to replace.
Here's an example configuration in your `next.config.mjs` file:
```js filename="next.config.mjs" {4}
import nextra from 'nextra'
const withNextra = nextra({
whiteListTagsStyling: ['table', 'thead', 'tbody', 'tr', 'th', 'td']
})
export default withNextra()
```
In this example, the tags `
`, ``, ``, `
`, `
`, and
`
` will be replaced with corresponding MDX components, allowing for
customized styling.
[^1]: https://mdxjs.com/packages/react/#usemdxcomponentscomponents
================================================
FILE: docs/app/docs/advanced/tailwind-css/page.mdx
================================================
---
icon: TailwindIcon
---
import { Steps } from 'nextra/components'
# Tailwind CSS
Tailwind CSS is a CSS framework that provides a set of pre-defined CSS classes
to quickly style elements.
## Follow the official guide
Follow the official
[Tailwind CSS guide for Next.js](https://tailwindcss.com/docs/guides/nextjs) to
set up Tailwind CSS for your project.
## Create the `globals.css` file
Import Tailwind CSS into `globals.css`:
```css filename="globals.css"
@import 'tailwindcss';
/* Optional: import Nextra theme styles */
@import 'nextra-theme-docs/style.css'; /* or nextra-theme-blog/style.css */
@variant dark (&:where(.dark *));
```
## Import styles in the root layout
To apply the styles globally, import the `globals.css` file in your root layout
file:
```jsx filename="app/layout.jsx"
import '../path/to/your/globals.css'
export default async function RootLayout({ children }) {
// ... Your layout logic here
}
```
================================================
FILE: docs/app/docs/advanced/twoslash/page.mdx
================================================
# Twoslash Support
Twoslash provides an inline type hove inside the code block.
## Basic usage
You can enable twoslash to your code blocks by adding a `twoslash` metadata:
{/* prettier-ignore */}
````md copy=false filename="Markdown"
```ts twoslash
// @errors: 2540
interface Todo {
title: string
}
const todo: Readonly = {
title: 'Delete inactive users'.toUpperCase()
// ^?
}
todo.title = 'Hello'
Number.parseInt('123', 10)
// ^|
// Just comments, so Popup will be
// not behind the viewport of ``
// element due his `position: absolute` style
//
```
````
Renders:
{/* prettier-ignore */}
```ts twoslash
// @errors: 2540
interface Todo {
title: string
}
const todo: Readonly = {
title: 'Delete inactive users'.toUpperCase()
// ^?
}
todo.title = 'Hello'
Number.parseInt('123', 10)
// ^|
```
## Custom log message
You can add log message to your code by adding:
- `@log: ` Custom log message
- `@error: ` Custom error message
- `@warn: ` Custom warn message
- `@annotate: ` Custom annotate message
```ts twoslash
// @log: Custom log message
const a = 1
// @error: Custom error message
const b = 1
// @warn: Custom warning message
const c = 1
// @annotate: Custom annotation message
```
================================================
FILE: docs/app/docs/advanced/typescript/page.mdx
================================================
---
icon: TypeScriptIcon
---
import { Steps } from 'nextra/components'
# TypeScript
Nextra is built with TypeScript and provides excellent TypeScript support out of
the box. This guide will help you leverage TypeScript in your Nextra project.
## Getting started
To use TypeScript in your Nextra project, you need to:
### Install TypeScript and types packages as `devDependencies`
```sh npm2yarn
npm i -D typescript @types/react @types/node
```
### `tsconfig.json`
You can manually create a `tsconfig.json` file in the root of your project or
rename the extension of some of the existing files to `.ts` or `.tsx` and then
Next.js will detect TypeScript in your project and create a `tsconfig.json` file
for you.
## Type definitions
Nextra provides type definitions for distribution code for its components and
configurations. You can leverage these types by renaming your theme
configuration file to `.ts` or `.tsx` extension and importing a theme config
type, e.g. for `nextra-theme-docs`:
```tsx filename="theme.config.tsx"
import type { DocsThemeConfig } from 'nextra-theme-docs'
const config: DocsThemeConfig = {
// Your theme configuration
}
export default config
```
By leveraging TypeScript in your Nextra project, you can catch errors early,
improve code quality, and enhance the developer experience with better
autocompletion and type inference.
================================================
FILE: docs/app/docs/blog-theme/get-posts-and-tags/page.mdx
================================================
---
icon: FilesIcon
---
import { ExampleCode } from 'components/example-code'
# Get Posts and Their Tags
The following code snippet demonstrates how to retrieve all posts along with
their associated tags.
================================================
FILE: docs/app/docs/blog-theme/page.mdx
================================================
---
asIndexPage: true
sidebarTitle: Blog Theme
---
import {
ChevronRightIcon,
FilesIcon,
RSSIcon,
TagsIcon
} from '@components/icons'
import { FileIcon } from 'nextra/icons'
import { OverviewPage } from '../../_components/overview-page'
# Nextra Blog Theme
================================================
FILE: docs/app/docs/blog-theme/posts/page.mdx
================================================
---
icon: FileIcon
---
import { ExampleCode } from 'components/example-code'
# Posts Page
The following code snippet demonstrates how to create `/posts` page.
================================================
FILE: docs/app/docs/blog-theme/rss/page.mdx
================================================
---
icon: RSSIcon
---
import { ExampleCode } from 'components/example-code'
# Generate RSS feed
The following code snippet demonstrates how to create `/rss.xml` route.
================================================
FILE: docs/app/docs/blog-theme/start/page.mdx
================================================
---
icon: ChevronRightIcon
---
import InstallNextraTheme from '@components/install-nextra-theme.mdx'
import ReadyToGo from '@components/ready-to-go.mdx'
import { ExampleCode } from '@components/example-code'
import { Steps } from 'nextra/components'
# Get Started
> [!NOTE]
>
> An example of the blog theme can be found [here](https://demo.vercel.blog),
> with source code [here](https://github.com/shuding/nextra/tree/main/examples/blog).
Similar to the [Docs Theme](/docs/docs-theme/start), you can install the blog
theme with the following commands:
## Start as a new project
### Install
To create a Nextra Blog site manually, you have to install **Next.js**,
**React**, **Nextra**, and **Nextra Blog Theme**. In your project directory, run
the following command to install the dependencies:
```sh npm2yarn
npm i next react react-dom nextra nextra-theme-blog
```
> [!NOTE]
>
> If you already have Next.js installed in your project, you only need to
> install `nextra` and `nextra-theme-blog` as the add-ons.
{/*
### Create Blog Theme Config
Lastly, create the corresponding `theme.config.jsx` file in your project's root
directory. This will be used to configure the Nextra Blog theme:
```jsx filename="theme.config.jsx"
export default {
footer:
,
head: ({ title, meta }) => (
<>
{meta.description && (
)}
{meta.tag && }
{meta.author && }
>
),
readMore: 'Read More →',
postFooter: null,
darkMode: false,
navs: [
{
url: 'https://github.com/shuding/nextra',
name: 'Nextra'
}
]
}
```
*/}
## Layout Props
================================================
FILE: docs/app/docs/blog-theme/tags/page.mdx
================================================
---
icon: TagsIcon
---
import { ExampleCode } from 'components/example-code'
# Tags Page
The following code snippet demonstrates how to create `/tags/:id` pages.
================================================
FILE: docs/app/docs/built-ins/[name]/page.tsx
================================================
import path from 'node:path'
import { generateApiReference } from '@components/generate-api-reference'
import type { ApiReference } from '@components/generate-api-reference'
import { useMDXComponents as getMDXComponents } from 'next-mdx-import-source-file'
import type { MdxFile } from 'nextra'
import { generateDefinition } from 'nextra/tsdoc'
import type { FC } from 'react'
type ComponentApiReference = ApiReference & { groupKeys?: string }
const API_REFERENCE: (
| ComponentApiReference
| { type: 'separator'; title: string; name: string }
)[] = [
{ type: 'separator', title: 'Layout Components', name: '_' },
{
name: 'Banner',
packageName: 'nextra/components',
groupKeys: 'HTMLAttributes'
},
{
name: 'Head',
packageName: 'nextra/components',
isFlattened: true
},
{
name: 'Search',
packageName: 'nextra/components',
isFlattened: false,
groupKeys:
"Omit"
},
{ type: 'separator', title: 'Content Components', name: '_2' },
{
name: 'Bleed',
packageName: 'nextra/components',
groupKeys: 'HTMLAttributes'
},
{
name: 'Callout',
packageName: 'nextra/components',
groupKeys: 'HTMLAttributes'
},
{
// TODO: add
//
name: 'Cards',
packageName: 'nextra/components',
groupKeys: 'HTMLAttributes'
},
{
// TODO: add
//
//
name: 'FileTree',
packageName: 'nextra/components',
groupKeys: 'HTMLAttributes'
},
{
name: 'Steps',
packageName: 'nextra/components',
groupKeys: 'HTMLAttributes'
},
{
// TODO: add
//
//
//
name: 'Table',
packageName: 'nextra/components',
groupKeys: 'HTMLAttributes'
},
{ name: 'Tabs', packageName: 'nextra/components' },
{ type: 'separator', title: 'Other Components', name: '_3' },
{ name: 'MDXRemote', packageName: 'nextra/mdx-remote' },
{ name: 'Playground', packageName: 'nextra/components' },
{ name: 'TSDoc', packageName: 'nextra/tsdoc' }
]
const routes = API_REFERENCE.filter(
(o): o is ComponentApiReference => !('type' in o)
)
export const generateStaticParams = () =>
routes.map(o => ({ name: o.name.toLowerCase() }))
// @ts-expect-error -- fixme
export const pageMap: (MdxFile & { title: string })[] = API_REFERENCE.map(o =>
'type' in o
? o
: {
name: o.name.toLowerCase(),
route: `/docs/built-ins/${o.name.toLowerCase()}`,
title:
o.name === 'TSDoc' ? (
{o.name}
) : (
o.name
)
}
)
const Wrapper = getMDXComponents().wrapper
async function getReference(props: PageProps) {
const params = await props.params
const apiRefIndex = routes.findIndex(
o => o.name.toLowerCase() === params.name
)
const apiRef = routes[apiRefIndex]
if (!apiRef) {
throw new Error(`API reference not found for "${params.name}"`)
}
const { name, packageName, groupKeys, isFlattened } = apiRef
const result = groupKeys
? `Omit & { '...props': ${groupKeys} }>`
: 'MyProps'
const code =
apiRef.code ??
`
import type { ComponentProps, HTMLAttributes } from 'react'
import type { ComboboxInputProps } from '../packages/nextra/node_modules/@headlessui/react'
import { ${name} as MyComponent } from '${packageName}'
type MyProps = ComponentProps
type $ = ${result}
export default $`
const flattened = isFlattened !== false
const fcPropsDefinition = generateDefinition({ code, flattened })
const {
// @ts-expect-error -- exist
signatures: _signatures,
...fcDefinition
} = generateDefinition({
code: `export { ${name} as default } from '${packageName}'`,
flattened
})
const definition = {
...fcPropsDefinition,
...fcDefinition
}
const res = await generateApiReference(apiRef, {
title: 'Component',
subtitle: 'Props',
definition
// bottomMdxContent: `
// **Tip for TypeScript users:**
// You can retrieve the props type for the \`<${name}>\` component using \`React.ComponentProps\`.
// `
})
const filePath =
fcDefinition.filePath &&
path
.relative('..', fcDefinition.filePath)
.replace(/\.d.ts$/, '.tsx')
.replace('/dist/', '/src/')
// Add edit on GitHub link to points on a source file
res.metadata.filePath = `https://github.com/shuding/nextra/tree/main/${filePath}`
return res
}
export async function generateMetadata(props: PageProps) {
const { metadata } = await getReference(props)
return metadata
}
type PageProps = Readonly<{
params: Promise<{ name: string }>
}>
const Page: FC = async props => {
const {
default: MDXContent,
toc,
metadata,
sourceCode
} = await getReference(props)
return (
)
}
export default Page
================================================
FILE: docs/app/docs/built-ins/page.mdx
================================================
---
asIndexPage: true
sidebarTitle: Built-In Components
---
import {
CardsIcon,
FolderTreeIcon,
IdCardIcon,
OneIcon,
TableIcon
} from '@components/icons'
import { Callout } from 'nextra/components'
import { GitHubWarningIcon } from 'nextra/icons'
import { OverviewPage } from '../../_components/overview-page'
import { getEnhancedPageMap } from '../../../components/get-page-map'
# Built-Ins
This API reference is automatically generated from the catch-all route file
`/docs/built-ins/[name]/page.tsx` using the new [Nextra ``
component](/docs/built-ins/tsdoc).
Nextra includes a couple of built-in components that you can use to better style
your content:
{/* `pageMap` prop will be removed once Nextra will add dynamic routes in pageMap */}
o.name === 'docs')
.children.find(o => o.name === 'built-ins').children
}
icons={{
WarningIcon: GitHubWarningIcon,
IdCardIcon,
FolderTreeIcon,
OneIcon,
TableIcon,
CardsIcon
}}
/>
================================================
FILE: docs/app/docs/custom-theme/old.mdx
================================================
### Render metadata for the active page
> [!WARNING]
>
> Docs from Nextra 3
Other than `children`, some other useful props are passed to the theme layout
too. With the `pageOpts` props, the theme can access the page's meta
information.
For example, let's implement these features:
- Render the page title in ``
- Show a simple Table of Contents via MDX `` component
- Add a meta tag for `og:image` via the front matter
```tsx filename="theme.tsx" /pageOpts/
import Head from 'next/head'
import type { NextraThemeLayoutProps } from 'nextra'
import { MDXProvider } from 'nextra/mdx'
export default function Layout({ children, pageOpts }: NextraThemeLayoutProps) {
const { title, frontMatter } = pageOpts
return (
<>
{title}{children}
>
)
}
function MyWrapper({ children, toc }) {
return (
<>
My Theme
Table of Contents:
{toc.map(heading => (
{heading.value}
))}
{children}
>
)
}
```
### Use page map of the entire site
> [!WARNING]
>
> Docs from Nextra 3
Now, if you want to render something like a sidebar or a navigation bar, which
relies on information of not only the current page but also other pages, you can
use the `pageMap` value.
For example, we can render a simple navigation list with all the pages in the
top level:
```tsx filename="theme.tsx" /pageMap/
import Link from 'next/link'
import type { NextraThemeLayoutProps } from 'nextra'
export default function Layout({ children, pageOpts }: NextraThemeLayoutProps) {
const { pageMap } = pageOpts
return (
My Theme
{pageMap.map(item => {
if ('route' in item && !('children' in item)) {
return (
{item.route}
)
}
})}
{children}
)
}
```
There are other item kinds such as `Folder` (for directories) and `Meta` (for
`_meta` files). All the items are typed so you can easily know the properties.
================================================
FILE: docs/app/docs/custom-theme/page.mdx
================================================
import { ExampleCode } from 'components/example-code'
import { Steps } from 'nextra/components'
import OldDocs from './old.mdx'
# Custom Theme
A theme in Nextra works like a layout, that will be rendered as a wrapper for
all pages. This docs will walk you through the process of creating a custom
theme.
> [!NOTE]
>
> Source code for the following custom theme can be found
> [here](https://github.com/shuding/nextra/tree/main/examples/custom-theme).
## Create a custom theme
### Create a root layout
### Create [`mdx-components` file](/docs/file-conventions/mdx-components-file)
### Create a basic theme
You can now start working on your theme! Create the `nextra-theme.tsx` file, it
accepts a `children` prop, which is the MDX content of the current page, and
wraps some other elements around the content:
### Create navbar and footer
### Create sidebar
### Add first MDX page
After creating the theme, you can simply add a MDX file as `app/page.mdx` and
see the result:

Inside your theme layout, you can use CSS imports or other ways to style it.
Next.js hooks such as `usePathname` are also available.
{process.env.NODE_ENV !== 'production' && }
================================================
FILE: docs/app/docs/docs-theme/api/page.mdx
================================================
# API
## `useThemeConfig` hook
The `useThemeConfig` hook returns values of your
[theme configuration](/docs/docs-theme/theme-configuration) and is made to
dynamically configure your project.
```js
import { useThemeConfig } from 'nextra-theme-docs'
```
## `useConfig` hook
```js
import { useConfig } from 'nextra-theme-docs'
```
The `useConfig` hook returns data from your current page context.
================================================
FILE: docs/app/docs/docs-theme/built-ins/footer/page.mdx
================================================
---
sidebarTitle: Footer
---
import { ToggleVisibilitySection } from 'components/toggle-visibility-section'
# Footer Component
The footer area of the website. You can specify content for your default footer.
## Props
## Example
You can add content, such as copyright information by passing it as `children`
of the `Footer` component:
```jsx filename="app/layout.jsx"
import { Footer, Layout } from 'nextra-theme-docs'
export default function MyLayout({ children, ...props }) {
return (
{children}
{children}
)
}
```
================================================
FILE: docs/app/docs/docs-theme/built-ins/layout/old.mdx
================================================
## MDX Components [!TODO]
Provide custom [MDX components](https://mdxjs.com/table-of-components) to render
the content. For example, you can use a custom `pre` component to render code
blocks.
================================================
FILE: docs/app/docs/docs-theme/built-ins/layout/page.mdx
================================================
---
sidebarTitle: Layout
---
import { ToggleVisibilitySection } from 'components/toggle-visibility-section'
export function PartialTSDoc({ flattened, props }) {
return (
, ${props.map(prop => JSON.stringify(prop)).join('|')}>
export default $`}
/>
)
}
# Layout Component
The theme is configured with the `` component. You should pass your
config options as Layout's `props`, for example:
```jsx filename="app/layout.jsx" {8-9}
import { Layout } from 'nextra-theme-docs'
export default function MyLayout({ children, ...props }) {
return (
{children}
)
}
```
Detailed information for each option is listed below.
## Props
## Page Map
## Banner
## Navbar
## Footer
## Search
## Docs Repository
Set the repository URL of the documentation. It's used to generate the
"[Edit this page](#edit-link)" link, the "[Feedback](#feedback-link)" link and
"[Report of broken link](./not-found)" on
[not found page](https://nextjs.org/docs/app/api-reference/file-conventions/not-found).
### Specify a Path
If the documentation is inside a monorepo, a subfolder, or a different branch of
the repository, you can simply set the `docsRepositoryBase` to the root path of
the `app/` (App Router) folder of your docs. For example:
```jsx filename="app/layout.jsx"
{children}
```
Then Nextra will automatically generate the correct file path for all pages.
## Dark Mode and Themes
Customize the theme behavior of the website.
import Old from './old.mdx'
{process.env.NODE_ENV !== 'production' && }
## Edit Link
Show an "Edit this page" link on the page that points to the file URL on GitHub
(or other places).
> [!TIP]
>
> To disable it, you can set `editLink` to `null`.
## Feedback Link
The built-in feedback link provides a way for users to submit feedback about the
documentation.
> [!TIP]
>
> To disable it, you can set `feedback.content` to `null`.
## I18n
## Last Updated Date
Show the last updated date of a page. It's useful for showing the freshness of
the content.
## Navigation
Show previous and next page links on the bottom of the content. It's useful for
navigating between pages.

```jsx filename="app/layout.jsx"
{children}
```
The above is also equivalent to `navigation: true{:js}`.
## Sidebar
### Menu Collapse Level
By default, the sidebar menu is collapsed at level `2`. You can change it by
setting `sidebar.defaultMenuCollapseLevel` to a different number. For example,
when set to `1`, every folder will be collapsed by default and when set to
`Infinity`, all nested folders will be expanded by default.
If `sidebar.autoCollapse` is set to `true`, then all folders that do not contain
an active/focused route will automatically collapse up to the level set by
`sidebar.defaultMenuCollapseLevel`. e.g. if `defaultMenuCollapseLevel` is `2`,
then top-level folders will not auto-collapse.
### Customize Sidebar Content
Together with the [Separators](/docs/docs-theme/page-configuration#separators)
item, you can customize how the sidebar content is rendered by using JSX
elements:
```jsx filename="_meta.jsx" {5-10}
export default {
index: 'Intro',
'--': {
type: 'separator',
title: (
{children}
)
},
frameworks: 'JS Frameworks & Libs',
about: 'About'
}
```

### Customize Sidebar with Front Matter
In addition, you can customize the sidebar title using the `sidebarTitle`
property in your front matter:
```mdx filename="getting-started.mdx"
---
sidebarTitle: Getting Started 🚀
---
```
The priority of the sidebar title is as follows:
1. A non-empty title from the `_meta` file.
1. `sidebarTitle` in the front matter.
1. `title` in the front matter.
1. The title derived from the first `h1` Markdown heading (e.g.
`# Dima Machina`).
1. If none of the above are available, it falls back to the filename of the
page, formatted according to [The Chicago Manual of Style](https://title.sh).
## Theme Switch
You are able to customize the option names for localization or other purposes:
```jsx filename="app/layout.jsx"
{children}
```
## Table of Contents (TOC)
Show a table of contents on the right side of the page. It's useful for
navigating between headings.
### Floating TOC
When enabled, the TOC will be displayed on the right side of the page, and it
will be sticky when scrolling. If it's disabled, the TOC will be displayed
directly on the page sidebar.
================================================
FILE: docs/app/docs/docs-theme/built-ins/navbar/page.mdx
================================================
---
sidebarTitle: Navbar
---
import { ToggleVisibilitySection } from 'components/toggle-visibility-section'
import { generateTsFromZod } from 'nextra/tsdoc'
# Navbar Component
## Props
### Logo
The logo of the website rendered on the navbar.
<>>
{/* prettier-ignore */}
[Live example on StackBlitz](https://stackblitz.com/edit/nextra-2-docs-yrlccm?file=theme.config.jsx)
```jsx filename="app/layout.jsx"
My Cool Project
>
}
/>
```
### Project link
Show a button that links to your project's homepage on the navbar. By default,
it links to Nextra's GitHub repository.
You can configure `projectLink` and `projectIcon` to customize the project link,
for example make it link to your GitLab repository:

```jsx filename="app/layout.jsx"
}
/>
```
### Chat link
Show a button that links to your project's forum or other social media on the
navbar.
You can configure `chatLink` and `chatIcon` to customize the chat link, for
example make it link to your Twitter account:
```jsx filename="app/layout.jsx"
}
/>
```
### Menu and custom links
Check out [Page Configuration](/docs/docs-theme/page-configuration#navbar-items)
to learn how to add custom menus or links to the navbar.
================================================
FILE: docs/app/docs/docs-theme/built-ins/not-found/page.mdx
================================================
---
sidebarTitle: NotFoundPage
---
# NotFoundPage Component
Options to configure report of broken link on not found page.
## Props
## Example
```jsx filename="app/not-found.jsx"
import { NotFoundPage } from 'nextra-theme-docs'
export default function NotFound() {
return (
The page is not found
)
}
```
================================================
FILE: docs/app/docs/docs-theme/built-ins/page.mdx
================================================
---
asIndexPage: true
sidebarTitle: Built-In Components
icon: BoxIcon
---
import { OverviewPage } from '../../../_components/overview-page'
# Built-Ins
================================================
FILE: docs/app/docs/docs-theme/page.mdx
================================================
---
asIndexPage: true
sidebarTitle: Docs Theme
---
# Nextra Docs Theme
import { BoxIcon, ChevronRightIcon } from '@components/icons'
import { OverviewPage } from '../../_components/overview-page'
================================================
FILE: docs/app/docs/docs-theme/start/page.mdx
================================================
---
sidebarTitle: Get Started
icon: ChevronRightIcon
---
import InstallNextraTheme from '@components/install-nextra-theme.mdx'
import ReadyToGo from '@components/ready-to-go.mdx'
import { Steps } from 'nextra/components'
# Docs Theme
Nextra Docs Theme is a theme that includes almost everything you need to build a
modern documentation website. It includes:
- a top navigation bar
- a search bar
- a pages sidebar
- a table of contents (TOC)
- and other built-in components
> [!TIP]
>
> This website itself is built with the Nextra Docs Theme.
## Start as a New Project
### Install
To create a Nextra Docs site manually, you have to install **Next.js**,
**React**, **Nextra**, and **Nextra Docs Theme**. In your project directory, run
the following command to install the dependencies:
```sh npm2yarn
npm i next react react-dom nextra nextra-theme-docs
```
> [!NOTE]
>
> If you already have Next.js installed in your project, you only need to
> install `nextra` and `nextra-theme-docs` as the add-ons.
```jsx filename="app/layout.jsx"
import { Footer, Layout, Navbar } from 'nextra-theme-docs'
import { Banner, Head } from 'nextra/components'
import { getPageMap } from 'nextra/page-map'
import 'nextra-theme-docs/style.css'
export const metadata = {
// Define your metadata here
// For more information on metadata API, see: https://nextjs.org/docs/app/building-your-application/optimizing/metadata
}
const banner = Nextra 4.0 is released 🎉
const navbar = (
Nextra}
// ... Your additional navbar options
/>
)
const footer =
export default async function RootLayout({ children }) {
return (
{/* Your additional tags should be passed as `children` of `` element */}
{children}
)
}
```
See the [File Conventions](/docs/file-conventions) for more details on
organizing your documentation structure, and check out the
[Layout Component](/docs/docs-theme/built-ins/layout) for configuring the docs
site's theme.
================================================
FILE: docs/app/docs/file-conventions/content-directory/page.mdx
================================================
---
icon: FolderIcon
sidebarTitle: content
description:
The `content` directory in Nextra allows you to organize your Markdown files
without adhering to the `page` filename convention, simplifying the migration
from Next.js `pages` router.
---
import fs from 'node:fs/promises'
import { compileMdx } from 'nextra/compile'
import { FileTree, Steps } from 'nextra/components'
import { MDXRemote } from 'nextra/mdx-remote'
export async function MDXPathPage() {
const filename = '[[...mdxPath]]/page.jsx'
const rawMdx = `~~~jsx filename="${filename}" showLineNumbers
${(await fs.readFile(`../examples/docs/src/app/docs/${filename}`, 'utf8')).trimEnd()}
~~~`
const rawJs = await compileMdx(rawMdx, { defaultShowCopyCode: true })
return
}
# `content` Directory
The `content` directory is designed to:
1. Migrate your existing Next.js `pages` router with minimal changes, you just
need to rename your `pages` directory to `content`.
1. Avoid having `page` filename convention, e.g. `app/configuration/page.mdx` ->
`content/configuration.mdx`
## Setup
### Create your first MDX page as `content/index.mdx`:
```mdx filename="content/index.mdx"
# Welcome to Nextra
Hello, world!
```
> [!TIP]
>
> You can keep `content` directory in root of your project, or in
> [`src` directory](./src-directory).
### Set `contentDirBasePath` option in `next.config` file (_optional_)
If you want to serve your content from a different path, you can set
`contentDirBasePath` option:
```js filename="next.config.mjs" {4}
import nextra from 'nextra'
const withNextra = nextra({
contentDirBasePath: '/docs' // Or even nested e.g. `/docs/advanced`
})
```
### Add `[[...mdxPath]]/page.jsx` file
Place this file in `app` directory with the following content:
you should get the following structure:
> [!TIP]
>
> Consider the single catch-all route `[[...mdxPath]]/page.jsx` as a gateway to
> your `content` directory.
>
> If you set `contentDirBasePath` option in `next.config` file, you should put
> `[[...mdxPath]]/page.jsx` in the corresponding directory.
### You are ready to go!
> [!NOTE]
>
> Many existing solutions such as
> [Refreshing the Next.js App Router When Your Markdown Content Changes](https://steveruiz.me/posts/nextjs-refresh-content)
> rely on extra dependencies like `concurrently` and `ws`. These approaches
> include
> [Dan Abramov workaround with `` component and dev web socket server](https://github.com/hashicorp/next-remote-watch/issues/42#issuecomment-1794052655).
>
> Nextra's `content` directory delivers a streamlined solution right out of the
> box:
>
> - you don't need to install unnecessary dependencies
> - you don't need to restart your server on changes in `content` directory
> - hot reloading works out of the box
> - you can use `import` statements in MDX files and
> [static images](/docs/guide/image#static-image) works as well
>
> Checkout Nextra's
> [docs website](https://github.com/shuding/nextra/tree/main/examples/docs) and
> [i18n website example](https://github.com/shuding/nextra/tree/main/examples/swr-site).
================================================
FILE: docs/app/docs/file-conventions/mdx-components-file/page.mdx
================================================
---
icon: MdxIcon
sidebarTitle: mdx-components.js
description:
The `mdx-components` file in Nextra is essential for customizing styles via
the `useMDXComponents` function, allowing you to define and override MDX
components globally.
---
# `mdx-components.js` File
The `mdx-components` file is **required**, you use it to customize styles via
`useMDXComponents` function.
## Example
The `mdx-components.js` file must export
[a single function named `useMDXComponents`](/api/usemdxcomponents).
```ts filename="mdx-components.js"
import { useMDXComponents as getThemeComponents } from 'nextra-theme-docs' // nextra-theme-blog or your custom theme
// Get the default MDX components
const themeComponents = getThemeComponents()
// Merge components
export function useMDXComponents(components) {
return {
...themeComponents,
...components
}
}
```
## Errors
### Module not found: Can't resolve `'next-mdx-import-source-file'`
To fix this, update the `turbopack.resolveAlias` section in your `next.config`
file:
> [!NOTE]
>
> - If you're using Next.js < 15.3, use `experimental.turbopack.resolveAlias`
> - If you're using Next.js ≥ 15.3, use `turbopack.resolveAlias`
```diff filename="next.config.mjs"
import nextra from 'nextra'
const withNextra = nextra()
export default withNextra({
+ turbopack: {
+ resolveAlias: {
+ // Path to your `mdx-components` file with extension
+ 'next-mdx-import-source-file': './src/mdx-components.tsx'
+ }
+ }
})
```
> [!TIP]
>
> - You can keep `mdx-components` file in root of your project, or in
> [`src` directory](./src-directory).
> - The `.js`, `.jsx`, or `.tsx` file extensions can be used for
> `mdx-components` file.
> - When importing `useMDXComponents`, alias it as `getMDXComponents` to avoid a
> false positive error from the ESLint React Hooks plugin.
>
> ```bash filename="react-hooks/rules-of-hooks"
> React Hook "useMDXComponents" cannot be called at the top level.
> React Hooks must be called in a React function component or a custom React Hook function.
> ```
================================================
FILE: docs/app/docs/file-conventions/meta-file/page.mdx
================================================
---
icon: FileIcon
sidebarTitle: _meta.js
description:
The `_meta` file in Nextra allows you to customize page sidebar titles, order,
and theme visibility, enhancing site organization and user experience.
---
import { ContentAndAppFileTee } from 'components/content-and-app-file-tree'
import { Video } from 'components/video'
import { FileTree } from 'nextra/components'
import { generateTsFromZod } from 'nextra/tsdoc'
import { pageThemeSchema } from 'private-next-root-dir/../packages/nextra/dist/server/schemas'
export function Block({ children }) {
return (
{children}
)
}
# `_meta.js` File
In Nextra, the site and individual page structure can be configured via the
co-located `_meta` files. Those configurations affect the overall layout of your
Nextra theme, especially the navigation bar and the sidebar:
<>>
{/* prettier-ignore */}
Example: [Nextra Docs Theme](/docs/docs-theme) has sidebar and navbar generated automatically from Markdown files.
{/* It's very common to customize each page's title, rather than just relying on filenames. Having a page titled "Index" lacks clarity. It is preferable to assign a meaningful title that accurately represents the content, such as "Home". */}
{/* That's where `_meta` files comes in. You can have an `_meta` file in each directory, and it will be used to override the default configuration of each page. */}
## Organizing files
Nextra allows you to organize files in the following ways:
- **In Next.js'
[`app` directory](https://nextjs.org/docs/app/getting-started/project-structure#top-level-folders):** Nextra
gathers all `page` files, including
[`page.md` and `page.mdx` files](/docs/file-conventions/page-file) as well as
`_meta` files.
- **In Nextra's
[`content` directory](/docs/file-conventions/content-directory):** Nextra
collects all `.md` and `.mdx` files, along with `_meta` files.
Below the same file-based routing structure is represented for `content` and
`app`-only directories:
> [!NOTE]
>
> You can combine both organizational ways for your project:
>
> - [x] the `content` directory with `.mdx` files
> - [x] the `app` directory with `page` files
### `pageMap` structure
Afterward, Nextra generates a `pageMap` array containing information about your
entire site's routes and directories structure. Features such as the navigation
bar and sidebar can be generated based on the `pageMap` information.
The generated `pageMap` will be:
```jsonc filename="pageMap" copy=false
[
// content/_meta.js
{ "data": {} },
{
// content/index.mdx
"name": "index",
"route": "/",
"title": "Index",
"frontMatter": {}
},
{
// content/contact.md
"name": "contact",
"route": "/contact",
"title": "Contact",
"frontMatter": {}
},
{
// content/about
"name": "about",
"route": "/about",
"title": "About",
"children": [
// content/about/_meta.js
{ "data": {} },
{
// content/about/index.mdx
"name": "index",
"route": "/about",
"title": "Index",
"frontMatter": {}
},
{
// content/about/legal.md
"name": "legal",
"route": "/about/legal",
"title": "Legal",
"frontMatter": {}
}
]
}
]
```
And the global `pageMap` will be imported to each page by Nextra. Then,
configured theme will render the actual UI with that `pageMap`.
## API
The title and order of a page shown in the sidebar/navbar can be configured in
the `_meta` file as key-value pairs.
```ts filename="_meta.ts"
import type { MetaRecord } from 'nextra'
/**
* type MetaRecordValue =
* | TitleSchema
* | PageItemSchema
* | SeparatorSchema
* | MenuSchema
*
* type MetaRecord = Record
**/
const meta: MetaRecord = {
// ...
}
export default meta
```
### `title` type
When specifying a `title` in `_meta` file, you can define it as either a simple
string or a JSX element.
```ts copy=false
type TitleSchema = string | ReactElement
```
For the below file structure:
The following `_meta` file defines pages titles:
```jsx filename="_meta.jsx"
import { GitHubIcon } from 'nextra/icons'
export default {
index: 'My Homepage',
// You can use JSX elements to change the look of titles in the sidebar, e.g. insert icons
contact: (
Contact Us
),
about: {
// Alternatively, you can set title with `title` property
title: 'About Us'
// ... and provide extra configurations
}
}
// Custom component for italicized text
function Italic({ children, ...props }) {
return {children}
}
```
### Pages
In `_meta` file you can define how the pages are shown in the sidebar, e.g. for
the following file structure:
```js filename="_meta.js" copy=false
export default {
index: 'My Homepage',
contact: 'Contact Us',
about: 'About Us'
}
```
> [!NOTE]
>
> If any routes are not listed in the `_meta` file, they will be appended to the
> end of the sidebar and sorted alphabetically (except for `index` key which
> comes first if it's not specified in `_meta` file).
```ts
type PageItemSchema = {
type: 'page' | 'doc' // @default 'doc'
display: 'normal' | 'hidden' | 'children' // @default 'normal'
title?: TitleSchema
theme?: PageThemeSchema
}
```
#### `type: 'page'` option
By defining a top-level page or folder as `type: 'page'{:js}`, it will be shown
as a special page on the navigation bar, instead of the sidebar. With this
feature, you can have multiple "sub docs", and special pages or links such as
"Contact Us" that are always visible.
For example, you can have 2 docs folders `frameworks` and `fruits` in your
project. In your top-level `_meta` file, you can set everything as a page,
instead of a normal sidebar item:
```js filename="_meta.js" copy=false {4,8,12,16}
export default {
index: {
title: 'Home',
type: 'page'
},
frameworks: {
title: 'Frameworks',
type: 'page'
},
fruits: {
title: 'Fruits',
type: 'page'
},
about: {
title: 'About',
type: 'page'
}
}
```
And it will look like this:
{/* prettier-ignore */}
[Live example on StackBlitz](https://stackblitz.com/edit/nextra-2-docs-eszspq?file=pages%2F_meta.js)
> [!TIP]
>
> You can also hide links like `Home` from the navbar with the
> [`display: 'hidden'{:js}`](#display-hidden-option) option.
>
> You can have external links in the navbar, similar to the
> [links section](#links):
>
> ```js filename="_meta.js" {4-5}
> export default {
> contact: {
> title: 'Contact Us',
> type: 'page',
> href: 'https://example.com/contact'
> }
> }
> ```
#### `display: 'hidden'` option
By default, all MDX routes in the filesystem will be shown on the sidebar. But
you can hide a specific pages or folders by using the `display: 'hidden'{:ts}`
configuration:
```js filename="_meta.js" {3}
export default {
contact: {
display: 'hidden'
}
}
```
> [!NOTE]
>
> The page will still be accessible via the `/contact` URL, but it will not be
> shown in the sidebar.
#### `theme` option
You can configure the theme for each page using the `theme` option. For example,
you can disable or enable specific components for specific pages:
```js filename="_meta.js" {4}
export default {
about: {
theme: {
sidebar: false
}
}
}
```
> [!WARNING]
>
> This option will be inherited by all child pages if set to a folder.
##### Layouts
By default, each page has `layout: 'default'{:js}` in their theme config, which
is the default behavior. You might want to render some page with the full
container width and height, but keep all the other styles. You can use the
`'full'{:js}` layout to do that:
```js filename="_meta.js" {4}
export default {
about: {
theme: {
layout: 'full'
}
}
}
```
##### Typesetting [#typesetting-section]
The `typesetting` option controls typesetting details like font features,
heading styles and components like `
` and ``. There are
`'default'{:js}` and `'article'{:js}` typesettings available in the docs theme.
The default one is suitable for most cases like documentation, but you can use
the `'article'{:js}` typesetting to make it look like an elegant article page:
```js filename="_meta.js" {4}
export default {
about: {
theme: {
typesetting: 'article'
}
}
}
```
[Live example on StackBlitz](https://stackblitz.com/edit/nextra-2-docs-hg77h3?file=pages%2F_meta.js,pages%2Findex.mdx)
### Folders
Folders can be configured in the same way as pages.
For example, the following top-level `_meta` file contains the meta information
for the top-level pages and folders. The nested `_meta` file contains the
meta information for pages in the same folder:
> [!NOTE]
>
> You can move directories around without having to change the `_meta` file
> since information for pages are grouped together in directories.
#### With `/index` page
To create a folder with an index page, add `asIndexPage: true{:js}` to its front
matter.
For example, to create a `/fruits` route, setting `asIndexPage: true{:js}` tells
Nextra that `/fruits` is a folder with an index page. Clicking the folder in the
sidebar will expand it and display the MDX page.
```mdx filename="content/fruits/index.mdx or app/fruits/page.mdx" copy=false {4}
---
title: All Fruits
sidebarTitle: 🍒 Fruits
asIndexPage: true
---
```
### Links
```ts
type LinkSchema = {
href: string
title?: TitleSchema
}
```
You can add external links to the sidebar by adding an item with `href` in
`_meta` file:
```js filename="_meta.js" {2-5}
export default {
github_link: {
title: 'Nextra',
href: 'https://github.com/shuding/nextra'
}
}
```
> [!TIP]
>
> You can use this option to link to relative internal links too.
### Separators
```ts
type SeparatorSchema = {
type: 'separator'
title?: TitleSchema
}
```
You can use a "placeholder" item with `type: 'separator'{:js}` to create a
separator line between items in the sidebar:
```js filename="_meta.js" {3-4}
export default {
'###': {
type: 'separator',
title: 'My Items' // Title is optional
}
}
```
### Menus
You can also add menus to the navbar using `type: 'menu'{:js}` and the `items`
option:
<>>
{/* prettier-ignore */}
[Live example on StackBlitz](https://stackblitz.com/edit/nextra-2-docs-2qopvp?file=pages%2F_meta.js)
```ts
type MenuItemSchema =
| TitleSchema
| { title: TitleSchema }
| (LinkSchema & { type?: 'page' | 'doc' })
| SeparatorSchema
type MenuSchema = {
type: 'menu'
title?: TitleSchema
items: Record
}
```
```js filename="_meta.js"
export default {
company: {
title: 'Company',
type: 'menu',
items: {
about: {
title: 'About',
href: '/about'
},
contact: {
title: 'Contact Us',
href: 'mailto:hi@example.com'
}
}
}
}
```
### Fallbacks
In the [`type: 'page'{:js}` option](#type-page-option) above, we have to define
the `type: 'page'{:js}` option for every page. To make it easier, you can use
the `'*'` key to define the fallback configuration for all items in this folder:
```js filename="_meta.js" {2-4}
export default {
'*': {
type: 'page'
},
index: 'Home',
frameworks: 'Frameworks',
fruits: 'Fruits',
about: 'About'
}
```
They are equivalent where all items have `type: 'page'{:js}` set.
### `_meta.global` file
You can also define all your pages in a single `_meta` file, suffixed with
`.global`. The API remains the same as for folder-specific `_meta` files, with 1
exception: **folder items must include an `items` field**.
For the following structure, you might use the following `_meta` files:
With single `_meta.global` file it can be defined as below:
```js filename="_meta.global.js" copy=false
export default {
fruits: {
type: 'page',
title: '✨ Fruits',
items: {
apple: '🍎 Apple',
banana: '🍌 BaNaNa'
}
}
}
```
> [!WARNING]
>
> You can't use both `_meta.global` and `_meta` files in your project.
## Good to know
### Sorting pages
You can use ESLint's built-in `sort-keys` rule, append
`/* eslint sort-keys: error */` comment at the top of your `_meta` file, and you
will receive ESLint's errors about incorrect order.
### Type of `_meta` keys
The type of your `_meta` keys should always **be a string** and **not a number**
because
[numbers are always ordered first](https://dev.to/frehner/the-order-of-js-object-keys-458d)
in JavaScript objects.
For example, consider the following:
```js filename="_meta.js" copy=false
export default {
foo: '',
1992_10_21: '',
1: ''
}
```
will be converted to:
{/* prettier-ignore */}
```js filename="_meta.js" copy=false
export default {
'1': '',
'19921021': '',
foo: ''
}
```
> [!TIP]
>
> The `.js`, `.jsx`, or `.tsx` file extensions can be used for `_meta` file.
================================================
FILE: docs/app/docs/file-conventions/page-file/page.mdx
================================================
---
icon: FileIcon
sidebarTitle: page.mdx
description:
The `page.mdx` file in Nextra is a special Next.js App Router convention file
that allows you to define UI unique to a route. By default, `.js`, `.jsx`, or
`.tsx` file extensions can be used for `page`, and Nextra enhances them with
`.md` and `.mdx` extensions.
---
import { FileTree } from 'nextra/components'
# `page.mdx` File
[`page` file](https://nextjs.org/docs/app/api-reference/file-conventions/page)
is a special
[Next.js App Router convention file](https://nextjs.org/docs/app/getting-started/project-structure#routing-files)
which allows you to define UI that is unique to a route:
> [!TIP]
>
> By default, the `.js`, `.jsx`, or `.tsx` file extensions can be used for
> `page`. Nextra enhance them with `.md` and `.mdx` extensions.
================================================
FILE: docs/app/docs/file-conventions/page.mdx
================================================
---
asIndexPage: true
description:
Nextra's File Conventions guide details the structure and organization of
files and directories within a Nextra project, including the use of
`page.mdx`, `_meta.js`, and `mdx-components.js` files, as well as the
`content` and `src` directories.
---
import { FileIcon, FolderIcon, MdxIcon } from 'nextra/icons'
import { OverviewPage } from '../../_components/overview-page'
# File Conventions
================================================
FILE: docs/app/docs/file-conventions/src-directory/page.mdx
================================================
---
icon: FolderIcon
sidebarTitle: src
description:
The `src` directory in Nextra allows you to organize your application code
separately from project configuration files, enhancing code structure and
maintainability.
---
import { FileTree } from 'nextra/components'
# `src` Directory
As an alternative to having the special Nextra `content` directory and
`mdx-components` file in the root of your project, Nextra also supports the
common pattern of placing application code under the `src` directory.
This separates application code from project configuration files which mostly
live in the root of a project, which is preferred by some individuals and teams.
To use the `src` directory, move the `content` directory and `mdx-components`
file to `src/content` or `src/mdx-components` respectively.
export const nextConfigPackageJson = (
<>
>
)
export const folders = (
<>
>
)
**Without `src` directory**
{folders}
{nextConfigPackageJson}
**With `src` directory**
{folders}
{nextConfigPackageJson}
================================================
FILE: docs/app/docs/guide/custom-css/page.mdx
================================================
---
sidebarTitle: Custom CSS
icon: BrushIcon
---
# Custom CSS Support
Nextra is 100% compatible with the
[built-in CSS support of Next.js](https://nextjs.org/docs/app/getting-started/css),
including `.css`, `.module.css`, and Sass (`.scss`, `.sass`, `.module.scss`,
`.module.sass`) files.
For example, consider the following stylesheet named `styles.css`:
```css filename="styles.css"
body {
font-family:
'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue', 'Helvetica', 'Arial',
sans-serif;
padding: 20px 20px 60px;
max-width: 680px;
margin: 0 auto;
}
```
To apply your global styles, import your CSS file in the root layout file:
```jsx filename="app/layout.jsx"
import '../path/to/your/styles.css'
export default async function RootLayout({ children }) {
// ... Your layout logic here
}
```
For more information on how to use CSS in Next.js, check out the
[Next.js CSS Support documentation](https://nextjs.org/docs/app/getting-started/css).
================================================
FILE: docs/app/docs/guide/github-alert-syntax/page.mdx
================================================
---
icon: InformationCircleIcon
---
# GitHub Alert Syntax
`nextra-theme-docs` and `nextra-theme-blog` support replacing
[GitHub alert syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts)
with `` component for `.md`/`.mdx` files.
## Usage
```md filename="Markdown"
> [!NOTE]
>
> Useful information that users should know, even when skimming content.
> [!TIP]
>
> Helpful advice for doing things better or more easily.
> [!IMPORTANT]
>
> Key information users need to know to achieve their goal.
> [!WARNING]
>
> Urgent info that needs immediate user attention to avoid problems.
> [!CAUTION]
>
> Advises about risks or negative outcomes of certain actions.
```
## Example
> [!NOTE]
>
> Useful information that users should know, even when skimming content.
> [!TIP]
>
> Helpful advice for doing things better or more easily.
> [!IMPORTANT]
>
> Key information users need to know to achieve their goal.
> [!WARNING]
>
> Urgent info that needs immediate user attention to avoid problems.
> [!CAUTION]
>
> Advises about risks or negative outcomes of certain actions.
## Usage with your own theme
If you want to benefit this feature with your own theme and your own ``
component:
import { Steps } from 'nextra/components'
### Create a `
` component
To create a `
` component, start by importing `withGitHubAlert` from
`nextra/components`. You should then create the `
` component by
invoking the `withGitHubAlert` function.
The first argument should be a React HOC component that handles the GitHub alert
syntax, and the second argument should be your standard `
`
component.
The `type` prop can be one of the following:
`'note' | 'tip' | 'important' | 'warning' | 'caution'{:ts}`.
```jsx
import { withGitHubAlert } from 'nextra/components'
const Blockquote = withGitHubAlert(({ type, ...props }) => {
return
}, MyBlockquoteComponent)
```
### Provide `
` to `useMDXComponents`
To make the `
` component available, you should integrate it into the
`useMDXComponents` function:
```jsx filename="mdx-components.jsx"
export function useMDXComponents(components) {
return {
blockquote: Blockquote,
...components
}
}
```
================================================
FILE: docs/app/docs/guide/i18n/page.mdx
================================================
---
icon: GlobeIcon
---
import { ExampleCode } from '@components/example-code'
import { Steps } from 'nextra/components'
# Next.js I18n
> [!WARNING]
>
> This feature is only available in `nextra-theme-docs`.
Nextra supports
[Next.js Internationalized Routing](https://nextjs.org/docs/advanced-features/i18n-routing)
out of the box. These docs explain how to configure and use it.
## Add i18n config
To add multi-language pages to your Nextra application, you need to config
`i18n` in `next.config.mjs` first:
```js filename="next.config.mjs" {8-11}
import nextra from 'nextra'
const withNextra = nextra({
// ... other Nextra config options
})
export default withNextra({
i18n: {
locales: ['en', 'zh', 'de'],
defaultLocale: 'en'
}
})
```
> [!NOTE]
>
> You can use any format of
> [UTS Locale Identifiers](https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers)
> for defining your locales in the `next.config` file, e.g. language with region
> format `en-US` (English as spoken in the United States).
## Configure the docs theme
Add the `i18n` option to your `theme.config.jsx` to configure the language
dropdown:
```js filename="theme.config.jsx"
i18n: [
{ locale: 'en', name: 'English' },
{ locale: 'zh', name: '中文' },
{ locale: 'de', name: 'Deutsch' },
{ locale: 'ar', name: 'العربية', direction: 'rtl' }
]
```
## Automatically detect and redirect to user-selected language (_optional_)
You can automatically detect the user's preferred language and redirect them to
the corresponding version of the site. To achieve this, create a `proxy.ts` or
`proxy.js` file in the root of your project and export Nextra's middleware
function from `nextra/locales`:
> [!WARNING]
>
> This approach will not work for i18n sites that are statically exported with
> `output: 'export'` in `nextConfig`.
## Custom 404 page (_optional_)
You can have a custom `not-found.jsx` with translations for an i18n website that
uses a shared theme layout. For guidance on implementing this, you can check out
the
[SWR i18n example](https://github.com/shuding/nextra/blob/c9d0ffc8687644401412b8adc34af220cccddf82/examples/swr-site/app/%5Blang%5D/not-found.ts).
================================================
FILE: docs/app/docs/guide/image/page.mdx
================================================
---
icon: PictureIcon
---
# Next.js Image
The standard way to use
[Next.js Image](https://nextjs.org/docs/app/getting-started/images) inside MDX
is to directly import the component:
```mdx filename="MDX"
import Image from 'next/image'
```
## Static image
> [!NOTE]
>
> This feature is enabled via `staticImage: true` in the Nextra config by
> default.
Nextra supports automatically optimizing your static image imports with the
Markdown syntax. You do not need to specify the width and height of the image,
just use the `![]()` Markdown syntax:
```md filename="Markdown"

```
This loads the `demo.png` file inside the `public` folder, and automatically
wraps it with Next.js ``.
> [!TIP]
>
> You can also use `` to load the image from a relative
> path, if you don't want to host it via `public`.
With Next.js Image, there will be no layout shift, and a beautiful blurry
placeholder will be shown by default when loading the images:

## Image zoom
> [!NOTE]
>
> The image zoom feature is enabled globally by default.
In the default configuration, if you want to use this feature, simply insert
images using `![]()` Markdown syntax.
### Disable image zoom
For `nextra-docs-theme` and `nextra-blog-theme`, you can disable image zoom by
replacing the `img` component used in MDX.
```jsx filename="theme.config.jsx"
import { Image } from 'nextra/components'
export default {
// ... your other configurations
components: {
img: props =>
}
}
```
### Enable/disable image zoom for specific images
When zoom is **disabled globally**, but you want to enable it for specific
images, you can do so by using the `` component:
```mdx
import { ImageZoom } from 'nextra/components'
```
When zoom is **enabled globally**, and you want to disable zoom for a specific
image, you can simply use the `` component:
```mdx
import { Image } from 'nextra/components'
```
================================================
FILE: docs/app/docs/guide/link/page.mdx
================================================
---
icon: LinkIcon
---
# Next.js Link
All relative Markdown links are automatically converted into
[Next.js links](https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating).
This means that the target page will be prefetched. When you click on a link,
the page will be loaded on the client-side like a
[Single-Page Application (SPA)](https://en.wikipedia.org/wiki/Single-page_application),
without making a full page load. For example:
```md filename="Markdown"
Click [here](/about) to read more.
```
Will be equivalent to:
```mdx filename="MDX"
import Link from 'next/link'
Click here to read more.
```
This feature makes navigation between Nextra pages fast and seamless.
================================================
FILE: docs/app/docs/guide/markdown/_counter.tsx
================================================
'use client'
import type { FC, ReactNode } from 'react'
import { useState } from 'react'
export const Counter: FC<{ children: ReactNode }> = ({ children }) => {
const [count, setCount] = useState(0)
return (
)
}
================================================
FILE: docs/app/docs/guide/markdown/page.mdx
================================================
---
icon: MarkdownIcon
---
import { Shadow } from '@components/shadow'
import { Counter } from './_counter'
# Markdown
## MDX
With Nextra, all your `.mdx` files under the `content` directory will be
rendered with [MDX](https://mdxjs.com/about), it's an advanced Markdown format
with React component support.
For example, you can use import and use React components inside your MDX files
like this:
```mdx filename="MDX"
## Hello MDX
import { useState } from 'react'
export function Counter({ children }) {
const [count, setCount] = useState(0)
return (
)
}
**Clicks**:
```
Generates:
Hello MDX
**Clicks**:
Besides basic MDX, Nextra also has some advanced Markdown features built-in.
## GitHub Flavored Markdown
[GFM](https://github.github.com/gfm) is an extension of Markdown, created by
GitHub, that adds support for strikethrough, task lists, tables, and more.
### Strikethrough
~~removed~~
```md filename="Markdown"
~~removed~~
```
### Task list
- [x] Write the press release
- [ ] Update the website
- [ ] Contact the media
```md filename="Markdown"
- [x] Write the press release
- [ ] Update the website
- [ ] Contact the media
```
### Table
| Syntax | Description | Test Text |
| :------------ | :---------: | ----------: |
| Header | Title | Here's this |
| Paragraph | Text | And more |
| Strikethrough | | ~~Text~~ |
```md filename="Markdown"
| Syntax | Description | Test Text |
| :------------ | :---------: | ----------: |
| Header | Title | Here's this |
| Paragraph | Text | And more |
| Strikethrough | | ~~Text~~ |
```
### Autolinks
Visit https://nextjs.org.
```md
Visit https://nextjs.org.
```
### Custom heading id
You can specify custom heading id using the format `## My heading [#custom-id]`.
For example:
```md filename="Markdown"
## Long heading about Nextra [#about-nextra]
```
In this example, `#about-nextra` will be used as the heading link, replacing the
default `#long-heading-about-nextra`.
================================================
FILE: docs/app/docs/guide/page.mdx
================================================
---
asIndexPage: true
---
import {
BrushIcon,
GlobeIcon,
LightningIcon,
PictureIcon,
StarsIcon
} from '@components/icons'
import { InformationCircleIcon, LinkIcon, MarkdownIcon } from 'nextra/icons'
import { OverviewPage } from '../../_components/overview-page'
# Guide
The following features are configured via the Next.js configuration and are
available in all themes.
================================================
FILE: docs/app/docs/guide/search/ai/page.mdx
================================================
import { Steps } from 'nextra/components'
# Ask AI
Enhance your Nextra documentation site with AI-powered chat assistance using
[Inkeep](https://docs.inkeep.com/cloud). This integration allows users to get
instant help and answers directly within your documentation, improving user
experience and reducing support burden.
## Setup
### Get an API key
Follow
[these steps](https://docs.inkeep.com/cloud/projects/overview#create-a-web-assistant)
to create an API key for your web assistant.
Copy and add the API key to your environment variables:
```dotenv filename=".env"
NEXT_PUBLIC_INKEEP_API_KEY="your_actual_api_key_here"
```
### Install the component library
Install the Inkeep React component library:
```sh npm2yarn
npm i @inkeep/cxkit-react
```
### Create the chat button component
Create a new file `inkeep-chat-button.tsx`:
```tsx filename="inkeep-chat-button.tsx"
'use client'
import { InkeepChatButton } from '@inkeep/cxkit-react'
import type { FC } from 'react'
export const ChatButton: FC = () => {
return (
)
}
```
### Add to your root layout
Import and add the `ChatButton` component to your root layout file:
```tsx filename="app/layout.tsx"
import { Layout } from 'nextra-theme-docs' // or nextra-theme-blog or your custom theme
import { Head } from 'nextra/components'
import { getPageMap } from 'nextra/page-map'
import type { FC } from 'react'
import { ChatButton } from '../path/to/your/inkeep-chat-button'
const RootLayout: FC> = async ({ children }) => {
const pageMap = await getPageMap()
return (
{children}
)
}
export default RootLayout
```
> [!IMPORTANT]
>
> The chat button will appear on all pages of your documentation site.
## Additional resources
- [Inkeep's official Nextra integration guide](https://docs.inkeep.com/cloud/integrations/nextra)
- [Inkeep React component documentation](https://docs.inkeep.com/cloud/ui-components/react/chat-button)
- [Inkeep Dashboard](https://portal.inkeep.com) for configuration and analytics
================================================
FILE: docs/app/docs/guide/search/page.mdx
================================================
import { Steps, Tabs } from 'nextra/components'
# Search Engine
Nextra includes a full-page search feature that makes it easy for users to find
relevant content across your entire documentation site.
> [!TIP]
>
> Check the
> [migration guide](https://the-guild.dev/blog/nextra-4#new-search-engine--pagefind)
> for more information.
## Setup
Nextra integrates with [Pagefind](https://pagefind.app), a static search library
that indexes your HTML files and provides lightning-fast, client-side full-text
search — all with no server required.
### Install `pagefind` as a dev dependency
```sh npm2yarn
npm i -D pagefind
```
### Add a `postbuild` script
Pagefind indexes `.html` files, so the indexing must happen **after building**
your application.
Add a `postbuild` script to your `package.json`:
```json filename="package.json"
"scripts": {
"postbuild": "pagefind --site .next/server/app --output-path public/_pagefind"
}
```
```json filename="package.json"
"scripts": {
"postbuild": "pagefind --site .next/server/app --output-path out/_pagefind"
}
```
### Ignore generated files
Add `_pagefind/` to your `.gitignore` file to avoid committing generated index
files.
### Verify indexing output
After building and running the `postbuild` script, check that a `_pagefind/`
directory exists in `public/` or `out/`. Start your app and test the search bar
to confirm everything is working.
## Configuration
Search is enabled by default. You can disable it entirely by setting
`search: false{:js}` in your `next.config.mjs` file:
```js filename="next.config.mjs" {4}
import nextra from 'nextra'
const withNextra = nextra({
search: false
})
export default withNextra()
```
To disable code block indexing while keeping search enabled set
`search: { codeblocks: false }{:js}`:
```js filename="next.config.mjs" {4}
import nextra from 'nextra'
const withNextra = nextra({
search: { codeblocks: false }
})
export default withNextra()
```
================================================
FILE: docs/app/docs/guide/ssg/page.mdx
================================================
---
icon: LightningIcon
---
import { compileMdx } from 'nextra/compile'
import { Callout } from 'nextra/components'
import { MDXRemote } from 'nextra/mdx-remote'
# Next.js Static Rendering
[Static Rendering](https://nextjs.org/docs/app/building-your-application/rendering/server-components#static-rendering-default)
is default server rendering strategy, where routes are rendered at **build
time** or in the background after
[data revalidation](https://nextjs.org/docs/app/building-your-application/data-fetching/incremental-static-regeneration).
The result is cached and can be distributed via a
[Content Delivery Network (CDN)](https://developer.mozilla.org/docs/Glossary/CDN)
for optimal performance.
Static rendering is ideal for routes with non-personalized data that can be
determined at build time, such as blog posts or product pages.
export async function SSGPage() {
const starsComponent = `{/* Via async components */}
export async function Stars() {
const response = await fetch('https://api.github.com/repos/shuding/nextra')
const repo = await response.json()
const stars = repo.stargazers_count
return {stars}
}
{/* Via async functions */}
export async function getUpdatedAt() {
const response = await fetch('https://api.github.com/repos/shuding/nextra')
const repo = await response.json()
const updatedAt = repo.updated_at
return new Date(updatedAt).toLocaleDateString()
}
Nextra has stars on GitHub!
Last repository update _{await getUpdatedAt()}_.
`
const mdx = `${starsComponent}
The number above was generated at build time via MDX server components. With
[Incremental Static Regeneration](https://nextjs.org/docs/app/building-your-application/data-fetching/incremental-static-regeneration)
enabled, it will be kept up to date.
## Example
Here's the MDX code for the example above:
~~~mdx filename="MDX"
${starsComponent}
~~~
`
const rawJs = await compileMdx(mdx)
return
}
================================================
FILE: docs/app/docs/guide/static-exports/page.mdx
================================================
import { Steps } from 'nextra/components'
# Static Exports
Export your pages statically, and deploy with [Nginx](https://nginx.org),
[GitHub Pages](https://pages.github.com) and more.
## Getting started
### Configuration
To enable a static export, update the options in `next.config.mjs` file as
follows:
```js filename="next.config.mjs" {7-10}
import nextra from 'nextra'
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
output: 'export',
images: {
unoptimized: true // mandatory, otherwise won't export
}
// Optional: Change the output directory `out` -> `dist`
// distDir: "build"
}
const withNextra = nextra({
// ... other Nextra config options
})
export default withNextra(nextConfig)
```
### Update `postbuild` script
Update the Pagefind [search engine setup](./search#add-a-postbuild-script) to
set the correct output path:
```json filename="package.json"
"scripts": {
"postbuild": "pagefind --site .next/server/app --output-path out/_pagefind"
}
```
### Building
Run the `build` command according to your package manager:
```sh npm2yarn
npm run build
```
By default, static export will be stored in `out` directory in the root of your
project.
For more in detail documentation for static export visit
[Next.js docs](https://nextjs.org/docs/app/building-your-application/deploying/static-exports).
================================================
FILE: docs/app/docs/guide/syntax-highlighting/_dynamic-code.tsx
================================================
'use client'
import { Button } from 'nextra/components'
import type { FC, ReactNode } from 'react'
import { useEffect, useRef } from 'react'
export const DynamicCode: FC<{ children: ReactNode }> = ({ children }) => {
const ref = useRef(null!)
const tokenRef = useRef(undefined)
// Find the corresponding token from the DOM
useEffect(() => {
tokenRef.current = [
...ref.current.querySelectorAll('code > span > span')
].find(el => el.textContent === '1')
}, [])
return (
<>
{children}
>
)
}
================================================
FILE: docs/app/docs/guide/syntax-highlighting/page.mdx
================================================
---
icon: StarsIcon
---
import { OptionTable } from 'components/_table'
import { compileMdx } from 'nextra/compile'
import { MDXRemote } from 'nextra/mdx-remote'
# Syntax Highlighting
Nextra uses [Shiki](https://shiki.style) to do syntax highlighting at build
time. It's very reliable and performant. For example, adding this in your
Markdown file:
````md copy=false filename="Markdown"
```js
console.log('hello, world')
```
````
Results in:
```js copy=false
console.log('hello, world')
```
## Features
### Inlined code
Inlined syntax highlighting like `let x = 1{:jsx}` is also supported via the
`{:}` syntax:
```md copy=false filename="Markdown"
Inlined syntax highlighting is also supported `let x = 1{:jsx}` via:
```
### Highlighting lines
You can highlight specific lines of code by adding a `{}` attribute to the code
block:
````md copy=false filename="Markdown"
```js {1,4-5}
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return
}
```
````
Result:
```js copy=false {1,4-5}
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return
}
```
### Highlighting substrings
You can highlight specific substrings of code by adding a `//` attribute to the
code block:
````md copy=false filename="Markdown"
```js /useState/
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return
}
```
````
```js copy=false /useState/
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return
}
```
You can highlight only a part of the occurrences of that substring by adding a
number it: `/str/1`, or multiple: `/str/1-3`, `/str/1,3`.
### Copy button
By adding a `copy` attribute, a copy button will be added to the code block when
the user hovers over it:
````md copy=false filename="Markdown"
```js copy
console.log('hello, world')
```
````
Renders:
```js copy
console.log('hello, world')
```
> [!NOTE]
>
> You can enable this feature globally by setting `defaultShowCopyCode: true` in
> your Nextra configuration (`next.config.mjs` file). Once it's enabled
> globally, you can disable it via the `copy=false` attribute.
### Line numbers
You can add line numbers to your code blocks by adding a `showLineNumbers`
attribute:
````md copy=false filename="Markdown"
```js showLineNumbers
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return
}
```
````
Renders:
```js copy=false showLineNumbers
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return
}
```
### Filenames and titles
You can add a filename or a title to your code blocks by adding a `filename`
attribute:
````md copy=false filename="Markdown"
```js filename="example.js"
console.log('hello, world')
```
````
Renders:
```js copy=false filename="example.js"
console.log('hello, world')
```
### ANSI highlighting
You can highlight ANSI escape codes:
export async function ANSI() {
const rawAnsi = `\`\`\`ansi
[0m [0;32m✓[0m [0;2msrc/[0mindex[0;2m.test.ts (1)[0m
[0;2m Test Files [0m [0;1;32m1 passed[0;98m (1)[0m
[0;2m Tests [0m [0;1;32m1 passed[0;98m (1)[0m
[0;2m Start at [0m 23:32:41
[0;2m Duration [0m 11ms
[42;1;39;0m PASS [0;32m Waiting for file changes...[0m
[0;2mpress [0;1mh[0;2m to show help, press [0;1mq[0;2m to quit
\`\`\``
const rawJs = await compileMdx(`~~~md filename="Markdown"
${rawAnsi}
~~~
Renders:
${rawAnsi}
`)
return
}
## Supported languages
Check [this list](https://github.com/shikijs/shiki/blob/main/docs/languages.md)
for all supported languages.
{/* ## Customize the Theme */}
{/* Nextra uses CSS variables to define the colors for tokens. */}
## With dynamic content
Since syntax highlighting is done at build time, you can't use dynamic content
in your code blocks. However, since MDX is very powerful there is a workaround
via client JS. For example:
import { DynamicCode } from './_dynamic-code'
```js copy=false filename="dynamic-code.js"
function hello() {
const x = 2 + 3
console.log(1)
}
```
This workaround has a limitation that updated content won't be re-highlighted.
For example if we update the number to `1 + 1`, it will be incorrectly
highlighted.
Check out the
[code](https://github.com/shuding/nextra/blob/main/docs/app/docs/guide/syntax-highlighting/_dynamic-code.tsx)
to see how it works.
## Disable syntax highlighting
You can opt out of syntax highlighting for using one of your own. You can
disable syntax highlighting globally by setting `codeHighlight: false` in your
Nextra configuration (`next.config.mjs` file).
## Custom grammar
Shiki accepts a
[VSCode TextMate Grammar](https://code.visualstudio.com/api/language-extensions/syntax-highlight-guide)
for syntax highlighting with custom language grammars.
You can provide these grammars by overriding the `getHighlighter` function in
`mdxOptions.rehypePrettyCodeOptions` option in your Nextra config inside
`next.config.mjs`:
```js copy=false filename="next.config.mjs" {13-18}
import { BUNDLED_LANGUAGES } from 'shiki'
nextra({
// ... other Nextra config options
mdxOptions: {
rehypePrettyCodeOptions: {
getHighlighter: options =>
getHighlighter({
...options,
langs: [
...BUNDLED_LANGUAGES,
// custom grammar options, see the Shiki documentation for how to provide these options
{
id: 'my-lang',
scopeName: 'source.my-lang',
aliases: ['mylang'], // Along with id, aliases will be included in the allowed names you can use when writing Markdown
path: '../../public/syntax/grammar.tmLanguage.json'
}
]
})
}
}
})
```
## Custom themes
Within `mdxOptions.rehypePrettyCodeOptions` you may also provide custom themes
instead of [relying on CSS Variables](/docs/guide/syntax-highlighting):
```js copy=false filename="next.config.mjs" {4}
nextra({
// ... other Nextra config options
mdxOptions: {
rehypePrettyCodeOptions: {
// VSCode theme or built-in Shiki theme, see Shiki documentation for more information
theme: JSON.parse(
readFileSync('./public/syntax/arctis_light.json', 'utf8')
)
}
}
})
```
### Multiple themes (dark and light mode)
Pass your themes to `theme`, where the keys represent the color mode:
```js copy=false filename="next.config.mjs" {5-8}
nextra({
// ... other Nextra config options
mdxOptions: {
rehypePrettyCodeOptions: {
theme: {
dark: 'nord',
light: 'min-light'
}
}
}
})
```
================================================
FILE: docs/app/docs/guide/turbopack/page.mdx
================================================
# Usage with Turbopack
To use [Turbopack](https://nextjs.org/docs/architecture/turbopack), simple
append the `--turbopack` flag to your development command:
```diff filename="package.json"
"scripts": {
- "dev": "next dev"
+ "dev": "next dev --turbopack"
}
```
> [!NOTE]
>
> Without `--turbopack` flag Next.js under the hood uses
> [Webpack](https://github.com/webpack/webpack), written in JavaScript.
## Only serializable options are supported
For this moment only JSON serializable values can be passed in `nextra`
function. This mean that with Turbopack enabled you can't pass custom
`remarkPlugins`, `rehypePlugins` or `recmaPlugins` since they are functions.
The following options could be passed only without Turbopack or only while
building your app with `next build`
([since Turbopack isn't support `next build` for now](https://nextjs.org/docs/architecture/turbopack#unsupported-features)
and Webpack is used instead).
```js filename="next.config.js" {5-7}
import nextra from 'nextra'
const withNextra = nextra({
mdxOptions: {
remarkPlugins: [myRemarkPlugin],
rehypePlugins: [myRehypePlugin],
recmaPlugins: [myRecmaPlugin]
}
})
```
If you try to pass them, you'll get an error from Turbopack:
```text copy=false
Error: loader nextra/loader for match "./{src/app,app}/**/page.{md,mdx}" does not have serializable options.
Ensure that options passed are plain JavaScript objects and values.
```
================================================
FILE: docs/app/docs/page.mdx
================================================
---
description:
Nextra is a framework built on top of Next.js that enables the creation of
content-focused websites. It combines the robust features of Next.js with
enhanced capabilities for crafting Markdown-based content.
---
import { Cards, Image } from 'nextra/components'
import { cloneElement } from 'react'
export default function MdxLayout(props) {
return cloneElement(props.children, {
components: {
img: Image
}
})
}
# Introduction
**Nextra** is a framework on top of Next.js, that lets you build content focused
websites. It has all the great features from Next.js, plus extra power to create
Markdown-based content with ease.
## Quick start
To start using Nextra, you need to select a theme first:
<>>
<>>
If you want to use Nextra without using these built-in themes, you can follow
the [Custom Theme](/docs/custom-theme) docs.
## FAQ
The Nextra FAQ is a collection of useful questions and answers about the
project. If you have a question that isn't answered here, please
[open a discussion](https://github.com/shuding/nextra/discussions).
{/* prettier-ignore */}
Can I use Nextra with Next.js `pages` router?
Nextra 4 only works with the `app` router. Only Nextra 1/2/3 supports the
`pages` router.
Can I use X with Nextra?
The answer is "yes" for most things. Since Nextra is just a Next.js plugin, almost all the things
that can be done with React can be done with Nextra. Here are some examples and guides:
- [Use Tailwind CSS](/docs/guide/tailwind-css)
- [Use custom CSS and Sass](/docs/guide/custom-css)
- [Use custom fonts](https://nextjs.org/docs/basic-features/font-optimization)
{/* prettier-ignore */}
How can I add a live coding component in Nextra?
There are libraries like [Sandpack](https://sandpack.codesandbox.io) and
[react-live](https://github.com/FormidableLabs/react-live) that can help you
add live coding components to your MDX.
================================================
FILE: docs/app/env.d.ts
================================================
declare module '*.svg?svgr' {
import type { FC, SVGProps } from 'react'
const ReactComponent: FC>
export default ReactComponent
}
declare module '*.mdx' {
import type { FC } from 'react'
import type { MDXComponents } from 'nextra/mdx-components'
const ReactComponent: FC<{
components?: MDXComponents
}>
export default ReactComponent
}
================================================
FILE: docs/app/globals.css
================================================
@import 'nextra-theme-docs/style.css';
@import 'tailwindcss';
/* Whitelist Tailwind CSS classes from TSDoc examples */
@source inline('aspect-video');
@source inline('py-10');
@variant dark (&:where(.dark *));
@theme {
}
body {
font-feature-settings:
'rlig' 1,
'calt' 1;
}
.home-content p {
margin-top: 1.5em;
line-height: 1.75em;
}
code.text-\[\.9em\] {
font-size: 14px;
}
@media screen and (max-width: 1200px) {
.home-content .hide-medium {
display: none;
}
}
@media screen and (max-width: 720px) {
.home-content p {
font-size: 0.9rem;
}
.home-content .hide-small {
display: none;
}
}
/* adds labels to the sidebar links */
.badge-new {
@apply dark:after:border-blue-200/30 dark:after:bg-blue-900/30 dark:after:text-blue-200;
&:after {
@apply ms-1.5 rounded-full border border-blue-200 bg-blue-100 px-1.5 text-xs font-bold text-blue-900 content-["New"];
}
}
================================================
FILE: docs/app/layout.tsx
================================================
import { getEnhancedPageMap } from '@components/get-page-map'
import { NextraLogo, VercelLogo } from '@components/icons'
import { ChatButton } from '@components/inkeep-chat-button'
import cn from 'clsx'
import type { Metadata } from 'next'
import NextImage from 'next/image'
import { Footer, Layout, Link, Navbar } from 'nextra-theme-docs'
import { Anchor, Banner, Head } from 'nextra/components'
import type { FC } from 'react'
import inkeep from './showcase/_logos/inkeep.png'
import xyflow from './showcase/_logos/xyflow.png'
import './globals.css'
export const metadata: Metadata = {
description: 'Make beautiful websites with Next.js & MDX.',
metadataBase: new URL('https://nextra.site'),
keywords: [
'Nextra',
'Next.js',
'React',
'JavaScript',
'MDX',
'Markdown',
'Static Site Generator'
],
generator: 'Next.js',
applicationName: 'Nextra',
appleWebApp: {
title: 'Nextra'
},
title: {
default: 'Nextra – Next.js Static Site Generator',
template: '%s | Nextra'
},
openGraph: {
// https://github.com/vercel/next.js/discussions/50189#discussioncomment-10826632
url: './',
siteName: 'Nextra',
locale: 'en_US',
type: 'website'
},
other: {
'msapplication-TileColor': '#fff'
},
twitter: {
site: 'https://nextra.site'
},
alternates: {
// https://github.com/vercel/next.js/discussions/50189#discussioncomment-10826632
canonical: './'
}
}
const banner = (
🎉 Nextra 4.0 has been released.{' '}
Read blogpost
)
const navbar = (
}
projectLink="https://github.com/shuding/nextra"
/>
)
const footer = (
)
const RootLayout: FC> = async ({ children }) => {
const pageMap = await getEnhancedPageMap()
return (
Sponsored by:
{[
{
url: 'https://inkeep.com',
alt: 'AI Agents that get real work done',
img: inkeep
},
{
url: 'https://xyflow.com',
alt: 'Wire your ideas with xyflow!',
img: xyflow
}
].map(o => (
))}
>
)
}}
>
{children}
)
}
export default RootLayout
================================================
FILE: docs/app/not-found.ts
================================================
export { NotFoundPage as default } from 'nextra-theme-docs'
================================================
FILE: docs/app/og/route.tsx
================================================
/* eslint react/no-unknown-property: ['error', { ignore: ['tw'] }] */
import { NextraLogo } from '@components/icons'
import { ImageResponse } from 'next/og'
export const runtime = 'edge'
// eslint-disable-next-line unicorn/prefer-top-level-await -- this will break og image
const font = fetch(new URL('Inter-SemiBold.otf', import.meta.url)).then(res =>
res.arrayBuffer()
)
export async function GET(req: Request): Promise {
try {
const { searchParams } = new URL(req.url)
// ?title=
const title =
searchParams.get('title')?.slice(0, 75) || 'Nextra Documentation'
return new ImageResponse(
Simple, powerful and flexible site generation framework{' '}
with everything you love from{' '}
Next.js
.
Get started →
Full-power documentation
in minutes
Links and images are
always optimized
Nextra automatically converts Markdown links and images to use{' '}
Next.js Link
{' '}
and{' '}
Next.js Image
{' '}
when possible. No slow navigation or layout shift.
[Learn more](/more) 
{''} {''}
Advanced syntax
highlighting solution
Performant and reliable build-time syntax highlighting powered
by Shiki.
I18n as easy as
creating new files
Place your page files in folders specific to each locale, Nextra
and Next.js will handle the rest for you.
MDX 3
{' '}
lets you use Components inside Markdown,{' '}
with huge performance boost since v1.
Dark
mode
included
Full-text search,
zero-config needed
Nextra indexes your content automatically at build-time and
performs incredibly fast full-text search via{' '}
Pagefind
.
Organize pages intuitively,
with file-system routing from Next.js
A11y as a top priority
Nextra respects system options
such as Increase Contrast and Reduce Motion.
Hybrid rendering,
next generation
You can leverage the hybrid rendering power from Next.js with
your Markdown content including{' '}
Server Components
,{' '}
Client Components
, and{' '}
Incremental Static Regeneration (ISR)
.
And more...
SEO / RTL Layout / Pluggable Themes / Built-in Components / Last
Git Edit Time / Multi-Docs...
A lot of new possibilities to be explored.
Start using Nextra →
)
}
export default IndexPage
================================================
FILE: docs/app/showcase/page.mdx
================================================
---
description:
Explore projects powered by Nextra, showcasing a diverse range of websites and
applications built with this powerful Next.js framework, from GraphQL tools to
JavaScript resources.
---
import { Cards, Image } from 'nextra/components'
import { cloneElement } from 'react'
# Showcase
Projects powered by Nextra
{/* prettier-ignore */}
<>[](https://reactflow.dev)>
<>[](https://svelteflow.dev)>
<>[](https://speakeasy.com/docs)>
<>[](https://thegraph.com/docs/en)>
<>[](https://graphql.org)>
<>[](https://the-guild.dev/graphql/hive)>
<>[](https://swr.vercel.app)>
<>[](https://cobe.vercel.app)>
<>[](https://javascriptpatterns.vercel.app)>
<>[](https://codesandbox.io/docs/learn/introduction/overview)>
<>[](https://docs.docsgpt.co.uk)>
<>[](https://cloudquery.io)>
<>[](https://edge-runtime.vercel.app)>
<>[](https://docs.sound.xyz)>
<>[](https://panda-css.com)>
<>[](https://kuma-ui.com)>
<>[](https://langfuse.com)>
<>[](https://the-guild.dev)>
<>[](https://the-guild.dev/graphql/yoga-server)>
<>[](https://the-guild.dev/graphql/envelop)>
<>[](https://the-guild.dev/graphql/inspector)>
<>[](https://the-guild.dev/graphql/codegen)>
<>[](https://the-guild.dev/graphql/mesh)>
<>[](https://the-guild.dev/graphql/tools)>
<>[](https://the-guild.dev/graphql/modules)>
<>[](https://the-guild.dev/graphql/eslint)>
<>[](https://the-guild.dev/graphql/config)>
<>[](https://the-guild.dev/graphql/scalars)>
<>[](https://the-guild.dev/graphql/shield)>
<>[](https://the-guild.dev/graphql/sofa-api)>
<>[](https://the-guild.dev/graphql/apollo-angular)>
<>[](https://the-guild.dev/graphql/sse)>
<>[](https://the-guild.dev/graphql/ws)>
<>[](https://the-guild.dev/openapi/fets)>
<>[](https://jscodechallenges.vercel.app)>
<>[](https://reactcosmos.org)>
<>[](https://typia.io)>
<>[](https://nestia.io)>
<>[](https://docs.safe.global)>
<>[](https://authjs.dev)>
<>[](https://docs.imgix.com)>
<>[](https://anythingllm.com)>
<>[](https://wiki.redbrick.land)>
<>[](https://www.makeform.ai/help)>
<>[](https://www.embedpdf.com)>
<>[](https://www.reactylon.com/docs)>
export default function MdxLayout(props) {
return cloneElement(props.children, {
components: {
img: props => (
),
a({ children, href }) {
const { alt } = children.props
return (
{children}
)
},
p(props) {
return (
)
}
}
})
}
================================================
FILE: docs/app/sponsors/page.mdx
================================================
---
description:
Support Nextra by exploring its sponsors. Learn how they contribute to the
development and success of the Nextra framework
---
import { Button, Cards, Image } from 'nextra/components'
import { cloneElement } from 'react'
# Sponsors
{/* prettier-ignore */}
<>[](https://inkeep.com)>
<>[](https://xyflow.com)>
export default function MdxLayout(props) {
return cloneElement(props.children, {
components: {
img: props => (
),
a({ children, href }) {
const { alt } = children.props
return (
{children}
)
}
}
})
}
================================================
FILE: docs/components/_slider.tsx
================================================
'use client'
import type { ComponentProps, FC } from 'react'
import { useCallback, useEffect, useState } from 'react'
export const Slider: FC<{
cssVar: string
max: number
}> = ({ cssVar, max }) => {
const handleChange: NonNullable['onChange']> =
useCallback(
e => {
const value = `${e.target.value}${max === 360 ? 'deg' : '%'}`
e.target.nextSibling!.textContent = value
document.documentElement.style.setProperty(cssVar, value)
},
[cssVar, max]
)
return (
)
}
}
================================================
FILE: docs/components/toggle-visibility-section.tsx
================================================
import { compileMdx } from 'nextra/compile'
import { MDXRemote } from 'nextra/mdx-remote'
import type { FC } from 'react'
export const ToggleVisibilitySection: FC<{
element: string
property: string
}> = async ({ element, property }) => {
const rawJs =
await compileMdx(`### Toggle Visibility [#toggle-visibility-for-${property}]
You can toggle visibility of the ${element} on the specific pages by setting \`theme.${property}\` property in the \`_meta.js\` file:
~~~js filename="_meta.js"
export default {
'my-page': {
theme: {
${property}: false // Hide ${property} on this page
}
}
}
~~~`)
return
}
================================================
FILE: docs/components/video.tsx
================================================
import type { FC } from 'react'
export const Video: FC<{ src: string }> = ({ src }) => {
return (
)
}
================================================
FILE: docs/mdx-components.tsx
================================================
import { getEnhancedPageMap } from '@components/get-page-map'
import type { Folder } from 'nextra'
import { useMDXComponents as getDocsMDXComponents } from 'nextra-theme-docs'
import type { UseMDXComponents } from 'nextra/mdx-components'
import { generateDefinition, TSDoc } from 'nextra/tsdoc'
import type { ComponentProps } from 'react'
type TSDocProps = ComponentProps
type GenerateDefinitionArgs = Parameters[0]
interface APIDocsProps
extends
Partial,
Pick {
componentName?: string
groupKeys?: string
packageName?: string
}
const { img: Image, ...docsComponents } = getDocsMDXComponents({
// @ts-expect-error -- FIXME
figure: props => ,
// @ts-expect-error -- FIXME
figcaption: props => (
),
async APIDocs({
componentName,
groupKeys,
packageName = 'nextra/components',
code: $code,
flattened,
definition: $definition,
...props
}: APIDocsProps) {
if (Object.keys(props).length) {
throw new Error(`Unexpected props: ${Object.keys(props)}`)
}
let code: string
if (componentName) {
const result = groupKeys
? `Omit & { '...props': ${groupKeys} }>`
: 'MyProps'
code = `
import type { ComponentProps } from 'react'
import type { ${componentName.split('.')[0]} } from '${packageName}'
type MyProps = ComponentProps
type $ = ${result}
export default $`
} else {
code = $code
}
const definition =
$definition ??
generateDefinition(
// @ts-expect-error -- exist
{ code, flattened }
)
// TODO pass `'/api'` as first argument
const pageMap = await getEnhancedPageMap()
const apiPageMap = pageMap.find(
(o): o is Folder => 'name' in o && o.name === 'api'
)!.children
return (
'route' in o && o.name !== 'index')
// @ts-expect-error -- fixme
.map(o => [o.title, o.route])
),
NextConfig:
'https://nextjs.org/docs/pages/api-reference/config/next-config-js',
RehypePrettyCodeOptions: 'https://rehype-pretty.pages.dev/#options',
PluggableList: 'https://github.com/unifiedjs/unified#pluggablelist',
GitHubIcon:
'https://github.com/shuding/nextra/blob/main/packages/nextra/src/client/icons/github.svg',
DiscordIcon:
'https://github.com/shuding/nextra/blob/main/packages/nextra/src/client/icons/discord.svg',
PagefindSearchOptions:
'https://github.com/CloudCannon/pagefind/blob/248f81d172316a642a83527fa92180abbb7f9c49/pagefind_web_js/types/index.d.ts#L72-L82',
ReactNode:
'https://github.com/DefinitelyTyped/DefinitelyTyped/blob/51fcf2a1c5da6da885c1f8c11224917bbc011493/types/react/index.d.ts#L426-L439',
ReactElement:
'https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d44fce6cd8765acbdb0256195e5f16f67471430d/types/react/index.d.ts#L315-L322',
TabItem:
'https://github.com/shuding/nextra/blob/fb376a635de7fa287d5ffec9dbb5f40f1cfdb0f6/packages/nextra/src/client/components/tabs/index.client.tsx#L21',
TabObjectItem:
'https://github.com/shuding/nextra/blob/fb376a635de7fa287d5ffec9dbb5f40f1cfdb0f6/packages/nextra/src/client/components/tabs/index.client.tsx#L23',
PageMapItem:
'https://github.com/shuding/nextra/blob/fb376a635de7fa287d5ffec9dbb5f40f1cfdb0f6/packages/nextra/src/types.ts#L66',
// ThemeProviderProps:
// 'https://github.com/pacocoursey/next-themes/blob/c89d0191ce0f19215d7ddfa9eb28e1e4f94d37e5/next-themes/src/types.ts#L34-L57',
LastUpdated:
'https://github.com/shuding/nextra/blob/main/packages/nextra-theme-docs/src/components/last-updated.tsx',
MDXRemote:
'https://github.com/shuding/nextra/blob/main/packages/nextra/src/client/mdx-remote.tsx',
MDXComponents:
'https://github.com/DefinitelyTyped/DefinitelyTyped/blob/4c3811099cbe9ee60151c11a679b780d0ba785bf/types/mdx/types.d.ts#L65',
ComboboxInputProps:
'https://github.com/tailwindlabs/headlessui/blob/0933dd5e5f563675c8a36e4520905bf9b58df00e/packages/%40headlessui-react/src/components/combobox/combobox.tsx#L506'
}}
/>
)
}
})
export const useMDXComponents: UseMDXComponents = (
components?: T
) => ({
...docsComponents,
// @ts-expect-error -- FIXME
img: props => (
),
...components
})
================================================
FILE: docs/next-sitemap.config.js
================================================
/** @type {import('next-sitemap').IConfig} */
export default {
siteUrl: 'https://nextra.site',
changefreq: 'weekly',
priority: '0.5',
generateIndexSitemap: false,
exclude: ['/icon.svg']
}
================================================
FILE: docs/next.config.ts
================================================
import nextra from 'nextra'
// @ts-expect-error -- fixme
function isExportNode(node, varName: string) {
if (node.type !== 'mdxjsEsm') return false
const [n] = node.data.estree.body
if (n.type !== 'ExportNamedDeclaration') return false
const name = n.declaration?.declarations?.[0].id.name
if (!name) return false
return name === varName
}
const DEFAULT_PROPERTY_PROPS = {
type: 'Property',
kind: 'init',
method: false,
shorthand: false,
computed: false
}
// @ts-expect-error -- fixme
export function createAstObject(obj) {
return {
type: 'ObjectExpression',
properties: Object.entries(obj).map(([key, value]) => ({
...DEFAULT_PROPERTY_PROPS,
key: { type: 'Identifier', name: key },
value:
value && typeof value === 'object' ? value : { type: 'Literal', value }
}))
}
}
type NextraParams = Parameters[0]
type MdxOptions = NonNullable
type RehypePlugin = NonNullable[0]
// eslint-disable-next-line unicorn/consistent-function-scoping
const rehypeOpenGraphImage: RehypePlugin = () => (ast: any) => {
// @ts-expect-error -- fixme
const frontMatterNode = ast.children.find(node =>
isExportNode(node, 'metadata')
)
if (!frontMatterNode) {
return
}
const { properties } =
frontMatterNode.data.estree.body[0].declaration.declarations[0].init
// @ts-expect-error -- fixme
const title = properties.find(o => o.key.value === 'title')?.value.value
if (!title) {
return
}
const [prop] = createAstObject({
openGraph: createAstObject({
images: `https://nextra.site/og?title=${title}`
})
}).properties
properties.push(prop)
}
const withNextra = nextra({
latex: true,
defaultShowCopyCode: true,
mdxOptions: {
rehypePlugins: [
// Provide only on `build` since turbopack on `dev` supports only serializable values
process.env.NODE_ENV === 'production' && rehypeOpenGraphImage
].filter(v => !!v)
},
whiteListTagsStyling: ['figure', 'figcaption']
})
const nextConfig = withNextra({
reactStrictMode: true,
env: {
NEXT_PUBLIC_INKEEP_API_KEY:
'dee399c7f7ac40b9de0d0b85ca32959953b9ff7c9fc8d96c'
},
redirects: () => [
{
source: '/docs/guide/:slug(typescript|latex|tailwind-css|mermaid)',
destination: '/docs/advanced/:slug',
permanent: true
},
{
source: '/docs/docs-theme/built-ins/:slug(callout|steps|tabs|bleed)',
destination: '/docs/built-ins/:slug',
permanent: true
},
{
source: '/docs/docs-theme/api/use-config',
destination: '/docs/docs-theme/api',
permanent: true
},
{
source: '/docs/guide/advanced/:slug',
destination: '/docs/advanced/:slug',
permanent: true
},
{
source: '/docs/docs-theme/theme-configuration',
destination: '/docs/docs-theme/built-ins/layout',
permanent: true
},
{
source: '/docs/docs-theme/page-configuration',
destination: '/docs/file-conventions/meta-file',
permanent: true
},
{
source: '/docs/guide/organize-files',
destination: '/docs/file-conventions',
permanent: true
},
{
source: '/docs/advanced/playground',
destination: '/docs/built-ins/playground',
permanent: true
}
],
webpack(config) {
// rule.exclude doesn't work starting from Next.js 15
const { test: _test, ...imageLoaderOptions } = config.module.rules.find(
// @ts-expect-error -- fixme
rule => rule.test?.test?.('.svg')
)
config.module.rules.push({
test: /\.svg$/,
oneOf: [
{
resourceQuery: /svgr/,
use: ['@svgr/webpack']
},
imageLoaderOptions
]
})
return config
},
turbopack: {
rules: {
'./components/icons/*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js'
}
}
},
experimental: {
optimizePackageImports: ['@components/icons']
}
})
export default nextConfig
================================================
FILE: docs/package.json
================================================
{
"name": "docs",
"author": "Shu Ding",
"license": "MIT",
"private": true,
"scripts": {
"build": "next build --webpack",
"dev": "next",
"postbuild": "next-sitemap && pagefind --site .next/server/app --output-path public/_pagefind",
"start": "next start"
},
"dependencies": {
"@inkeep/cxkit-react": "^0.5.98",
"clsx": "^2.1.0",
"framer-motion": "^12.0.0",
"next": "^16.0.7",
"nextra": "workspace:*",
"nextra-theme-docs": "workspace:*",
"react": "19.1.0",
"react-dom": "19.1.0"
},
"devDependencies": {
"@svgr/webpack": "^8.0.1",
"@tailwindcss/postcss": "4.1.10",
"@types/node": "^22.0.0",
"@types/react": "^19.1.8",
"next-sitemap": "^4.2.3",
"pagefind": "^1.3.0",
"tailwindcss": "4.1.10"
},
"browserslist": [
">= .25%",
"not dead"
]
}
================================================
FILE: docs/postcss.config.mjs
================================================
// If you want to use other PostCSS plugins, see the following:
// https://tailwindcss.com/docs/using-with-preprocessors
/** @type {import('postcss').Postcss} */
export default {
plugins: {
'@tailwindcss/postcss': {}
}
}
================================================
FILE: docs/tsconfig.json
================================================
{
"compilerOptions": {
"baseUrl": ".",
"target": "es2022",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"isolatedModules": true,
"moduleResolution": "bundler",
"jsx": "react-jsx",
"module": "esnext",
"resolveJsonModule": true,
"paths": {
"@components/*": ["components/*"],
// These work at runtime due to Next.js internal resolution or custom config,
// but we include them here so TypeScript doesn't complain.
"private-next-root-dir/*": ["./*"],
"next-mdx-import-source-file": ["./mdx-components.tsx"]
},
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true,
"noUncheckedIndexedAccess": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": ["node_modules", ".next"]
}
================================================
FILE: docs/vercel.json
================================================
{
"public": true
}
================================================
FILE: examples/blog/app/_meta.global.js
================================================
export default {
index: {
type: 'page'
},
posts: {
type: 'page',
items: {
draft: {
display: 'hidden'
}
}
}
}
================================================
FILE: examples/blog/app/layout.jsx
================================================
import { Footer, Layout, Navbar, ThemeSwitch } from 'nextra-theme-blog'
import { Banner, Head, Search } from 'nextra/components'
import { getPageMap } from 'nextra/page-map'
import 'nextra-theme-blog/style.css'
export const metadata = {
title: 'Blog Example'
}
export default async function RootLayout({ children }) {
const banner = (
🎉 Nextra 4.0 is released.{' '}
Read more →
)
return (
{children}
)
}
================================================
FILE: examples/blog/app/page.mdx
================================================
---
title: About
---
Hey there!

This Markdown image is automatically converted into a
[Next.js Image](https://nextjs.org/docs/api-reference/next/image).
================================================
FILE: examples/blog/app/posts/(with-comments)/aaron-swartz-a-programmable-web/page.mdx
================================================
---
title: Notes on A Programmable Web by Aaron Swartz
date: 2016-05-21
description:
At the time when I was getting into web development, I had the chance to read
one of the most inspiring book about the web, Aaron Swartz's A Programmable
Web. And it completely changed my mind.
tags: [web development]
author: Shu
---
At the time when I was getting into web development, I had the chance to read
one of the most inspiring book about the web, Aaron Swartz's A Programmable Web.
And it completely changed my mind.
**Updated in
2017**: [@tommyjtl](https://github.com/tommyjtl), [@zimine](https://github.com/zimine) and
I are currently helping translate this post into Simplified Chinese.
Any [contributions](https://github.com/tommyjtl/the-programmable-web/tree/translation) are
welcomed :)
---
> Sure, it sounds a little bit crazy. But it paid off the last time they made
> that gamble: we ended up with a little thing called the World Wide Web. Let's
> see if they can do it again.
— Aaron
Read Aaron's original post:
[Building Programmable Web Sites by Aaron Swartz, 2009](http://www.cs.rpi.edu/~hendler/ProgrammableWebSwartz2009.html).
## Quotes About WWW
1. "(the Internet) It should be kept open. It should be kept free." – Steve Jobs
2. "The third reason it's very exciting is that Microsoft doesn't own it and I
don't think they can. It's the one thing in the industry that Microsoft can
probably never own. I think one of the things that's essential is that the
government continue to fund the Internet as a public trust, as a public
facility and remove any of these ridiculous notions of privatizing it that
have been brought up. I don't think they're going to fly, thankfully." –
Steve Jobs
3. Jacques Mattheij wrote an article, about the web in 2050, which feels like
the *New Speak* in George Orwell's _1984_.
## Quotes from Aaron's Post
About API
> APIs only let you look at the data in a **particular way**, typically the way
> that the hosting site looks at it
About API, II (Q: differences between data, protocol, API?)
> pass the noun to the verb: /share?v=1234 pass the verb to the noun:
> /v/1234?m=share
Then he describes the interesting hybrid that the Web adopted, which he terms
"Representational State Transfer" or REST.
About "standard" vs "design"
> And instead of spending time building things, they've convinced people
> interested in these ideas that the first thing we need to do is write
> standards. (**To engineers, this is absurd from the start -- standards are
> things you write after you've got something working, not before!**)
Fortunately for us, the Web was designed with this future in mind. The protocols
that underpin it are not designed simply to provide pages for human consumption,
but also to easily accommodate the menagerie of spiders, bots, and scripts that
explore its fertile soil. ...for applications.
Then we'll look into what it means for your application to be not just another
tool for people and software to use, but **part of the ecology** -- a section of
the programmable web. This means exposing your data to be queried and copied and
integrated, even without explicit permission, into the larger software
ecosystem, while protecting users' freedom.
The more likely option is, of course, to break away from the Web altogether, and
force people to download special software to use your application. ... If that's
a choice you want to make, you probably shouldn't be reading this book.
This is one of the secrets of success on the Web: the more you send people away,
the more they come back
About URL and URI
> Moreover, URLs do not just exist as isolated entities
URLs shouldn't change (and if they do change, the old ones should redirect to
the new ones) so they should only contain information about the page that never
changes. This leads to some obvious requirements.
First, URLs shouldn't include technical details of the software you used to
build your website, since that could change at any moment
About TBL
> And even then, they're far more limited than the wide-reaching interactivity
> that Berners-Lee imagined.
Instead, they used the clone created by a team at the University of Illinois
Urbana-Champaign (UIUC), which never supported editing because programmer Marc
Andreesen was too dumb to figure out how to do page editing with inline
pictures, something Tim Berners-Lee's version had no problem with.
(related: http://www.aaronsw.com/weblog/mylifewithtim)
## External links
1. [Steve Jobs interview: One-on-one in 1995](http://computerworld.com/article/2498543/it-management/steve-jobs-interview--one-on-one-in-1995.html?page=8)
2. [Solid: Re-decentralizing the web](https://solid.mit.edu)
3. [Aaron's public links & notes tagged with "web" on Pinboard](https://pinboard.in/u:aaronsw/t:web)
4. ["URLs shouldn't include technical details": 天猫首页](https://zhihu.com/question/54777923/answer/141058259)
5. [Semantic Web – W3C](https://w3.org/standards/semanticweb)
6. [Tim Berners-Lee: The next web](https://ted.com/talks/tim_berners_lee_on_the_next_web)
7. [The Web in 2050 · Jacques Mattheij](https://jacquesmattheij.com/the-web-in-2050):
"reboot the web"
================================================
FILE: examples/blog/app/posts/(with-comments)/code-blocks/page.mdx
================================================
---
title: Code blocks
date: 2022-07-29
description: En example of using code blocks in your blog.
tags:
[
web development,
JavaScript,
GraphQL,
C++,
Java,
React,
Next.js,
The Guild,
MacBook Pro
]
author: Dimitri POSTOLOV
---
## Test `filename`, line highlighting and empty lines
```js filename="test.js" {1}
console.log('hello world')
console.log('goodbye world')
```
## Test `showLineNumbers` and word highlighting
```scala showLineNumbers {2-4} /println/
object Hello {
def main(args: Array[String]) = {
println("hello, world")
}
}
object Hello {
def main(args: Array[String]) = {
println("hello, world")
}
}
```
## Test highlighting inline code
`import React from 'react'{:js}`
## Test without specified language
```text /hello/
hello world
```
## Test with code block default language
```
const links = [
{ href: '/settings', label: 'Settings' },
{ href: '/support', label: 'Support' },
{ href: '/license', label: 'License' },
]
```
## Test link in code
Link to [`google`](https://google.com)
Link to [GitHub](https://github.com)
================================================
FILE: examples/blog/app/posts/(with-comments)/draft/page.mdx
================================================
---
title: Draft
date: 2023-06-28
description: An example of a draft post.
tags: [web development]
author: Ada Lovelace
---
Because this post has `display: 'hidden'` defined in `_meta.global.js`, it won't
show up in the [list of posts](/posts) or
[list of posts by tag](/tags/web%20development) but can be access directly by
its url at [/posts/draft](/posts/draft).
================================================
FILE: examples/blog/app/posts/(with-comments)/layout.jsx
================================================
import { Comments } from 'nextra-theme-blog'
export default function CommentsLayout({ children }) {
return (
<>
{children}
>
)
}
================================================
FILE: examples/blog/app/posts/(with-comments)/lists/page.mdx
================================================
---
title: Lists
date: 2024-07-11
description: Example of ordered/unordered nested lists.
tags: [web development]
author: Dimitri POSTOLOV
---
- **foo**
- bar
- **baz**
- qux
- **qwe**
1. he
1. **be**
1. wo
1. **be** da
- da
- ba
================================================
FILE: examples/blog/app/posts/(with-comments)/nextra-components/page.mdx
================================================
---
title: Nextra Components
date: 2023-05-15
description: En example of using the Callout component in your blog.
tags: [web development]
author: Tristan Dubbeld
---
import { Bleed, Callout, Cards, FileTree, Steps, Tabs } from 'nextra/components'
import { CopyIcon, GitHubIcon, MenuIcon } from 'nextra/icons'
## ``
### Default
This is a default callout.
### Error
This is an error callout.
### Info
This is an info callout. [^2]
### Warning
This is a warning callout.
### With custom emoji [^1]
This is a callout with a custom emoji.
## ``
### Step 1
Content for step 1.
### Step 2
Contents for step 2.
## Test
Run pnpm test
### nextra:test
### nextra-theme-docs:test
#### more
#### more more
#### more more more
### nextra-theme-blog:test
## Lint
## ``
{/* prettier-ignore */}
**pnpm**: Fast, disk space efficient package manager.**npm** is a package manager for the JavaScript programming language.**Yarn** is a software packaging system.
## ``
## ``
<>>
<>>
} title="Callout" href="/foo" />
} title="Tabs" href="/bar" />
} title="Steps" href="/baz" />
## `` and ``
Summary
Details
Summary 2
Details 2
## ``

## GitHub Alert Syntax
> [!NOTE]
>
> Useful information that users should know, even when skimming content.
> [!TIP]
>
> Helpful advice for doing things better or more easily.
> [!IMPORTANT]
>
> Key information users need to know to achieve their goal.
> [!WARNING]
>
> Urgent info that needs immediate user attention to avoid problems.
> [!CAUTION]
>
> Advises about risks or negative outcomes of certain actions.
[^1]: With custom emoji
[^2]: This is an info callout.
================================================
FILE: examples/blog/app/posts/(with-comments)/table/page.mdx
================================================
---
title: Table
date: 2022-08-28
description: En example of using table.
tags: [web development]
author: Dimitri POSTOLOV
---
## Test table
| Left | Center | Right | Right | Right | Right | Right | Right | Right | Right |
| ---- | :----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: |
| ss2 | 333 | 3232 | 3232 | 3232 | 3232 | 3232 | 3232 | 3232 | 3232 |
| | 222 | 3232 | 3232 | 3232 | 3232 | 3232 | 3232 | 3232 | 3232 |
| | 23 | | | | | | | | |
================================================
FILE: examples/blog/app/posts/get-posts.js
================================================
import { normalizePages } from 'nextra/normalize-pages'
import { getPageMap } from 'nextra/page-map'
export async function getPosts() {
const { directories } = normalizePages({
list: await getPageMap('/posts'),
route: '/posts'
})
return directories
.filter(post => post.name !== 'index')
.sort((a, b) => new Date(b.frontMatter.date) - new Date(a.frontMatter.date))
}
export async function getTags() {
const posts = await getPosts()
const tags = posts.flatMap(post => post.frontMatter.tags)
return tags
}
================================================
FILE: examples/blog/app/posts/page.jsx
================================================
import Link from 'next/link'
import { PostCard } from 'nextra-theme-blog'
import { getPosts, getTags } from './get-posts'
export const metadata = {
title: 'Posts'
}
export default async function PostsPage() {
const tags = await getTags()
const posts = await getPosts()
const allTags = Object.create(null)
for (const tag of tags) {
allTags[tag] ??= 0
allTags[tag] += 1
}
return (
)
}
================================================
FILE: examples/docs/src/content/_meta.js
================================================
export default {
index: '',
'get-started': '',
features: '',
themes: '',
advanced: {
theme: {
copyPage: false
}
}
}
================================================
FILE: examples/docs/src/content/advanced/code-highlighting.mdx
================================================
# Code Highlighting
Nextra uses [Shiki](https://shiki.style) and
[Rehype Pretty Code](https://github.com/FormidableLabs/prism-react-renderer) to
highlight the code blocks. This section covers how you can customize it.
## Meta strings
### Highlight lines
````mdx
```jsx {1,3-5}
import 'nextra-theme-docs/style.css'
export default function Nextra({ Component, pageProps }) {
const getLayout = Component.getLayout || (page => page)
return getLayout()
}
```
````
```jsx {1,4-5}
import 'nextra-theme-docs/style.css'
export default function Nextra({ Component, pageProps }) {
const getLayout = Component.getLayout || (page => page)
return getLayout()
}
```
### Title
````mdx
```jsx filename="_app.js"
import 'nextra-theme-docs/style.css'
export default function Nextra({ Component, pageProps }) {
const getLayout = Component.getLayout || (page => page)
return getLayout()
}
```
````
```jsx filename="_app.js"
import 'nextra-theme-docs/style.css'
export default function Nextra({ Component, pageProps }) {
const getLayout = Component.getLayout || (page => page)
return getLayout()
}
```
## Supported Languages
You can find a list of supported languages
[here](https://github.com/shikijs/shiki/blob/main/docs/languages.md).
================================================
FILE: examples/docs/src/content/features/_meta.js
================================================
export default {
mdx: '',
ssg: '',
i18n: '',
image: '',
themes: '',
latex: ''
}
================================================
FILE: examples/docs/src/content/features/i18n.mdx
================================================
# Next.js I18n
> [!NOTE]
>
> This feature is only available in the docs theme.
Nextra supports
[Next.js Internationalized Routing](https://nextjs.org/docs/advanced-features/i18n-routing)
out of the box.
To add multi-language pages to your Nextra application, just need to config
`i18n` in `next.config.mjs`:
```js filename="next.config.mjs"
import nextra from 'nextra'
const withNextra = nextra({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.js'
})
export default withNextra({
i18n: {
locales: ['en', 'zh', 'de'],
defaultLocale: 'en'
}
})
```
Then, add the locale codes to your file extensions (required for the default
locale too):
```text
/pages
index.en.md
index.zh.md
index.de.md
meta.en.json
meta.zh.json
meta.de.json
...
```
Finally, add the `i18n` option to your `theme.config.js` to configure the
language dropdown:
```jsx filename="theme.config.js"
i18n: [
{ locale: 'en', text: 'English' },
{ locale: 'zh', text: '中文' },
{ locale: 'de', text: 'Deutsch' },
{ locale: 'ar', text: 'العربية', direction: 'rtl' }
]
```
================================================
FILE: examples/docs/src/content/features/image.mdx
================================================
# Next.js Image
You can use [Next.js Image](https://nextjs.org/docs/app/getting-started/images)
directly in MDX.
If the `demo.png` file is located at `/public/demo.png`, you can use the code
below to display it:
```mdx
import Image from 'next/image'
```
## Static Image
> [!NOTE]
>
> This feature is enabled via `staticImage: true` in the Nextra config by
> default.
Nextra also supports automatic static image imports, you no longer need to
specify the width and height of the image manually, and you can directly use the
Markdown syntax to display the same image:
```mdx

```
With Next.js Image, there will be no layout shift, and a beautiful blurry
placeholder will be shown by default when loading the images:

================================================
FILE: examples/docs/src/content/features/latex.mdx
================================================
{/* is unsupported in Metadata API https://nextjs.org/docs/app/api-reference/functions/generate-metadata#unsupported-metadata */}
# LaTeX
Nextra uses [KaTeX](https://katex.org) to render LaTeX expressions directly in
MDX. To enable LaTeX support, you must add the following to your
`next.config.mjs`:
```js filename="next.config.mjs"
import nextra from 'nextra'
const withNextra = nextra({
latex: true
})
export default withNextra()
```
Using LaTeX within MDX is as simple as wrapping your expression in `$$` or `$`.
For example, the following code
```latex
$\sqrt{a^2 + b^2}$
```
will be rendered as: $\sqrt{a^2 + b^2}$
To learn more about KaTeX and its supported functions, visit their
[documentation](https://katex.org/docs/supported.html).
================================================
FILE: examples/docs/src/content/features/mdx.mdx
================================================
import { compileMdx } from 'nextra/compile'
import { Callout } from 'nextra/components'
import { MDXRemote } from 'nextra/mdx-remote'
# MDX
With Nextra, all your `.md` and `.mdx` files under the pages directory will be
rendered with [MDX](https://mdxjs.com/about), it's an advanced Markdown format
with React component support.
You can use import and use React components inside your Markdown files like
this:
export async function Demo() {
const mdx = `import { Callout } from 'nextra/components'
**Markdown With React Components**
**MDX** (the library), at its core, transforms MDX (the syntax) to JSX. It
receives an MDX string and outputs a _JSX string_. It does this by parsing the
MDX document to a syntax tree and then generates a JSX document from that
tree.
`
const rawJs = await compileMdx(`~~~mdx filename="example.mdx"
${mdx}
~~~
Generates:
${mdx}`)
return
}
## Headings
# **Hello**, This Is a _Title_ Inside `h1`
## **Hello**, This Is a _Title_ Inside `h2`
### **Hello**, This Is a _Title_ Inside `h3`
#### **Hello**, This Is a _Title_ Inside `h4`
##### **Hello**, This Is a _Title_ Inside `h5`
###### **Hello**, This Is a _Title_ Inside `h6`
## List
1. one
1. two
1. three
- one
- two
- three
## Task List
```mdx
- [x] Write the press release
- [ ] Update the website
- [ ] Contact the media
```
Renders
- [x] Write the press release
- [ ] Update the website
- [ ] Contact the media
## Syntax Highlighting
Automatic syntax highlighting
````mdx
```js
console.log('hello, world')
```
````
Renders:
```js
console.log('hello, world')
```
You can also add the `{line|range}` modifier to highlight specific lines:
````mdx
```jsx {4,6-8}
import useSWR from 'swr'
function Profile() {
const { data, error } = useSWR('/api/user', fetcher)
if (error) return
failed to load
if (!data) return
loading...
return
hello {data.name}!
}
```
````
```jsx {4,6-8}
import useSWR from 'swr'
function Profile() {
const { data, error } = useSWR('/api/user', fetcher)
if (error) return
)
}
================================================
FILE: packages/nextra/src/client/components/index.ts
================================================
// @ts-expect-error -- types available only with "moduleResolution": "bundler" in tsconfig
export { Mermaid } from '@theguild/remark-mermaid/mermaid'
export { MathJax, MathJaxContext } from 'better-react-mathjax'
export { Banner } from './banner/index.js'
export { FileTree } from './file-tree/index.js'
export { SkipNavContent, SkipNavLink } from './skip-nav/index.js'
export { Popup } from './popup/index.js'
export { Tabs } from './tabs/index.js'
export { Bleed } from './bleed.js'
export { Button } from './button.js'
export { Callout } from './callout.js'
export { Cards } from './cards.js'
export { Collapse } from './collapse.js'
export { Head } from './head.js'
export { ImageZoom } from './image-zoom.js'
export { Playground } from './playground.js'
export { Search } from './search.js'
export { Select } from './select.js'
export { Steps } from './steps.js'
export * from '../hocs/index.js'
export * from '../mdx-components/index.js'
================================================
FILE: packages/nextra/src/client/components/playground.tsx
================================================
'use client'
import { useEffect, useState } from 'react'
import type { FC, ReactElement } from 'react'
import { evaluate } from '../evaluate.js'
import type { MDXRemoteProps } from '../mdx-remote.js'
import { Callout } from './callout.js'
type PlaygroundProps = {
/**
* String with source MDX.
* @example '# hello world nice to see you'
*/
source: string
/**
* Fallback component for loading.
* @default null
*/
fallback?: ReactElement | null
} & Pick
/**
* A built-in component lets you write Nextra-compatible MDX that renders only on the client.
* @example
*
*
* @usage
* ```mdx filename="Basic Usage"
* import { Playground } from 'nextra/components'
*
* # Playground
*
* Below is a playground component. It mixes into the rest of your MDX perfectly.
*
* }}
* />
* ```
*
* You may also specify a fallback component like so:
*
* ```mdx filename="Usage with Fallback"
* import { Playground } from 'nextra/components'
*
* }}
* fallback={
Loading playground...
}
* />
* ```
*
* ### Avoiding unstyled outputs
*
* To prevent unstyled elements, import `useMDXComponents` from your
* `mdx-components` file. Call this function and pass the returned components to
* the `components` prop. You can also include your custom components as the first
* argument:
*
* ```mdx {1,6-8}
* import { Playground } from 'nextra/components'
* import { useMDXComponents } from '../path/to/my/mdx-components'
*
*
* })}
* fallback={
Loading playground...
}
* />
* ```
*/
export const Playground: FC = ({
source,
fallback = null,
components,
scope
}) => {
const [compiledSource, setCompiledSource] = useState('')
const [error, setError] = useState()
useEffect(() => {
async function doCompile() {
// Importing in useEffect to not increase global bundle size
const { compileMdx } = await importCompile()
try {
const rawJs = await compileMdx(source)
setCompiledSource(rawJs)
setError(null)
} catch (error) {
setError(error)
}
}
doCompile()
}, [source])
if (error) {
return (
Could not compile code
{error instanceof Error
? `${error.name}: ${error.message}`
: String(error)}
)
}
if (compiledSource) {
// `` cannot be used here because `useMDXComponents` may include components that
// are only available on the server.
const MDXContent = evaluate(compiledSource, components, scope).default
return
}
return fallback
}
// Otherwise react-compiler fails
function importCompile() {
return import('../../server/compile.js')
}
================================================
FILE: packages/nextra/src/client/components/popup/index.client.tsx
================================================
'use client'
import { Popover, PopoverPanel } from '@headlessui/react'
import type { PopoverPanelProps, PopoverProps } from '@headlessui/react'
import cn from 'clsx'
import { createContext, useContext, useState } from 'react'
import type { FC, MouseEventHandler } from 'react'
const PopupContext = createContext(null)
function usePopup(): boolean {
const ctx = useContext(PopupContext)
if (typeof ctx !== 'boolean') {
// eslint-disable-next-line unicorn/prefer-type-error -- Doesn't fit in this case
throw new Error('`usePopup` must be used within a `` component')
}
return ctx
}
export const Popup: FC = props => {
const [isOpen, setIsOpen] = useState(false)
const handleMouse: MouseEventHandler = event => {
setIsOpen(event.type === 'mouseenter')
}
return (
)
}
export const PopupPanel: FC = props => {
const isOpen = usePopup()
return (
)
}
================================================
FILE: packages/nextra/src/client/components/popup/index.tsx
================================================
'use no memo'
import { PopoverButton } from '@headlessui/react'
import type { ComponentProps } from 'react'
import { Popup as _Popup, PopupPanel } from './index.client.js'
// Workaround to fix
// Error: Cannot access Popup.Button on the server. You cannot dot into a client
// module from a server component. You can only pass the imported name through.
export const Popup = Object.assign(
(props: ComponentProps) => <_Popup {...props} />,
{
Button: PopoverButton,
Panel: PopupPanel
}
)
================================================
FILE: packages/nextra/src/client/components/search.tsx
================================================
'use client'
import {
Combobox,
ComboboxInput,
ComboboxOption,
ComboboxOptions,
type ComboboxInputProps
} from '@headlessui/react'
import cn from 'clsx'
import { addBasePath } from 'next/dist/client/add-base-path'
import NextLink from 'next/link'
import { useRouter } from 'next/navigation'
import type {
FC,
FocusEventHandler,
ReactElement,
ReactNode,
SyntheticEvent
} from 'react'
import { useDeferredValue, useEffect, useRef, useState } from 'react'
import type { PagefindSearchOptions } from '../../types.js'
import { useMounted } from '../hooks/use-mounted.js'
import { InformationCircleIcon, SpinnerIcon } from '../icons/index.js'
// Fix React Compiler (BuildHIR::lowerExpression) Handle Import expressions
export async function importPagefind() {
window.pagefind = await import(
/* webpackIgnore: true */ addBasePath('/_pagefind/pagefind.js')
)
await window.pagefind!.options({
baseUrl: addBasePath('/')
// ... more search options
})
}
type PagefindResult = {
excerpt: string
meta: {
title: string
}
raw_url: string
sub_results: {
excerpt: string
title: string
url: string
}[]
url: string
}
type InputProps = Omit<
ComboboxInputProps,
'className' | 'onChange' | 'onFocus' | 'onBlur' | 'value' | 'placeholder'
>
interface SearchProps extends InputProps {
/**
* Not found text.
* @default 'No results found.'
*/
emptyResult?: ReactNode
/**
* Error text.
* @default 'Failed to load search index.'
* */
errorText?: ReactNode
/**
* Loading text.
* @default 'Loading…'
*/
loading?: ReactNode
/**
* Placeholder text.
* @default 'Search documentation…'
*/
placeholder?: string
/** Input container CSS class name. */
className?: string
searchOptions?: PagefindSearchOptions
/**
* Callback function that triggers whenever the search input changes.
*
* This prop is **not serializable** and cannot be used directly in a server-side layout.
*
* To use this prop, wrap the component in a **client-side** wrapper. Example:
*
* ```tsx filename="search-with-callback.jsx"
* 'use client'
*
* import { Search } from 'nextra/components'
*
* export function SearchWithCallback() {
* return (
* {
* console.log('Search query:', query)
* }}
* />
* )
* }
* ```
*
* Then pass the wrapper to the layout:
*
* ```tsx filename="app/layout.jsx"
* import { SearchWithCallback } from '../path/to/your/search-with-callback'
* // ...
* } {...rest} />
* ```
*
* @param query - The current search input string.
*/
onSearch?: (query: string) => void
}
const INPUTS = new Set(['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA'])
const DEV_SEARCH_NOTICE = (
<>
Search isn't available in development because Nextra 4 uses
Pagefind package, which indexes built `.html` files instead of
`.md`/`.mdx`.
To test search during development, run `next build` and then restart your
app with `next dev`.
>
)
/**
* A built-in search component provides a seamless and fast search
* experience out of the box. Under the hood, it leverages the
* [Pagefind package](https://pagefind.app) — a fully client-side search engine optimized for static
* sites. Pagefind indexes your content at build time and enables highly performant,
* zero-JavaScript-dependency searches at runtime.
*
* @see [Nextra search setup guide](https://nextra.site/docs/guide/search)
*/
export const Search: FC = ({
className,
emptyResult = 'No results found.',
errorText = 'Failed to load search index.',
loading = 'Loading…',
placeholder = 'Search documentation…',
searchOptions,
onSearch,
...props
}) => {
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState('')
const [results, setResults] = useState([])
const [search, setSearch] = useState('')
// https://github.com/shuding/nextra/pull/3514
// defer pagefind results update for prioritizing user input state
const deferredSearch = useDeferredValue(search)
useEffect(() => {
const handleSearch = async (value: string) => {
if (!value) {
setResults([])
setError('')
return
}
setIsLoading(true)
if (!window.pagefind) {
try {
await importPagefind()
} catch (error) {
const message =
error instanceof Error
? process.env.NODE_ENV !== 'production' &&
error.message.includes('Failed to fetch')
? DEV_SEARCH_NOTICE // This error will be tree-shaked in production
: `${error.constructor.name}: ${error.message}`
: String(error)
setError(message)
setIsLoading(false)
return
}
}
const response = await window.pagefind!.debouncedSearch(
value,
searchOptions
)
if (!response) return
const data = await Promise.all(response.results.map(o => o.data()))
setIsLoading(false)
setError('')
setResults(
data.map(newData => ({
...newData,
sub_results: newData.sub_results.map(r => {
const url = r.url.replace(/\.html$/, '').replace(/\.html#/, '#')
return { ...r, url }
})
}))
)
}
handleSearch(deferredSearch)
}, [deferredSearch]) // eslint-disable-line react-hooks/exhaustive-deps -- ignore searchOptions
const router = useRouter()
const [focused, setFocused] = useState(false)
const mounted = useMounted()
const inputRef = useRef(null!)
useEffect(() => {
function handleKeyDown(event: KeyboardEvent) {
const el = document.activeElement
if (
!el ||
INPUTS.has(el.tagName) ||
(el as HTMLElement).isContentEditable
) {
return
}
if (
event.key === '/' ||
(event.key === 'k' &&
!event.shiftKey &&
(navigator.userAgent.includes('Mac') ? event.metaKey : event.ctrlKey))
) {
event.preventDefault()
// prevent to scroll to top
inputRef.current.focus({ preventScroll: true })
}
}
window.addEventListener('keydown', handleKeyDown)
return () => {
window.removeEventListener('keydown', handleKeyDown)
}
}, [])
const shortcut = (
{mounted && navigator.userAgent.includes('Mac') ? (
<>
⌘K
>
) : (
'CTRL K'
)}
)
const handleFocus: FocusEventHandler = event => {
const isFocus = event.type === 'focus'
setFocused(isFocus)
}
const handleChange = (event: SyntheticEvent) => {
const { value } = event.currentTarget
setSearch(value)
onSearch?.(value)
}
const handleSelect = (searchResult: PagefindResult | null) => {
if (!searchResult) return
// Calling before navigation so selector `html:not(:has(*:focus))` in styles.css will work,
// and we'll have padding top since input is not focused
inputRef.current.blur()
const [url, hash] = searchResult.url.split('#')
const isSamePathname = location.pathname === url
// `useHash` hook doesn't work with NextLink, and clicking on search
// result from same page doesn't scroll to the heading
if (isSamePathname) {
location.href = `#${hash}`
} else {
router.push(searchResult.url)
}
setSearch('')
}
return (
)}
))}
)
}
================================================
FILE: packages/nextra/src/client/components/skip-nav/index.client.tsx
================================================
'use client'
/**
* The code included in this file is inspired by https://github.com/reach/reach-ui/blob/43f450db7bcb25a743121fe31355f2294065a049/packages/skip-nav/src/reach-skip-nav.tsx which is part of the @reach/skip-nav library.
*
* @reach/skip-nav is licensed as follows:
* The MIT License (MIT)
*
* Copyright (c) 2018-2023, React Training LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Source: https://github.com/reach/reach-ui/blob/43f450db7bcb25a743121fe31355f2294065a049/LICENSE
*/
import { Button } from '@headlessui/react'
import type { ButtonProps } from '@headlessui/react'
import cn from 'clsx'
import type { FC } from 'react'
const DEFAULT_ID = 'nextra-skip-nav'
const DEFAULT_LABEL = 'Skip to Content'
export const SkipNavLink: FC = ({
// Give the option to the user to pass a falsy other than undefined to remove the default styles
className,
id = DEFAULT_ID,
children = DEFAULT_LABEL
}: Pick) => {
return (
)
}
================================================
FILE: packages/nextra/src/client/components/skip-nav/index.tsx
================================================
import type { FC, HTMLAttributes } from 'react'
const DEFAULT_ID = 'nextra-skip-nav'
export { SkipNavLink } from './index.client.js'
export const SkipNavContent: FC, 'id'>> = ({
id = DEFAULT_ID
}) => {
return
}
================================================
FILE: packages/nextra/src/client/components/steps.tsx
================================================
import cn from 'clsx'
import type { FC, HTMLAttributes } from 'react'
import { useId } from 'react'
/**
* A built-in component to turn a numbered list into a visual representation of
* steps.
*
* @example
*
*
* ### This is the first step
*
* This is the first step description.
*
* ### This is the second step
*
* This is the second step description.
*
* ### This is the third step
*
* This is the third step description.
*
*
*
* @usage
* Wrap a set of Markdown headings (from `
` to `
`) with the ``
* component to display them as visual steps. You can choose the appropriate
* heading level based on the content hierarchy on the page.
*
* ```mdx filename="MDX" {7-15}
* import { Steps } from 'nextra/components'
*
* ## Getting Started
*
* Here is some description.
*
*
* ### Step 1
*
* Contents for step 1.
*
* ### Step 2
*
* Contents for step 2.
*
* ```
*
* ### Excluding Headings from Table of Contents
*
* To exclude the headings from the `` component (or any other headings)
* to appear in the Table of Contents, replace the Markdown headings `### ...`
* with `
` HTML element wrapped in curly braces.
*
* ```diff filename="MDX"
*
* - ### Step 1
* + {
)
}
================================================
FILE: packages/nextra/src/client/mdx-components/pre/toggle-word-wrap-button.tsx
================================================
'use client'
import type { FC, ReactNode } from 'react'
import { Button } from '../../components/button.js'
function toggleWordWrap() {
const htmlDataset = document.documentElement.dataset
const hasWordWrap = 'nextraWordWrap' in htmlDataset
if (hasWordWrap) {
delete htmlDataset.nextraWordWrap
} else {
htmlDataset.nextraWordWrap = ''
}
}
export const ToggleWordWrapButton: FC<{
children: ReactNode
}> = ({ children }) => {
return (
)
}
================================================
FILE: packages/nextra/src/client/mdx-components/summary.tsx
================================================
import cn from 'clsx'
import type { FC, HTMLAttributes } from 'react'
import { ArrowRightIcon, LinkIcon } from '../icons/index.js'
export const Summary: FC> = ({
children,
className,
id,
...props
}) => {
return (
summary:first-child>&]:rotate-90 x:transition'
)}
strokeWidth="3"
/>
` jumps to incorrect position in viewport.
// Also, it's better to put `` content inside heading, so Pagefind will have
// sub result title of this `` content
id={id}
className="x:grow x:hyphens-auto x:p-1"
>
{children}