Nextra 4 x App Router. What's New and Migration Guide
Nextra 4 just released, marking the largest update in its history, with a ton of exciting improvements. Here’s what’s new:
App Router Support
Nextra 4 exclusively uses Next.js App Router. Support for the Pages Router has been discontinued. In Nextra 4, there are two ways to render MDX files using file-based routing:
- Content directory convention: Using a catch-all route which loads MDX files from the specific directory.
- Page file convention: Follows
Next.js App Router convention
with enhanced page extensions:
page.{md,mdx}
.
Trade-offs
- The catch-all route route may lead to longer compilation times, depending on the number of MDX files.
- The page file convention works well with colocation, allowing you to keep all the assets for an article in one place.
Using Content Directory Convention
Migrate your Pages Router site with minimal changes using this mode. Follow these steps:
Rename your pages
folder to content
You can keep content
directory in root of your project, or in 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:
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:
- layout.jsx
- page.jsx
- index.mdx
- index.mdx
- getting-started.mdx
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
rely on extra dependencies like concurrently
and ws
. These approaches include
Dan Abramov workaround with <AutoRefresh>
component and dev web socket server.
Nextra’s content mode 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 works as well
Checkout Nextra’s docs website and i18n website example.
Using Page File Convention
The above file-based routing structure, following the Using Content Directory Convention, will appear as follows when using this mode:
- layout.jsx
- page.jsx
- page.mdx
- page.mdx
Warning
All pages page.{jsx,tsx}
should export
metadata
object.
Note
See Nextra’s website or Nextra’s blog example as an examples of this mode.
Turbopack Support
After being one of our most requested features for
over 2 years, Nextra 4 finally supports Turbopack,
the Rust-based incremental bundler. Enable it by adding --turbopack
to your dev command:
"scripts": {
- "dev": "next dev"
+ "dev": "next dev --turbopack"
}
Note
Without --turbopack
flag Next.js under the hood uses
Webpack, written in JavaScript.
Warning
At the time of writing this blog only JSON serializable values can be passed to nextra
function. You cannot pass custom remarkPlugins
, rehypePlugins
or recmaPlugins
since they are
functions.
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:
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.
Discontinuing theme.config
Support
Nextra 4 no longer support theme.config
files, which were previously used for configuring your
theme options, theme
and themeConfig
option were removed too:
const withNextra = nextra({
- theme: 'nextra-theme-docs',
- themeConfig: './theme.config.tsx'
})
Note
Previously theme config options now should be passed as props
for <Layout>
, <Navbar>
,
<Footer>
, <Search>
and <Banner>
components in app/layout.jsx
file.
New Search Engine – Pagefind
Search engine was migrated from FlexSearch written in JavaScript to Rust-powered Pagefind.
Benefits
Pagefind is significantly faster and delivers far superior search results compared to FlexSearch. Here are some examples that previously didn’t work in earlier versions of Nextra:
-
Indexing remote MDX.
page.mdximport { Callout } from 'nextra/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 <b>{stars}</b> } 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() } <Callout emoji="🏆"> {/* Stars count will be indexed 🎉 */} Nextra has <Stars /> stars on GitHub! {/* Last update time will be indexed 🎉 */} Last repository update _{await getUpdatedAt()}_. </Callout>
-
Indexing dynamic content written in Markdown/MDX.
page.mdx{/* Current year will be indexed 🎉 */} MIT {new Date().getFullYear()} © Nextra.
-
Indexing imported JavaScript or MDX files in MDX page.
../path/to/your/reused-js-component.jsexport function ReusedJsComponent() { return <strong>My content will be indexed</strong> }
../path/to/your/reused-mdx-component.mdx**My content will be indexed as well**
page.mdximport { ReusedJsComponent } from '../path/to/your/reused-js-component.js' import ReusedMdxComponent from '../path/to/your/reused-mdx-component.mdx' <ReusedJsComponent /> <ReusedMdxComponent />
-
Indexing static pages written in JavaScript or TypeScript.
For JavaScript/TypeScript pages you need to add
data-pagefind-body
attribute to the tag enclosing the main content area you want indexed.You can ignore indexing of specific tags by adding
data-pagefind-ignore
attribute.page.jsxexport default function Page() { return ( // All content of tags with `data-pagefind-body` attribute will be indexed <ul data-pagefind-body> <li>Nextra 4 is the best MDX Next.js library</li> {/* Except for tags with `data-pagefind-ignore` attribute */} <li data-pagefind-ignore>Nextra 3 is the best MDX Next.js library</li> </ul> ) }
💡Tip
For MDX pages while using
nextra-theme-docs
andnextra-theme-blog
you don’t need to adddata-pagefind-body
attribute.
Setup
New search engine requires few steps to setup:
Instal pagefind
as devDependency
npm i -D pagefind
Add postbuild
script
Pagefind indexes .html
pages and search indexing should be done after building your application:
"scripts": {
"dev": "next --turbopack",
"build": "next build",
"postbuild": "pagefind --site .next/server/app --output-path public/_pagefind",
// Or if you use Next.js' Static Exports
"postbuild": "pagefind --site .next/server/app --output-path out/_pagefind"
}
Enable pre
/post
scripts (optional)
Some package managers like pnpm@8
by default
don’t execute pre/post
scripts, you should
enable it via .npmrc
file via enable-pre-post-scripts
setting:
enable-pre-post-scripts=true
Note
pnpm@9
by default run pre
/post
scripts.
Add _pagefind/
directory to .gitignore
file
You don’t need to commit _pagefind/
directory to your git repository, he should be always newly
generated.
node_modules/
.next/
_pagefind/
Use <Search>
component in your custom theme
Search from nextra-theme-docs
was exported in nextra/components
. With this change
nextra-theme-blog
received search feature too. And you can benefit from it as well in your custom
themes.
import { Search } from 'nextra/components'
export function RootLayout({ children }) {
return (
<html>
<body>
<header>
<Search />
</header>
<main>{children}</main>
</body>
</html>
)
}
RSC I18n Support
Thanks to server components, we no longer need to ship to client translation dictionary files, e.g
./dictionaries/en.json
. We can dynamically load translations in server components and pass
according translation as props
to <Layout>
, <Banner>
, <Footer>
, etc. components in
app/[lang]/layout.jsx
.
Example
Below an example of server components i18n using nextra-theme-docs
, but the same approach should
be applied for your custom theme:
import { Footer, LastUpdated, Layout, Navbar } from 'nextra-theme-docs'
import { Banner, Head, Search } from 'nextra/components'
import { getPageMap } from 'nextra/page-map'
import { getDictionary, getDirection } from '../path/to/your/get-dictionary'
// Required for theme styles, previously was imported under the hood
import 'nextra-theme-docs/style.css'
export const metadata = {
// ... your metadata API
// https://nextjs.org/docs/app/building-your-application/optimizing/metadata
}
export default async function RootLayout({ children, params }) {
const { lang } = await params
const pageMap = await getPageMap(lang)
const direction = getDirection(lang)
const dictionary = await getDictionary(lang)
return (
<html
lang={lang}
// Required to be set
dir={direction}
// Suggested by `next-themes` package https://github.com/pacocoursey/next-themes#with-app
suppressHydrationWarning
>
<Head />
<body>
<Layout
banner={<Banner storageKey="some-key">{dictionary.banner}</Banner>}
docsRepositoryBase="https://github.com/shuding/nextra/blob/main/examples/swr-site"
editLink={dictionary.editPage}
feedback={{ content: dictionary.feedback }}
footer={<Footer>{dictionary.footer}</Footer>}
i18n={[
{ locale: 'en', name: 'English' },
{ locale: 'fr', name: 'Français' },
{ locale: 'ru', name: 'Русский' }
]}
lastUpdated={<LastUpdated>{dictionary.lastUpdated}</LastUpdated>}
navbar={<Navbar logo={<MyLogo />} />}
pageMap={pageMap}
search={
<Search
emptyResult={dictionary.searchEmptyResult}
errorText={dictionary.searchError}
loading={dictionary.searchLoading}
placeholder={dictionary.searchPlaceholder}
/>
}
themeSwitch={{
dark: dictionary.dark,
light: dictionary.light,
system: dictionary.system
}}
toc={{
backToTop: dictionary.backToTop,
title: dictionary.tocTitle
}}
>
{children}
</Layout>
</body>
</html>
)
}
where get-dictionary
file may looks like:
// Ensure this file is always called in server component
import 'server-only'
// Enumerate all dictionaries
const dictionaries = {
en: () => import('./en.json'),
fr: () => import('./fr.json'),
ru: () => import('./ru.json')
}
export async function getDictionary(locale) {
const { default: dictionary } = await (dictionaries[locale] || dictionaries.en)()
return dictionary
}
export function getDirection(locale) {
switch (locale) {
case 'he':
return 'rtl'
default:
return 'ltr'
}
}
Note
Read more about server components i18n in
Next.js docs.
See
working example of i18n website
in Nextra’s examples.
Enhanced by React Compiler
The source code for nextra
, nextra-theme-docs
and nextra-theme-blog
has been optimized using
the React Compiler. All Nextra’s components and hooks are
optimized under the hood by React Compiler and all internal usages of useCallback
, useMemo
and
memo
were removed.
GitHub Alert Syntax
nextra-theme-docs
and nextra-theme-blog
support replacing
GitHub alert syntax
with <Callout>
component for .md
/.mdx
files.
> [!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.
Will be rendered as:
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.
Bundle Size Difference with Nextra 3
Let’s compare how bundle size changed between Nextra 3 and Nextra 4. Similar to
Nextra 3 blogpost I am comparing
Docs Example which uses
nextra-theme-docs
and Blog Example
which uses nextra-theme-blog
.
Note
All 4 examples were updated to the latest Next.js 15.1.3 at the moment of writing this blogpost.
Docs Example
Theme Docs with Nextra 3
Route (pages) Size First Load JS
┌ ○ / 1.67 kB 164 kB
├ /_app 0 B 153 kB
├ ● /_meta 249 B 154 kB
├ ○ /404 188 B 154 kB
├ ● /advanced/_meta 256 B 154 kB
├ ○ /advanced/code-highlighting 1.92 kB 165 kB
├ ● /features/_meta 256 B 154 kB
├ ○ /features/i18n 2.41 kB 165 kB
├ ○ /features/image 2.64 kB 165 kB
├ ○ /features/latex 2.56 kB 165 kB
├ ○ /features/mdx 4.07 kB 167 kB
├ ● /features/ssg (ISR: 60 Seconds) (583 ms) 2.28 kB 165 kB
├ ○ /features/themes 1.35 kB 164 kB
├ ○ /get-started 3.38 kB 171 kB
├ ● /themes/_meta 254 B 154 kB
├ ○ /themes/blog 3.06 kB 166 kB
├ ● /themes/blog/_meta 258 B 154 kB
├ ○ /themes/docs 2.38 kB 170 kB
├ ● /themes/docs/_meta 258 B 154 kB
├ ○ /themes/docs/bleed 2.64 kB 165 kB
├ ○ /themes/docs/callout 2.42 kB 165 kB
├ ○ /themes/docs/configuration 6.17 kB 169 kB
└ ○ /themes/docs/tabs 5.97 kB 169 kB
+ First Load JS shared by all 168 kB
├ chunks/framework-6b181cec221b9dfa.js 44.8 kB
├ chunks/main-c456fe7452186752.js 38.5 kB
├ chunks/pages/_app-350c9924bcb3142d.js 68.3 kB
├ css/8c65e010692a2141.css 15.1 kB
└ other shared chunks (total) 1.71 kB
○ (Static) prerendered as static content
● (SSG) prerendered as static HTML (uses getStaticProps)
(ISR) incremental static regeneration (uses revalidate in getStaticProps)
Theme Docs with Nextra 4
Route (app) Size First Load JS
┌ ○ / 146 B 106 kB
├ ○ /_not-found 988 B 107 kB
├ ○ /apple-icon.png 0 B 0 B
├ ○ /blog 146 B 106 kB
├ ● /docs/[[...mdxPath]] 299 B 177 kB
├ ├ /docs/advanced/code-highlighting
├ ├ /docs/features/i18n
├ ├ /docs/features/image
├ └ [+14 more paths]
├ ○ /icon.png 0 B 0 B
└ ○ /opengraph-image.png 0 B 0 B
+ First Load JS shared by all 106 kB
├ chunks/5323-beeaeed450ee0972.js 50.7 kB
├ chunks/67a2b45f-b45ef6a709f04795.js 53 kB
└ other shared chunks (total) 2.63 kB
○ (Static) prerendered as static content
● (SSG) prerendered as static HTML (uses generateStaticParams)
First Load JS shared by all is decreased by 36.9% from 168 kB to 106 kB.
I18n Docs Example
I18n Theme Docs with Nextra 3
Route (pages) Size First Load JS
┌ /_app 0 B 158 kB
├ ○ /404 559 B 158 kB
├ ○ /en (600 ms) 8.8 kB 179 kB
├ ● /en/_meta 254 B 158 kB
├ ● /en/about/_meta 257 B 158 kB
├ ○ /en/about/a-page 748 B 171 kB
├ ○ /en/about/acknowledgement 747 B 171 kB
├ ○ /en/about/changelog 1.47 kB 171 kB
├ ○ /en/about/team 799 B 171 kB
├ ○ /en/blog 1.54 kB 171 kB
├ ● /en/blog/_meta 259 B 158 kB
├ ○ /en/blog/swr-v1 (631 ms) 6.28 kB 176 kB
├ ● /en/docs/_meta 258 B 158 kB
├ ○ /en/docs/404-500 (624 ms) 1.63 kB 172 kB
├ ○ /en/docs/advanced (629 ms) 1.13 kB 171 kB
├ ● /en/docs/advanced/_meta 264 B 158 kB
├ ○ /en/docs/advanced/cache (627 ms) 4.77 kB 184 kB
├ ○ /en/docs/advanced/code-highlighting (625 ms) 1.71 kB 172 kB
├ ● /en/docs/advanced/dynamic-markdown-import 2.42 kB 172 kB
├ ○ /en/docs/advanced/file-name.with.DOTS (626 ms) 777 B 171 kB
├ ○ /en/docs/advanced/images 1.27 kB 171 kB
├ ○ /en/docs/advanced/markdown-import 9.13 kB 179 kB
├ ○ /en/docs/advanced/more/loooooooooooooooooooong-title (624 ms) 878 B 171 kB
├ ● /en/docs/advanced/more/tree/one 750 B 171 kB
├ ○ /en/docs/advanced/more/tree/three 753 B 171 kB
├ ○ /en/docs/advanced/more/tree/two 733 B 171 kB
├ ○ /en/docs/advanced/performance 3.25 kB 173 kB
├ ○ /en/docs/advanced/react-native 3.32 kB 173 kB
├ ○ /en/docs/advanced/scrollbar-x 5.74 kB 176 kB
├ ○ /en/docs/arguments 2.66 kB 173 kB
├ ○ /en/docs/callout 1.42 kB 171 kB
├ ● /en/docs/change-log 1.1 kB 171 kB
├ ○ /en/docs/code-block-without-language 909 B 171 kB
├ ○ /en/docs/conditional-fetching 1.85 kB 172 kB
├ ○ /en/docs/custom-header-ids 1.29 kB 171 kB
├ ○ /en/docs/data-fetching 2.77 kB 173 kB
├ ○ /en/docs/error-handling 3.62 kB 174 kB
├ ○ /en/docs/getting-started (316 ms) 16 kB 186 kB
├ ○ /en/docs/global-configuration (605 ms) 2.17 kB 172 kB
├ ○ /en/docs/middleware (604 ms) 4.59 kB 175 kB
├ ○ /en/docs/mutation (612 ms) 3.67 kB 174 kB
├ ○ /en/docs/options (607 ms) 3.15 kB 173 kB
├ ○ /en/docs/pagination 6.23 kB 184 kB
├ ○ /en/docs/prefetching (604 ms) 2.33 kB 172 kB
├ ○ /en/docs/raw-layout (603 ms) 1.5 kB 171 kB
├ ○ /en/docs/revalidation (603 ms) 5.08 kB 175 kB
├ ○ /en/docs/suspense (603 ms) 2.85 kB 173 kB
├ ○ /en/docs/typescript 2.68 kB 173 kB
├ ○ /en/docs/understanding 3.6 kB 176 kB
├ ○ /en/docs/with-nextjs 3.04 kB 173 kB
├ ○ /en/docs/wrap-toc-items 1.61 kB 172 kB
├ ● /en/examples/_meta 259 B 158 kB
├ ○ /en/examples/auth 1.01 kB 171 kB
├ ○ /en/examples/basic 1.01 kB 171 kB
├ ○ /en/examples/error-handling 1.01 kB 171 kB
├ ○ /en/examples/full (604 ms) 1 kB 171 kB
├ ○ /en/examples/infinite-loading 1.03 kB 171 kB
├ ○ /en/examples/ssr 1.02 kB 171 kB
├ ○ /en/foo 5.07 kB 175 kB
├ ● /en/remote/graphql-eslint/_meta 272 B 158 kB
├ ● /en/remote/graphql-eslint/[[...slug]] (18773 ms) 5.21 kB 175 kB
├ ├ /en/remote/graphql-eslint/configs (4353 ms)
├ ├ /en/remote/graphql-eslint/custom-rules (4351 ms)
├ ├ /en/remote/graphql-eslint/getting-started (4351 ms)
├ ├ /en/remote/graphql-eslint/getting-started/parser-options (4349 ms)
├ ├ /en/remote/graphql-eslint/getting-started/parser (948 ms)
├ └ /en/remote/graphql-eslint/index (421 ms)
├ ● /en/remote/graphql-yoga/_meta 272 B 158 kB
├ ● /en/remote/graphql-yoga/[[...slug]] (61009 ms) 5.22 kB 175 kB
├ ├ /en/remote/graphql-yoga/features/error-masking (5851 ms)
├ ├ /en/remote/graphql-yoga/features/file-uploads (5846 ms)
├ ├ /en/remote/graphql-yoga/features/graphiql (5843 ms)
├ ├ /en/remote/graphql-yoga/features/subscriptions (5841 ms)
├ ├ /en/remote/graphql-yoga/features/testing (5840 ms)
├ ├ /en/remote/graphql-yoga/index (5840 ms)
├ ├ /en/remote/graphql-yoga/integrations/integration-with-aws-lambda (5840 ms)
├ └ [+16 more paths] (avg 1257 ms)
├ ○ /en/test 1.01 kB 171 kB
├ ○ /es (545 ms) 5.53 kB 173 kB
├ ● /es/_meta 253 B 158 kB
├ ● /es/docs/_meta 257 B 158 kB
├ ● /es/docs/advanced/_meta 265 B 158 kB
├ ○ /es/docs/advanced/file-name.with.DOTS 1.71 kB 169 kB
├ ○ /es/docs/advanced/performance (545 ms) 4.32 kB 171 kB
├ ○ /es/docs/arguments (523 ms) 3.63 kB 171 kB
├ ● /es/docs/change-log (973 ms) 7.95 kB 175 kB
├ ○ /es/docs/conditional-fetching (521 ms) 2.8 kB 170 kB
├ ○ /es/docs/data-fetching (522 ms) 3.67 kB 171 kB
├ ○ /es/docs/error-handling (521 ms) 4.63 kB 172 kB
├ ○ /es/docs/getting-started (521 ms) 10 kB 177 kB
├ ○ /es/docs/global-configuration 3.12 kB 170 kB
├ ○ /es/docs/mutation 4.66 kB 172 kB
├ ○ /es/docs/options 4.14 kB 171 kB
├ ○ /es/docs/pagination 7.5 kB 182 kB
├ ○ /es/docs/prefetching 3.29 kB 170 kB
├ ○ /es/docs/revalidation 6.13 kB 173 kB
├ ○ /es/docs/suspense (546 ms) 3.79 kB 171 kB
├ ○ /es/docs/understanding (552 ms) 4.48 kB 174 kB
├ ○ /es/docs/with-nextjs 4.02 kB 171 kB
├ ○ /es/docs/wrap-toc-items 2.54 kB 170 kB
├ ● /es/examples/_meta 259 B 158 kB
├ ○ /es/examples/auth 1.91 kB 169 kB
├ ○ /es/examples/basic (549 ms) 1.91 kB 169 kB
├ ○ /es/examples/error-handling 1.92 kB 169 kB
├ ○ /es/examples/infinite-loading 1.93 kB 169 kB
├ ○ /ru 6.66 kB 174 kB
├ ● /ru/_meta 254 B 158 kB
├ ○ /ru/blog (545 ms) 3.3 kB 170 kB
├ ● /ru/blog/_meta 258 B 158 kB
├ ○ /ru/blog/swr-v1 (546 ms) 9.38 kB 177 kB
├ ● /ru/docs/_meta 259 B 158 kB
├ ● /ru/docs/advanced/_meta 264 B 158 kB
├ ○ /ru/docs/advanced/cache 7.72 kB 184 kB
├ ○ /ru/docs/advanced/file-name.with.DOTS 2.65 kB 170 kB
├ ○ /ru/docs/advanced/performance (546 ms) 5.71 kB 173 kB
├ ○ /ru/docs/advanced/react-native (546 ms) 5.54 kB 173 kB
├ ○ /ru/docs/arguments 4.7 kB 172 kB
├ ● /ru/docs/change-log (972 ms) 8.95 kB 176 kB
├ ○ /ru/docs/conditional-fetching 3.88 kB 171 kB
├ ○ /ru/docs/data-fetching 4.75 kB 172 kB
├ ○ /ru/docs/error-handling 5.99 kB 173 kB
├ ○ /ru/docs/getting-started 11.2 kB 178 kB
├ ○ /ru/docs/global-configuration 4.24 kB 171 kB
├ ○ /ru/docs/middleware 7.14 kB 174 kB
├ ○ /ru/docs/mutation 6.18 kB 173 kB
├ ○ /ru/docs/options 5.55 kB 173 kB
├ ○ /ru/docs/pagination 9.21 kB 184 kB
├ ○ /ru/docs/prefetching 4.48 kB 172 kB
├ ○ /ru/docs/revalidation 7.53 kB 175 kB
├ ○ /ru/docs/suspense (501 ms) 4.97 kB 172 kB
├ ○ /ru/docs/understanding (503 ms) 5.44 kB 175 kB
├ ○ /ru/docs/with-nextjs (506 ms) 5.22 kB 172 kB
├ ○ /ru/docs/wrap-toc-items (501 ms) 3.49 kB 171 kB
├ ● /ru/examples/_meta 260 B 158 kB
├ ○ /ru/examples/auth (501 ms) 2.84 kB 170 kB
├ ○ /ru/examples/basic (504 ms) 2.85 kB 170 kB
├ ○ /ru/examples/error-handling (502 ms) 2.85 kB 170 kB
├ ○ /ru/examples/infinite-loading 2.86 kB 170 kB
└ ○ /ru/examples/ssr (501 ms) 2.86 kB 170 kB
+ First Load JS shared by all 173 kB
├ chunks/framework-dc66eabaa62619da.js 44.8 kB
├ chunks/main-6304e7d3c1f52717.js 39.2 kB
├ chunks/pages/_app-0f8d74dd6fdf7d5b.js 71.9 kB
├ css/aa443d1a875a7ede.css 15.6 kB
└ other shared chunks (total) 1.77 kB
ƒ Middleware 40.3 kB
○ (Static) prerendered as static content
● (SSG) prerendered as static HTML (uses getStaticProps)
I18n Theme Docs with Nextra 4
Route (app) Size First Load JS
┌ ○ /_not-found 979 B 107 kB
├ ● /[lang]/[[...mdxPath]] 3.79 kB 184 kB
├ ├ /en/about/a-page
├ ├ /en/about/acknowledgement
├ ├ /en/about/changelog
├ └ [+99 more paths]
├ ● /[lang]/remote/graphql-eslint/[[...slug]] 144 B 184 kB
├ ● /[lang]/remote/graphql-yoga/[[...slug]] 144 B 184 kB
├ ○ /apple-icon.png 0 B 0 B
├ ○ /icon.svg 0 B 0 B
└ ○ /manifest.webmanifest 0 B 0 B
+ First Load JS shared by all 106 kB
├ chunks/323-fcca95ec98243d99.js 50.8 kB
├ chunks/67a2b45f-3be7f33ae655c29d.js 52.9 kB
└ other shared chunks (total) 2 kB
ƒ Middleware 40.4 kB
○ (Static) prerendered as static content
● (SSG) prerendered as static HTML (uses generateStaticParams)
First Load JS shared by all is decreased by 38.7% from 173 kB to 106 kB.
Blog Example
Theme Blog with Nextra 3
Route (pages) Size First Load JS
┌ ○ / 1.61 kB 115 kB
├ /_app 0 B 104 kB
├ ○ /404 188 B 104 kB
├ ○ /posts 1.08 kB 115 kB
├ ○ /posts/aaron-swartz-a-programmable-web 3.9 kB 118 kB
├ ○ /posts/callout 9.77 kB 123 kB
├ ○ /posts/code-blocks 2.16 kB 116 kB
├ ○ /posts/draft 1.28 kB 115 kB
├ ○ /posts/lists 1.22 kB 115 kB
├ ○ /posts/table 1.36 kB 115 kB
└ ● /tags/[tag] (324 ms) 1.33 kB 115 kB
├ /tags/web development
├ /tags/JavaScript
├ /tags/GraphQL
└ [+6 more paths]
+ First Load JS shared by all 114 kB
├ chunks/framework-6b181cec221b9dfa.js 44.8 kB
├ chunks/main-c456fe7452186752.js 38.5 kB
├ chunks/pages/_app-04c2827288399efd.js 19.5 kB
└ other shared chunks (total) 10.9 kB
○ (Static) prerendered as static content
● (SSG) prerendered as static HTML (uses getStaticProps)
Theme Blog with Nextra 4
Route (app) Size First Load JS
┌ ○ / 578 B 130 kB
├ ○ /_not-found 979 B 106 kB
├ ○ /icon.svg 0 B 0 B
├ ○ /posts 144 B 140 kB
├ ○ /posts/aaron-swartz-a-programmable-web 231 B 130 kB
├ ○ /posts/code-blocks 231 B 130 kB
├ ○ /posts/draft 231 B 130 kB
├ ○ /posts/lists 231 B 130 kB
├ ○ /posts/nextra-components 2.79 kB 137 kB
├ ○ /posts/table 231 B 130 kB
└ ● /tags/[tag] 144 B 140 kB
├ /tags/web development
├ /tags/JavaScript
├ /tags/GraphQL
└ [+6 more paths]
+ First Load JS shared by all 105 kB
├ chunks/323-87b56278accb196b.js 50.5 kB
├ chunks/67a2b45f-6901f39824410d2a.js 52.9 kB
└ other shared chunks (total) 1.91 kB
○ (Static) prerendered as static content
● (SSG) prerendered as static HTML (uses generateStaticParams)
First Load JS shared by all is decreased by 7.9% from 114 kB to 105 kB.
Conclusion
First Load JS is significantly decreased in all above examples.
Remote Docs
Remote docs configuration has changed, please refers to
official example,
Additionally you will need to modify pageMap
list in layout
file, to properly display sidebar
navigation links. Example of modified layout
file is
here.
nextra-theme-docs
Changes
Zustand
All previous React context usages were migrated to zustand
except places where we need
dependency injection. This applies only to the
useConfig
and useThemeConfig
hooks which needs
initialize their state with props.
Migrated to Tailwind CSS 4
Theme Docs has been updated to Tailwind CSS 4. The
previously used Tailwind CSS prefix _
which
helped prevent class name conflicts, has been replaced with x:
. If you have overridden Nextra’s
theme classes, make sure to update them accordingly.
- ._text-primary-600 { ... }
+ .x\:text-primary-600 { ... }
New Headings :target
State Animation
All headings now have animation for :target
state
Enhanced <NotFoundPage>
The built-in <NotFoundPage>
now includes a URL for creating a issue with the referrer
URL
included. This makes it easier to identify the broken page that led the user to the 404 error.
Migration Guide
Choose MDX rendering mode
Add layout.jsx
file
Example of app/layout.jsx
for Nextra documentation theme:
import { Footer, Layout, Navbar } from 'nextra-theme-docs'
import { Banner, Head } from 'nextra/components'
import { getPageMap } from 'nextra/page-map'
// Required for theme styles, previously was imported under the hood
import 'nextra-theme-docs/style.css'
export const metadata = {
// ... your metadata API
// https://nextjs.org/docs/app/building-your-application/optimizing/metadata
}
const banner = <Banner storageKey="some-key">Nextra 4.0 is released 🎉</Banner>
const navbar = <Navbar logo={<b>Nextra</b>} projectLink="https://github.com/shuding/nextra" />
const footer = (
<Footer className="flex-col items-center md:items-start">
MIT {new Date().getFullYear()} © Nextra.
</Footer>
)
export default async function RootLayout({ children }) {
return (
<html
// Not required, but good for SEO
lang="en"
// Required to be set
dir="ltr"
// Suggested by `next-themes` package https://github.com/pacocoursey/next-themes#with-app
suppressHydrationWarning
>
<Head
backgroundColor={{
dark: 'rgb(15, 23, 42)',
light: 'rgb(254, 252, 232)'
}}
color={{
hue: { dark: 120, light: 0 },
saturation: { dark: 100, light: 100 }
}}
>
{/* Your additional tags should be passed as `children` of `<Head>` element */}
</Head>
<body>
<Layout
banner={banner}
navbar={navbar}
pageMap={await getPageMap()}
docsRepositoryBase="https://github.com/shuding/nextra/tree/main/docs"
editLink="Edit this page on GitHub"
sidebar={{ defaultMenuCollapseLevel: 1 }}
footer={footer}
// ...Your additional theme config options
>
{children}
</Layout>
</body>
</html>
)
}
Add an mdx-components.jsx
file
Create an mdx-components.jsx
file in the root of your project to define global MDX Components:
import { useMDXComponents as getDocsMDXComponents } from 'nextra-theme-docs'
const docsComponents = getDocsMDXComponents()
export function useMDXComponents(components) {
return {
...docsComponents,
...components
// ... your additional components
}
}
Migrate theme.config
options
Table of migration theme.config
options
Nextra 3 | Nextra 4 |
---|---|
banner.content | children prop in <Banner> 1 |
banner.dismissible | dismissible prop in <Banner> |
banner.key | storageKey prop in <Banner> |
backgroundColor.dark | backgroundColor.dark prop in <Head> 2 |
backgroundColor.light | backgroundColor.light prop in <Head> |
chat.icon | chatIcon prop in <Navbar> 3 |
chat.link | chatLink prop in <Navbar> |
components | Removed. Provide custom components inside useMDXComponents function |
darkMode | darkMode prop in <Layout> 4 |
direction | Removed. Use dir attribute on <html> tag |
docsRepositoryBase | docsRepositoryBase prop in <Layout> |
editLink.component | editLink prop in <Layout> |
editLink.content | children prop in <LastUpdated> 5 |
faviconGlyph | faviconGlyph prop in <Head> |
feedback.content | feedback.content prop in <Layout> |
feedback.labels | feedback.labels prop in <Layout> |
feedback.useLink | Removed |
footer.component | footer prop in <Layout> |
footer.content | children prop in <Footer> 6 |
gitTimestamp | lastUpdated prop in <Layout> |
head | Removed. Use <Head> or Next.js Metadata API |
i18n[number].direction | Removed. Use dir attribute on <html> tag |
i18n[number].locale | i18n[number].locale prop in <Layout> |
i18n[number].name | i18n[number].name prop in <Layout> |
logo | logo prop in <Navbar> |
logoLink | logoLink prop in <Navbar> |
main | Removed |
navbar.component | navbar prop in <Layout> |
navbar.extraContent | children prop in <Layout> |
navigation | navigation prop in <Layout> |
nextThemes | nextThemes prop in <Layout> |
notFound.content | content prop in <NotFoundPage> 7 |
notFound.labels | labels prop in <NotFoundPage> |
color.hue | color.hue prop in <Head> |
color.saturation | color.saturation prop in <Head> |
project.icon | projectIcon prop in <Navbar> |
project.link | projectLink prop in <Navbar> |
search.component | search prop in <Layout> |
search.emptyResult | emptyResult prop in <Search> 8 |
search.error | errorText prop in <Search> |
search.loading | loading prop in <Search> |
search.placeholder | placeholder prop in <Search> |
sidebar.autoCollapse | sidebar.autoCollapse prop in <Layout> |
sidebar.defaultMenuCollapseLevel | sidebar.defaultMenuCollapseLevel prop in <Layout> |
sidebar.toggleButton | sidebar.toggleButton prop in <Layout> |
themeSwitch.component | Removed |
themeSwitch.useOptions | themeSwitch prop in <Layout> |
toc.backToTop | toc.backToTop prop in <Layout> |
toc.component | Removed |
toc.extraContent | toc.extraContent prop in <Layout> |
toc.float | toc.float prop in <Layout> |
toc.title | toc.title prop in <Layout> |
Dynamic <head>
Tags
Dynamic head tags previously were configured via head
theme config option. In Nextra 4 you should
use Next.js
Metadata API instead.
Nextra 3:
export default {
head() {
const config = useConfig()
const { route } = useRouter()
const title = config.title + (route === '/' ? '' : ' | Nextra')
return (
<>
<title>{title}</title>
<meta property="og:title" content={title} />
</>
)
}
}
Nextra 4:
export const metadata = {
title: {
default: 'Nextra – Next.js Static Site Generator',
template: '%s | Nextra'
},
openGraph: {
url: 'https://nextra.site',
siteName: 'Nextra',
locale: 'en_US',
type: 'website'
}
}
---
description: Make beautiful websites with Next.js & MDX.
---
# Hello Nextra 4
Front matter title
field or first Markdown heading <h1>
will set <title>
and
<meta property="og:title">
and front matter description
field will set
<meta name="description">
and <meta property="og:description">
in <head>
element.
<head>
<title>Hello Nextra 4 | Nextra</title>
<meta property="og:title" content="Hello Nextra 4 | Nextra" />
<meta name="description" content="Make beautiful websites with Next.js & MDX." />
<meta property="og:description" content="Make beautiful websites with Next.js & MDX." />
</head>
nextra-theme-blog
Changes
Support for _meta
Files
The blog theme now includes partial support for _meta
files, allowing you to define navbar links
directly within an _meta
file. The previously used draft: true
option in the front matter has
been replaced with
display: 'hidden'
in an _meta
file.
react-cusdis
Was Removed
Previously optional peer dependency react-cusdis
was removed, since:
- this library is no longer maintained
- has only React 17 specified in
peerDependencies
We use Cusdis SDK directly in <Comments>
9 component.
next-view-transitions
Was Added
Blog received support of
View Transitions API by
using next-view-transitions
package.
Use Built-In Nextra <Search>
nextra-theme-blog
v4 is the first version where you can use built-in search. To do it, import
<Search>
component from nextra/components
and place it as children of <Navbar>
component, then
follow search setup steps.
<Navbar pageMap={await getPageMap()}>
<Search />
<ThemeSwitch />
</Navbar>
Migration Guide
Choose MDX rendering mode
Migrate theme.config
options
Add layout.jsx
file
Example of app/layout.jsx
for Nextra blog theme:
import { Footer, Layout, Navbar, ThemeSwitch } from 'nextra-theme-blog'
import { Banner, Head, Search } from 'nextra/components'
import { getPageMap } from 'nextra/page-map'
// Required for theme styles, previously was imported under the hood
import 'nextra-theme-blog/style.css'
export const metadata = {
// ... your metadata API
// https://nextjs.org/docs/app/building-your-application/optimizing/metadata
}
const banner = <Banner storageKey="some-key">Nextra 4.0 is released 🎉</Banner>
export default async function RootLayout({ children }) {
return (
<html
// Not required, but good for SEO
lang="en"
// Suggested by `next-themes` package https://github.com/pacocoursey/next-themes#with-app
suppressHydrationWarning
>
<Head backgroundColor={{ dark: '#0f172a', light: '#fefce8' }} />
<body>
<Layout banner={banner}>
<Navbar pageMap={await getPageMap()}>
<Search />
<ThemeSwitch />
</Navbar>
{children}
<Footer>{new Date().getFullYear()} © Dimitri Postolov.</Footer>
</Layout>
</body>
</html>
)
}
Add an mdx-components.jsx
file
Create an mdx-components.jsx
file in the root of your project to define global MDX Components:
import { useMDXComponents as getBlogMDXComponents } from 'nextra-theme-blog'
const blogComponents = getBlogMDXComponents()
export function useMDXComponents(components) {
return {
...blogComponents,
...components
// ... your additional components
}
}
Table of migration theme.config
options
Nextra 3 | Nextra 4 |
---|---|
comments | Provide your comments component in post layout, e.g. app/posts/(with-comments)/layout.jsx file |
components | Provide custom components inside useMDXComponents function |
darkMode | To disable theme toggle remove <ThemeSwitch> from <Navbar> |
dateFormatter | Provide DateFormatter component inside useMDXComponents function |
footer | Provide your <Footer> component as last child of <Layout> |
head | Use <Head> or Next.js Metadata API |
navs | Setup your navbar links via _meta files |
postFooter | Provide your post footer in post layout, e.g. app/posts/(with-comments)/layout.jsx file |
readMore | Provide readMore prop for <PostCard> component |
titleSuffix | Use Next.js Metadata API |
New banner prop on <Layout> . E.g. you can now provide Nextra’s <Banner> 1 | |
New nextThemes prop on <Layout> . Configuration options can be found in next-themes docs |
Various Changes
_meta
Files Changes
- Your
_meta
files should be server component files, without'use client'
directive. - zod now parses and transforms
_meta
files on server, to improve DX and avoid typos. - The
_meta
’snewWindow
field was removed. - All external links declared in
_meta
files now opens in a new tab with withrel="noreferrer"
attribute and have visual↗
suffix icon. theme.topContent
andtheme.bottomContent
fields were removed.theme.layout: 'raw'
option has been removed. To create pages without a layout, usepage.{jsx,tsx}
files instead.
_meta.global
File
You can now 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.
Example
For the following structure, you might use the following _meta
files:
- layout.jsx
- _meta.js
- page.jsx
- page.mdx
- _meta.js
- page.mdx
export default {
docs: {
type: 'page',
title: 'Documentation'
}
}
export default {
items: {
index: 'Getting Started'
}
}
With signle _meta.global
file it can be defined as below:
export default {
docs: {
type: 'page',
title: 'Documentation',
items: {
index: 'Getting Started'
}
}
}
Warning
You can’t use both _meta.global
and _meta
files in your project.
Component Migration: Moving UI Elements
Several components were migrated from nextra-theme-docs
to nextra/components
to make them
accessible for use in your custom themes:
<Collapse>
<Details>
<Summary>
<SkipNavContent>
<SkipNavLink>
<Select>
<Bleed>
<Head>
Tip
With migration <Head>
component to nextra/components
, both nextra-theme-blog
and your custom
themes can now configure:
- primary color,
color
prop - background color,
backgroundColor
prop - glyph to use as the favicon,
faviconGlyph
prop - color of selection based on primary color
Previously all of these options were exclusive to nextra-theme-docs
only.
All built-in Nextra components were refactored to be either server or client.
Table of comparison components from nextra/components
Here’s the table sorted alphabetically by component name:
Component | Type |
---|---|
<Banner> | server |
<Bleed> | server |
<Button> | client |
<Callout> | server |
<Cards> /<Cards.Card> | server |
<Collapse> | client |
<FileTree> /<FileTree.File> | server |
<FileTree.Folder> | client |
<Head> | server |
<Playground> | client |
<Popup> /<Popup.Button> /<Popup.Panel> | client |
<Search> | client |
<Select> | client |
<SkipNavContent> | server |
<SkipNavLink> | client |
<Tabs> /<Tabs.Tab> | client |
<Steps> | server |
<MDXRemote> (previous <RemoteContent> ) | server |
Front Matter = Metadata
All fields from the front matter are now exported as a metadata
object in MDX file. If you prefer
not to use front matter, you can directly export a metadata
from your MDX file, but you can’t use
both in same file.
---
title: Foo
description: Bar
---
{/* Will be compiled to 👇 */}
export const metadata = {
title: 'Foo',
description: 'Bar'
}
export const metadata = {
title: 'Foo',
description: 'Bar'
}
---
title: Foo
---
export const metadata = { description: 'Bar' }
whiteListTagsStyling
Option
Nextra introduces a new option called whiteListTagsStyling
, which allows you to whitelist HTML
elements to be replaced with components defined in the
mdx-components.js
file. By default, Nextra only replaces <details>
and
<summary>
. With this option, you can extend the behavior to replace any HTML element.
import nextra from 'nextra'
const withNextra = nextra({
whiteListTagsStyling: ['h1']
})
# Hello
{/* This will be replaced as well */}
<h1>World</h1>
Folders with Index Pages Changes
In Nextra 2 and 3, the structure shown below is referred to as
folders with index pages
. Notice the presence of both a docs
folder and a docs.mdx
file at the same level:
- getting-started.mdx
- docs.mdx
This structure is incompatible with the page file convention, to fix it a
new front matter option called asIndexPage
has been introduced. You should set asIndexPage: true
in the front matter of docs/page.mdx
to achieve the same effect.
- layout.jsx
- page.mdx
- page.mdx
When using the content directory convention, you can achieve the
same result by setting asIndexPage: true
in the front matter of docs/index.mdx
- index.mdx
- getting-started.mdx
List Subpages
It’s now possible to automatically list all subpages of a given route as cards.
- Import
createIndexPage
andgetPageMap
fromnextra/page-map
. - Use
MDXRemote
fromnextra/mdx-remote
to render list of subpages - Replace
'/my-route'
with the route whose subpages you want to display - To show card’s icon, use
icon
front matter field in your subpages (optional) - Provide
Cards
and your icons toMDXRemote
’s components prop
import { Cards } from 'nextra/components'
import { MDXRemote } from 'nextra/mdx-remote'
import { createIndexPage, getPageMap } from 'nextra/page-map'
import { MyIcon } from '../path/to/your/icons'
<MDXRemote
compiledSource={
await createIndexPage(
await getPageMap('/my-route')
)
}
components={{
Cards,
MyIcon
}}
/>
---
icon: MyIcon
---
# My Subpage
Sidebar Title Priority Changes
In Nextra 4, the sidebar title is now determined by the following order:
- A non-empty title from the
_meta
file. sidebarTitle
in the front matter.title
in the front matter.- New in Nextra 4: The title derived from the first
h1
Markdown heading (e.g.# Dima Machina
). - If none of the above are available, it falls back to the filename of the page, formatted according to The Chicago Manual of Style.
Code Block Icons Changes
Customizing Icons
You can now customize icons for code blocks. Here’s an example for nextra-theme-docs
, but the same
approach applies to custom themes:
- Import
withIcons
HOC function fromnextra/components
. - Wrap the
Pre
component withwithIcons
, passing your custom icon for the desired language.
import { useMDXComponents as useDocsMDXComponents } from 'nextra-theme-docs'
import { Pre, withIcons } from 'nextra/components'
const docsComponents = getDocsMDXComponents({
pre: withIcons(Pre, { js: MyCustomIcon })
})
export function useMDXComponents(components) {
return {
...docsComponents,
...components
// ... your additional components
}
}
Icons Updates
- JSX icons: The
jsx
andtsx
languages now display the React icon for better visual identification. - Diff icons: For code blocks with the
diff
language, if afilename
attribute is provided, the icon will automatically correspond to the file extension of the specified filename.
Markdown Links Changes
All external Markdown links declared in MDX files now opens in a new tab with with
rel="noreferrer"
attribute and have visual ↗
suffix icon. (Was inspired from Next.js docs)
Example:
[dimaMachina](https://github.com/dimaMachina)
Will compiles to:
<a href="https://github.com/dimaMachina" target="_blank" rel="noreferrer">
dimaMachina 
<LinkArrowIcon height="16" className="_inline _align-baseline" />
</a>
::selection
Styles
Variant of primary color which is set via color
prop on <Head>
component is now used for
::selection
styles.
<Table>
Changes
The <Th>
, <Tr>
and <Td>
components have been removed and are now attached directly into
<Table>
component. This change improves the DX, as these components are typically used together
within a <Table>
.
- import { Table, Th, Tr, Td } from 'nextra/components'
+ import { Table } from 'nextra/components'
<Table>
<thead>
- <Tr>
+ <Table.Tr>
- <Th>Items</Th>
+ <Table.Th>Items</Table.Th>
- </Tr>
+ </Table.Tr>
</thead>
<tbody>
- <Tr>
+ <Table.Tr>
- <Th>Donuts</Th>
+ <Table.Th>Donuts</Table.Th>
- </Tr>
+ </Table.Tr>
</tbody>
<tfoot>
- <Tr>
+ <Table.Tr>
- <Th>Totals</Th>
+ <Table.Th>Totals</Table.Th>
- </Tr>
+ </Table.Tr>
</tfoot>
</Table>
compileMdx
Changes
compileMdx
function now returns a Promise<string>
instead of a Promise<object>
.
import { compileMdx } from 'nextra/compile'
const rawMdx = '# Hello Nextra 4'
- const { result: rawJs } = await compileMdx(rawMdx)
+ const rawJs = await compileMdx(rawMdx)
<RemoteContent>
Changes
- The
<RemoteContent>
component has been renamed to<MDXRemote>
. - He has been removed from
nextra/components
and moved tonextra/mdx-remote
- You no longer need to manually pass your default components to
<MDXRemote>
. They will now be automatically provided from yourmdx-components.jsx
file.
Optimized Imports
Imports from nextra/components
, nextra-theme-docs
and nextra-theme-blog
are now optimized
internally with Next.js’
optimizePackageImports
option resulting in improved bundle size.
useRouter
Removed
Nextra’s useRouter
hook was removed, use Next.js’ useRouter
from next/navigation
instead now.
- import { useRouter } from 'nextra/hooks'
+ import { useRouter } from 'next/navigation'
Bump Minimal Next.js to V14
Nextra 4 requires at least Next.js 14 to be installed in your project.
Update Your tsconfig.json
If you are using TypeScript and have moduleResolution
set to node
, you will receive type errors:
Type error: Cannot find module 'nextra/components' or its corresponding type declarations.
"typesVersions"
fields from package.json
’s of Nextra packages were removed. You need to set
"moduleResolution": "bundler"
in your tsconfig.json
.
{
"compilerOptions": {
- "moduleResolution": "node"
+ "moduleResolution": "bundler"
}
}
Conclusion. Why Prefer Nextra 4 over Others?
Nextra 4 introduces several key features that enhance its functionality and performance:
-
App Router Support: Nextra 4 uses Next.js App Router, discontinuing support for the Pages Router. This change aligns with the latest Next.js API such as Metadata API.
-
Turbopack Support: Improved development experience, addressing a long-standing community request.
-
New Search Engine: New Rust-powered search engine Pagefind provides faster performance and significantly better search results.
-
RSC I18n Support: Enhanced internationalization by supporting React Server Components (RSC) with built-in i18n capabilities, facilitating the creation of multilingual websites.
-
Enhanced by React Compiler: Compiling official Nextra packages source code with the React Compiler in leads to optimized performance, resulting in a better user experience.
-
Reduced Bundle Size: Through various optimizations, the smallest bundle size ever for a Nextra-powered website was achieved, enhancing load times and overall performance.
-
Improved Pages Collection: Structure of pages called “Page Map” was improved and now collects not only static
md
/mdx
pages, but any staticjsx
/tsx
pages fromapp/
directory as well. -
Powerful Table of Content: The improved Table of Content (TOC) from Nextra 3 shows exact same content from your Markdown headings, including interpolated content, inlined code-blocks, JSX elements, React components and even rendered math formulas LaTeX.
-
Remote MDX Rendering: Remote rendering of MDX content added in Nextra 3 allows you to render your content from any source, and rendering proper sidebar navigation links.
These features make Nextra 4 a more powerful and efficient framework for building content-focused websites.
Next Steps
Nextra 4 is a major release with a lot of breaking changes. I already created a roadmap for next
major release Nextra 5 optional using Rust-powered
MDX mdxjs-rs
parser. Share your thoughts as well.
Credits
A sincere thank for the most active Nextra contributor, I can even name him as a perfect example of a great Open Source contributor Oscar Xie! His amazing contributions previously pushed me a lot to release Nextra 3!
Huge thanks for Nextra sponsors:
The Nextra 4 documentation is still a work in progress, and we need your help to improve it!
Check out the recent updates in this blog post, and feel free to submit your contribution PRs to improve Nextra docs.
Spread the word about Nextra on 𝕏 using the #Nextra
hashtag. You can also follow me on
𝕏 and GitHub to stay updated!
Footnotes
-
import { Head } from 'nextra/components'
↩ -
import { Navbar } from 'nextra-theme-docs'
↩ -
import { Layout } from 'nextra-theme-docs'
↩ -
import { LastUpdated } from 'nextra-theme-docs'
↩ -
import { Footer } from 'nextra-theme-docs'
↩ -
import { NotFoundPage } from 'nextra-theme-docs'
↩ -
import { Search } from 'nextra/components'
↩ -
import { Comments } from 'nextra-theme-blog'
↩
Join our newsletter
Want to hear from us when there's something new?
Sign up and stay up to date!
*By subscribing, you agree with Beehiiv’s Terms of Service and Privacy Policy.