Nextra 3 – Your Favourite MDX Framework, Now on 🧪 Steroids
Intro
Hi everyone 👋, I am Dimitri POSTOLOV, you may know me previously as B2o5T (boost).
I recently decided to change my nickname to easy-to-remember Dima Machina (for a long time that’s what my team The Guild called me).
Once upon a time… I discovered Nextra and wanted to use it as a base framework for The Guild documentation websites, I found a lot of issues and started to contribute to this library to fix them and to help with the v2 release.

Nextra 3 is currently available as an alpha release, and I am looking for feedback from the community, choose your favourite package manager and try it:
npm i nextra@alpha # Install Nextra
npm i nextra-theme-docs@alpha # Install Nextra Theme Docs
npm i nextra-theme-blog@alpha # Install Nextra Theme BlogWhat’s New
Starting from releasing v2 in which release I actively contributed, I actively maintained Nextra and received feedback from the community and ideas for future improvements.
So let’s begin what I prepared with a new major release 🎉.
MDX 3
Nextra 3 will be built on top of MDX 3. There are no significant breaking changes (as it was between MDX 1 and MDX 2). More info about what’s new in MDX 3 can be found in the official blog post.
Complete New I18n with Static Exports Support
I18n was rewritten from scratch. The previous i18n was hard to scalable especially when you have a lot of languages, but also had really blocking issues, especially:
-
only the default locale was produced in sitemap #712
-
index files were not working for subfolders #1263
-
build output directory for each locale contained all pages locales #1922
-
some static files were inaccessible #1354
The original proposal of better i18n by locale folders instead of locale suffix was proposed 2 years ago and in Nextra 3 it was implemented by me.
Comparison of I18n Project Structure for Nextra 2/3
| Nextra 2 | Nextra 3 |
|---|---|
|
|
You should know also a few notes:
- By default, Next.js doesn’t support project structure like above with the
i18nproperty enabled in yournext.config
If without Nextra you’ll try to create this project structure, and you try to access
http://localhost:3000/en or http://localhost:3000/en/getting-started you’ll get a 404 error. To fix
it Nextra set the i18n property in next.config to undefined. With i18n enabled and with
Nextra you’ll see the following console warning:
- warn [nextra] Next.js doesn't support i18n by locale folder names.
When i18n enabled, Nextra unset nextConfig.i18n to `undefined`, use `useRouter` from `nextra/hooks`
if you need `locale` or `defaultLocale` values.-
useRouterfromnext/routerreturns incorrectlocaleanddefaultLocalevalues (sincei18nwas unset), so instead you should useuseRouterfromnextra/hookswhich will return correct values. -
Popular i18n libraries like
react-intlandreact-i18nextor others will not work either.
Keep in mind that, you may not need the above i18n libraries since maintaining docs and keeping them updated for multiple languages is really hard, and you may just use some platform like Crowdin that will translate docs for you once the default language is changed. Also Crowdin is free for Open Source 😍.
On the other hand, since Nextra under the hood disables i18n in next.config your i18n docs
website could have static exports!
New _meta.{js,jsx,ts,tsx} Files with JSX Support
After one year of
creating my proposal of using _meta.js instead of _meta.json,
I finally found a way of implementing the trickiest feature of Nextra 3 and _meta.json files are
no longer supported.
This means that:
- you can use either
_meta.jsor_meta.jsxwith JSX support or_meta.{ts,tsx}for TypeScript projects instead - you can use ESLint’s built-in rule
sort-keyswith/* eslint sort-keys: error */comment to sort your sidebar items alphabetically - you can export and import everything that you want in your
_metafiles - you can have typesafe
_meta.{ts,tsx}files while asserting type definition from thenextrapackage - you can add a banner/footer for your repeated content in
_metafiles, for individual pages, or for all nested pages in*key
In the case of using next-sitemap, you probably
need to add exclude: ['*/_meta'] in your next-sitemap.config.js since it’s tricky to exclude
_meta files from the build.
More Powerful TOC
There is no MDX framework where TOC can be better than in Nextra 3 (Correct me if I am wrong 🙂).
If you wanna this feature to be part of your MDX framework or Open Source, ping me and I’ll try my best to publish it as my separate Open Source package.
Previously TOC could show only static content from your headings, and it showed unexpected results for dynamic content #2411 #1955 #2076 #1884.
With Nextra 3 all issues are fixed, and everything that is inserted in your main content is properly rendered in TOC too 😍.
Better Bundle Size
A lot of work was done to make Nextra’s bundle size smaller, I deep-dived into the generated _app
file and took a look what is included and what can be removed.
Let’s do some calculations! 🧮
Note: all 4 examples for v2 and v3 were updated on the latest Next.js 13.5.6 at the moment of writing this blog post.
Build Output of Blog Example Website
with Nextra 2
https://github.com/shuding/nextra/tree/66798f8e7f92cca80f2d62d19f9db5667bcc62ef/examples/blog
Route (pages) Size First Load JS
┌ ○ / 1.08 kB 119 kB
├ /_app 0 B 114 kB
├ ○ /404 180 B 115 kB
├ ○ /posts 588 B 119 kB
├ ○ /posts/aaron-swartz-a-programmable-web 3.53 kB 122 kB
├ ○ /posts/callout 1.98 kB 120 kB
├ ○ /posts/code-blocks 1.53 kB 120 kB
├ ○ /posts/draft 795 B 119 kB
├ ○ /posts/table 844 B 119 kB
└ ● /tags/[tag] (716 ms) 788 B 119 kB
├ /tags/web development
├ /tags/JavaScript
├ /tags/GraphQL
└ [+6 more paths]
+ First Load JS shared by all 123 kB
├ chunks/framework-bce0fd2bcc8d4c85.js 45.3 kB
├ chunks/main-48077aa82984b023.js 37.6 kB
├ chunks/pages/_app-83deac736929a1bc.js 29.6 kB
├ chunks/webpack-109eb0aced926db3.js 1.81 kB
└ css/e187e49945a86f29.css 8.61 kB
○ (Static) automatically rendered as static HTML (uses no initial props)
● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)with Nextra 3
https://github.com/shuding/nextra/tree/c2ad837d3dacd87e1aa3ea3b9839f18062ae1c25/examples/blog
Route (pages) Size First Load JS
┌ ○ / 1.61 kB 108 kB
├ ○ /404 180 B 101 kB
├ ○ /posts 1.12 kB 107 kB
├ ○ /posts/aaron-swartz-a-programmable-web 3.93 kB 110 kB
├ ○ /posts/callout 11.2 kB 117 kB
├ ○ /posts/code-blocks 2.02 kB 108 kB
├ ○ /posts/draft 1.28 kB 107 kB
├ ○ /posts/table 1.35 kB 107 kB
└ ● /tags/[tag] (872 ms) 1.34 kB 107 kB
├ /tags/web development
├ /tags/JavaScript
├ /tags/GraphQL
└ [+6 more paths]
+ First Load JS shared by all 101 kB
├ chunks/framework-bce0fd2bcc8d4c85.js 45.3 kB
├ chunks/main-c12ab09220a28f5f.js 37.8 kB
├ chunks/pages/_app-f6bbbcf4ee8f890e.js 16.2 kB
└ chunks/webpack-d4712bf1c9b5b172.js 1.81 kB
○ (Static) automatically rendered as static HTML (uses no initial props)
● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)Let’s compare everything starting from the First Load JS shared by all lines:
-+ First Load JS shared by all 123 kB
++ First Load JS shared by all 101 kB
├ chunks/framework-bce0fd2bcc8d4c85.js 45.3 kB
- ├ chunks/main-48077aa82984b023.js 37.6 kB
+ ├ chunks/main-c12ab09220a28f5f.js 37.8 kB
- ├ chunks/pages/_app-83deac736929a1bc.js 29.6 kB
+ ├ chunks/pages/_app-f6bbbcf4ee8f890e.js 16.2 kB
├ chunks/webpack-109eb0aced926db3.js 1.81 kB
- └ css/e187e49945a86f29.css 8.61 kBWe can assume that the first load is decreased by 17.89% and _app file is decreased by
45.27% for nextra-theme-blog 🤯.
Note: the .css file is not included in the bundle report for Nextra 3, because the v3 blog
example just doesn’t contain a custom _app file, and importing CSS from node_modules is not
possible for pages router, so instead .css file is imported in each MDX page (in Nextra’s
webpack loader).
Build Output of I18n Docs Website with LaTeX Enabled
with Nextra 2
https://github.com/shuding/nextra/tree/66798f8e7f92cca80f2d62d19f9db5667bcc62ef/examples/swr-site
Route (pages) Size First Load JS
┌ /_app 0 B 187 kB
├ ○ /404 560 B 191 kB
├ ○ /500 555 B 191 kB
├ ○ /about/a-page.en-US 527 B 191 kB
├ ○ /about/acknowledgement.en-US 526 B 191 kB
├ ○ /about/changelog.en-US 1.22 kB 192 kB
├ ○ /about/team.en-US 574 B 191 kB
├ ○ /blog.en-US 1.5 kB 192 kB
├ ○ /blog.ru 1.5 kB 192 kB
├ ○ /blog/swr-v1.en-US 6.32 kB 197 kB
├ ○ /blog/swr-v1.ru 8.1 kB 199 kB
├ ○ /docs/404-500.en-US 1.44 kB 192 kB
├ ○ /docs/advanced.en-US 909 B 192 kB
├ ○ /docs/advanced/cache.en-US 4.46 kB 204 kB
├ ○ /docs/advanced/cache.ru 5.74 kB 205 kB
├ ○ /docs/advanced/code-highlighting.en-US 1.39 kB 192 kB
├ ○ /docs/advanced/dynamic-markdown-import.en-US 2.92 kB 194 kB
├ ○ /docs/advanced/file-name.with.DOTS.en-US 557 B 191 kB
├ ○ /docs/advanced/file-name.with.DOTS.es-ES 568 B 191 kB
├ ○ /docs/advanced/file-name.with.DOTS.ru 621 B 191 kB
├ ○ /docs/advanced/images.en-US 892 B 192 kB
├ ○ /docs/advanced/markdown-import.en-US 4.69 kB 195 kB
├ ○ /docs/advanced/more/loooooooooooooooooooong-title.en-US 637 B 191 kB
├ ● /docs/advanced/more/tree/one.en-US 588 B 191 kB
├ ○ /docs/advanced/more/tree/three.en-US 532 B 191 kB
├ ● /docs/advanced/more/tree/two.en-US 587 B 191 kB
├ ○ /docs/advanced/performance.en-US 2.97 kB 194 kB
├ ○ /docs/advanced/performance.es-ES 3.15 kB 194 kB
├ ○ /docs/advanced/performance.ru 3.79 kB 195 kB
├ ○ /docs/advanced/react-native.en-US 2.51 kB 193 kB
├ ○ /docs/advanced/react-native.ru 3.08 kB 194 kB
├ ○ /docs/advanced/scrollbar-x.en-US 1.72 kB 192 kB
├ ○ /docs/arguments.en-US 1.9 kB 193 kB
├ ○ /docs/arguments.es-ES 1.98 kB 193 kB
├ ○ /docs/arguments.ru 2.23 kB 193 kB
├ ○ /docs/callout.en-US 693 B 191 kB
├ ● /docs/change-log.en-US 898 B 192 kB
├ ● /docs/change-log.es-ES 928 B 197 kB
├ ● /docs/change-log.ru 1.04 kB 197 kB
├ ○ /docs/code-block-without-language.en-US 718 B 191 kB
├ ○ /docs/conditional-fetching.en-US 1.56 kB 192 kB
├ ○ /docs/conditional-fetching.es-ES 1.62 kB 192 kB
├ ○ /docs/conditional-fetching.ru 1.87 kB 193 kB
├ ○ /docs/custom-header-ids.en-US 1.02 kB 192 kB
├ ○ /docs/data-fetching.en-US 1.97 kB 193 kB
├ ○ /docs/data-fetching.es-ES 2.02 kB 193 kB
├ ○ /docs/data-fetching.ru 2.33 kB 193 kB
├ ○ /docs/error-handling.en-US 2.81 kB 194 kB
├ ○ /docs/error-handling.es-ES 2.96 kB 194 kB
├ ○ /docs/error-handling.ru 3.55 kB 194 kB
├ ○ /docs/getting-started.en-US 8.96 kB 200 kB
├ ○ /docs/getting-started.es-ES 7.88 kB 199 kB
├ ○ /docs/getting-started.ru 8.58 kB 199 kB
├ ○ /docs/global-configuration.en-US 1.85 kB 193 kB
├ ○ /docs/global-configuration.es-ES 1.9 kB 193 kB
├ ○ /docs/global-configuration.ru 2.22 kB 193 kB
├ ○ /docs/middleware.en-US 3.9 kB 195 kB
├ ○ /docs/middleware.ru 4.75 kB 196 kB
├ ○ /docs/mutation.en-US 3.41 kB 194 kB
├ ○ /docs/mutation.es-ES 3.48 kB 194 kB
├ ○ /docs/mutation.ru 4.21 kB 195 kB
├ ○ /docs/options.en-US 2.42 kB 193 kB
├ ○ /docs/options.es-ES 2.57 kB 193 kB
├ ○ /docs/options.ru 3.21 kB 194 kB
├ ○ /docs/pagination.en-US 6.02 kB 204 kB
├ ○ /docs/pagination.es-ES 6.42 kB 204 kB
├ ○ /docs/pagination.ru 7.38 kB 205 kB
├ ○ /docs/prefetching.en-US 2.05 kB 193 kB
├ ○ /docs/prefetching.es-ES 2.14 kB 193 kB
├ ○ /docs/prefetching.ru 2.56 kB 193 kB
├ ○ /docs/raw-layout.en-US 1.24 kB 192 kB
├ ○ /docs/revalidation.en-US 2.76 kB 198 kB
├ ○ /docs/revalidation.es-ES 2.9 kB 198 kB
├ ○ /docs/revalidation.ru 3.5 kB 199 kB
├ ○ /docs/suspense.en-US 2.08 kB 193 kB
├ ○ /docs/suspense.es-ES 2.15 kB 193 kB
├ ○ /docs/suspense.ru 2.53 kB 193 kB
├ ○ /docs/typescript.en-US 2.37 kB 193 kB
├ ○ /docs/understanding.en-US 3.88 kB 199 kB
├ ○ /docs/understanding.es-ES 3.88 kB 199 kB
├ ○ /docs/understanding.ru 3.88 kB 199 kB
├ ○ /docs/with-nextjs.en-US 2.25 kB 193 kB
├ ○ /docs/with-nextjs.es-ES 2.39 kB 193 kB
├ ○ /docs/with-nextjs.ru 2.82 kB 194 kB
├ ○ /docs/wrap-toc-items.en-US 1.39 kB 192 kB
├ ○ /docs/wrap-toc-items.es-ES 1.39 kB 192 kB
├ ○ /docs/wrap-toc-items.ru 1.39 kB 192 kB
├ ○ /examples/auth.en-US 830 B 192 kB
├ ○ /examples/auth.es-ES 834 B 192 kB
├ ○ /examples/auth.ru 865 B 192 kB
├ ○ /examples/basic.en-US 832 B 192 kB
├ ○ /examples/basic.es-ES 834 B 192 kB
├ ○ /examples/basic.ru 875 B 192 kB
├ ○ /examples/error-handling.en-US 839 B 192 kB
├ ○ /examples/error-handling.es-ES 842 B 192 kB
├ ○ /examples/error-handling.ru 875 B 192 kB
├ ○ /examples/full.en-US 815 B 192 kB
├ ○ /examples/infinite-loading.en-US 851 B 192 kB
├ ○ /examples/infinite-loading.es-ES 853 B 192 kB
├ ○ /examples/infinite-loading.ru 896 B 192 kB
├ ○ /examples/ssr.en-US 840 B 192 kB
├ ○ /examples/ssr.ru 838 B 192 kB
├ ○ /foo.en-US 954 B 192 kB
├ ○ /index.en-US 4.34 kB 195 kB
├ ○ /index.es-ES 3.99 kB 195 kB
├ ○ /index.ru 4.38 kB 195 kB
├ ○ /remote/_meta 225 B 187 kB
├ ○ /remote/graphql-eslint/_meta 237 B 187 kB
├ ○ /remote/graphql-yoga/_meta 236 B 187 kB
└ ○ /test.en-US 786 B 192 kB
+ First Load JS shared by all 203 kB
├ chunks/framework-733009b6474116fd.js 45.3 kB
├ chunks/main-1dbce7e00d8751d5.js 38.4 kB
├ chunks/pages/_app-f3b27ac2556aba2f.js 101 kB
├ chunks/webpack-e7e126cbd02a5d11.js 1.95 kB
└ css/86d65edbc5de703f.css 16.1 kB
ƒ Middleware 26.8 kB
○ (Static) automatically rendered as static HTML (uses no initial props)
● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)with Nextra 3
https://github.com/shuding/nextra/tree/c2ad837d3dacd87e1aa3ea3b9839f18062ae1c25/examples/swr-site
Route (pages) Size First Load JS
┌ /_app 0 B 148 kB
├ ○ /404 183 B 149 kB
├ ○ /en 8.34 kB 164 kB
├ ● /en/_meta 247 B 149 kB
├ ● /en/about/_meta 251 B 149 kB
├ ○ /en/about/a-page 472 B 156 kB
├ ○ /en/about/acknowledgement 471 B 156 kB
├ ○ /en/about/changelog 1.17 kB 157 kB
├ ○ /en/about/team 518 B 156 kB
├ ○ /en/blog 1.22 kB 157 kB
├ ● /en/blog/_meta 250 B 149 kB
├ ○ /en/blog/swr-v1 6.18 kB 162 kB
├ ● /en/docs/_meta 250 B 149 kB
├ ○ /en/docs/404-500 1.28 kB 157 kB
├ ○ /en/docs/advanced 855 B 157 kB
├ ● /en/docs/advanced/_meta 258 B 149 kB
├ ○ /en/docs/advanced/cache 4.46 kB 170 kB
├ ○ /en/docs/advanced/code-highlighting 1.38 kB 157 kB
├ ● /en/docs/advanced/dynamic-markdown-import 2.89 kB 159 kB
├ ○ /en/docs/advanced/file-name.with.DOTS 502 B 156 kB
├ ○ /en/docs/advanced/images 829 B 157 kB
├ ○ /en/docs/advanced/markdown-import 8.66 kB 165 kB
├ ○ /en/docs/advanced/more/loooooooooooooooooooong-title 591 B 157 kB
├ ● /en/docs/advanced/more/tree/one 507 B 156 kB
├ ○ /en/docs/advanced/more/tree/three 475 B 156 kB
├ ○ /en/docs/advanced/more/tree/two 492 B 156 kB
├ ○ /en/docs/advanced/performance 2.95 kB 159 kB
├ ○ /en/docs/advanced/react-native 3.02 kB 159 kB
├ ○ /en/docs/advanced/scrollbar-x 5.33 kB 161 kB
├ ○ /en/docs/arguments 2.35 kB 158 kB
├ ○ /en/docs/callout 1.13 kB 157 kB
├ ● /en/docs/change-log 810 B 157 kB
├ ○ /en/docs/code-block-without-language 604 B 157 kB
├ ○ /en/docs/conditional-fetching 1.54 kB 157 kB
├ ○ /en/docs/custom-header-ids 987 B 157 kB
├ ○ /en/docs/data-fetching 2.45 kB 158 kB
├ ○ /en/docs/error-handling 3.3 kB 159 kB
├ ○ /en/docs/getting-started 14.4 kB 170 kB
├ ○ /en/docs/global-configuration 1.86 kB 158 kB
├ ○ /en/docs/middleware 4.28 kB 160 kB
├ ○ /en/docs/mutation 3.36 kB 159 kB
├ ○ /en/docs/options 2.86 kB 159 kB
├ ○ /en/docs/pagination 5.9 kB 170 kB
├ ○ /en/docs/prefetching 2.02 kB 158 kB
├ ○ /en/docs/raw-layout 1.2 kB 157 kB
├ ○ /en/docs/revalidation 2.71 kB 164 kB
├ ○ /en/docs/suspense 2.52 kB 158 kB
├ ○ /en/docs/typescript 2.36 kB 158 kB
├ ○ /en/docs/understanding 3.85 kB 165 kB
├ ○ /en/docs/with-nextjs 2.71 kB 159 kB
├ ○ /en/docs/wrap-toc-items 1.31 kB 157 kB
├ ● /en/examples/_meta 254 B 149 kB
├ ○ /en/examples/auth 738 B 157 kB
├ ○ /en/examples/basic 738 B 157 kB
├ ○ /en/examples/error-handling 747 B 157 kB
├ ○ /en/examples/full 729 B 157 kB
├ ○ /en/examples/infinite-loading 753 B 157 kB
├ ○ /en/examples/ssr 754 B 157 kB
├ ○ /en/foo 4.68 kB 161 kB
├ ● /en/remote/graphql-eslint/_meta 264 B 149 kB
├ ● /en/remote/graphql-eslint/[[...slug]] (1853 ms) 4.86 kB 161 kB
├ ├ /en/remote/graphql-eslint/custom-rules (464 ms)
├ ├ /en/remote/graphql-eslint/getting-started (373 ms)
├ ├ /en/remote/graphql-eslint/getting-started/parser-options (345 ms)
├ ├ /en/remote/graphql-eslint/configs
├ ├ /en/remote/graphql-eslint/getting-started/parser
├ └ /en/remote/graphql-eslint/index
├ ● /en/remote/graphql-yoga/_meta 263 B 149 kB
├ ● /en/remote/graphql-yoga/[[...slug]] (32819 ms) 4.88 kB 161 kB
├ ├ /en/remote/graphql-yoga/features/subscriptions (2996 ms)
├ ├ /en/remote/graphql-yoga/features/context (2885 ms)
├ ├ /en/remote/graphql-yoga/features/file-uploads (2777 ms)
├ ├ /en/remote/graphql-yoga/features/apollo-federation (2738 ms)
├ ├ /en/remote/graphql-yoga/features/graphiql (2724 ms)
├ ├ /en/remote/graphql-yoga/features/cors (2674 ms)
├ ├ /en/remote/graphql-yoga/features/testing (2560 ms)
├ └ [+16 more paths] (avg 842 ms)
├ ○ /en/test 699 B 157 kB
├ ○ /es 5.56 kB 159 kB
├ ● /es/_meta 247 B 149 kB
├ ● /es/docs/_meta 251 B 149 kB
├ ● /es/docs/advanced/_meta 256 B 149 kB
├ ○ /es/docs/advanced/file-name.with.DOTS 1.78 kB 155 kB
├ ○ /es/docs/advanced/performance 4.39 kB 157 kB
├ ○ /es/docs/arguments 3.68 kB 157 kB
├ ● /es/docs/change-log (1096 ms) 2.13 kB 161 kB
├ ○ /es/docs/conditional-fetching 2.86 kB 156 kB
├ ○ /es/docs/data-fetching 3.73 kB 157 kB
├ ○ /es/docs/error-handling 4.69 kB 158 kB
├ ○ /es/docs/getting-started 9.69 kB 163 kB
├ ○ /es/docs/global-configuration 3.18 kB 156 kB
├ ○ /es/docs/mutation 4.72 kB 158 kB
├ ○ /es/docs/options 4.21 kB 157 kB
├ ○ /es/docs/pagination 7.57 kB 168 kB
├ ○ /es/docs/prefetching 3.34 kB 156 kB
├ ○ /es/docs/revalidation 4.09 kB 162 kB
├ ○ /es/docs/suspense 3.85 kB 157 kB
├ ○ /es/docs/understanding 5.1 kB 163 kB
├ ○ /es/docs/with-nextjs 4.07 kB 157 kB
├ ○ /es/docs/wrap-toc-items 2.6 kB 156 kB
├ ● /es/examples/_meta 253 B 149 kB
├ ○ /es/examples/auth 1.99 kB 155 kB
├ ○ /es/examples/basic 2 kB 155 kB
├ ○ /es/examples/error-handling 2 kB 155 kB
├ ○ /es/examples/infinite-loading 2 kB 155 kB
├ ○ /ru 6.18 kB 159 kB
├ ● /ru/_meta 248 B 149 kB
├ ○ /ru/blog 2.85 kB 156 kB
├ ● /ru/blog/_meta 251 B 149 kB
├ ○ /ru/blog/swr-v1 9.12 kB 162 kB
├ ● /ru/docs/_meta 250 B 149 kB
├ ● /ru/docs/advanced/_meta 258 B 149 kB
├ ○ /ru/docs/advanced/cache 7.15 kB 169 kB
├ ○ /ru/docs/advanced/file-name.with.DOTS 2.15 kB 155 kB
├ ○ /ru/docs/advanced/performance 5.2 kB 158 kB
├ ○ /ru/docs/advanced/react-native 5.05 kB 158 kB
├ ○ /ru/docs/arguments 4.21 kB 157 kB
├ ● /ru/docs/change-log (1101 ms) 2.56 kB 161 kB
├ ○ /ru/docs/conditional-fetching 3.36 kB 156 kB
├ ○ /ru/docs/data-fetching 4.26 kB 157 kB
├ ○ /ru/docs/error-handling 5.49 kB 159 kB
├ ○ /ru/docs/getting-started 10.6 kB 164 kB
├ ○ /ru/docs/global-configuration 3.73 kB 157 kB
├ ○ /ru/docs/middleware 6.59 kB 160 kB
├ ○ /ru/docs/mutation 5.65 kB 159 kB
├ ○ /ru/docs/options 5.05 kB 158 kB
├ ○ /ru/docs/pagination 8.66 kB 169 kB
├ ○ /ru/docs/prefetching 3.97 kB 157 kB
├ ○ /ru/docs/revalidation 4.91 kB 163 kB
├ ○ /ru/docs/suspense 4.47 kB 158 kB
├ ○ /ru/docs/understanding 5.5 kB 164 kB
├ ○ /ru/docs/with-nextjs 4.73 kB 158 kB
├ ○ /ru/docs/wrap-toc-items 2.98 kB 156 kB
├ ● /ru/examples/_meta 253 B 149 kB
├ ○ /ru/examples/auth 2.37 kB 155 kB
├ ○ /ru/examples/basic 2.37 kB 155 kB
├ ○ /ru/examples/error-handling 2.38 kB 155 kB
├ ○ /ru/examples/infinite-loading 2.38 kB 155 kB
└ ○ /ru/examples/ssr 2.38 kB 155 kB
+ First Load JS shared by all 163 kB
├ chunks/framework-733009b6474116fd.js 45.3 kB
├ chunks/main-3f7e5ffcd26ef392.js 37.9 kB
├ chunks/pages/_app-163bf496e5a12010.js 63.4 kB
├ chunks/webpack-958fb7687230bd78.js 1.89 kB
└ css/a30520c2298a663c.css 14.6 kB
○ (Static) automatically rendered as static HTML (uses no initial props)
● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)Let’s compare everything starting from the First Load JS shared by all lines:
-+ First Load JS shared by all 203 kB
++ First Load JS shared by all 163 kB
├ chunks/framework-733009b6474116fd.js 45.3 kB
- ├ chunks/main-1dbce7e00d8751d5.js 38.4 kB
+ ├ chunks/main-3f7e5ffcd26ef392.js 37.9 kB
- ├ chunks/pages/_app-f3b27ac2556aba2f.js 101 kB
+ ├ chunks/pages/_app-163bf496e5a12010.js 63.4 kB
- ├ chunks/webpack-e7e126cbd02a5d11.js 1.95 kB
+ ├ chunks/webpack-958fb7687230bd78.js 1.89 kB
- └ css/86d65edbc5de703f.css 16.1 kB
+ └ css/a30520c2298a663c.css 14.6 kBWe can assume that the first load is decreased by 19.7% and the _app file is decreased by
37.23% for nextra-theme-docs 🤯.
Remote Docs Support
Nextra 2 already had undocumented remote docs support, and now remote docs are officially supported. You can do it with a few lines of code 🤯. To render your remote docs, you’ll need 2 exports:
-
buildDynamicMDXandbuildDynamicMetafromnextra/remotebuildDynamicMDX- compile your MDX data into an evaluable functionbuildDynamicMeta- create a page map for your catch-all[[...slug]].mdxroute
-
RemoteContentfromnextra/components- React component which evaluates your compiled remote MDX.⚠️All imports/exports expressions are stripped by the
buildDynamicMDXfunction since in remote MDX it’s impossible to use them, imported components in your remote MDX should be passed instead inRemoteContent’scomponentsprop.
An Example of Remote Docs Fetched from GitHub
Where for current example:
user:"dimaMachina"repo:"graphql-eslint"branch:"master"docsPath:"website/src/pages/docs/"filePaths: An array of available docs file paths of your remote doc, for example:
[
"configs.mdx",
"custom-rules.mdx",
"getting-started.mdx",
"getting-started/parser-options.mdx",
"getting-started/parser.mdx",
"index.mdx"
]import { Callout, RemoteContent } from 'nextra/components'
import { buildDynamicMDX, buildDynamicMeta } from 'nextra/remote'
import { branch, docsPath, filePaths, repo, user } from '@somewhere/graphq-eslint-data'
export async function getStaticProps({ params }) {
const path = params.slug?.join('/') ?? 'index'
const foundPath = filePaths.find(filePath => filePath.replace(/\.mdx?/, '') === path)
const url = `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${docsPath}${foundPath}`
const response = await fetch(url)
const data = await response.text()
const { __nextra_pageMap } = await buildDynamicMeta()
const dynamicMdx = await buildDynamicMDX(data, { defaultShowCopyCode: true })
return {
props: {
__nextra_pageMap,
...dynamicMdx
}
}
}
export const getStaticPaths = () => ({
fallback: 'blocking',
paths: filePaths.map(filePath => ({
params: { slug: filePath.replace(/\.mdx?$/, '').split('/') }
}))
})
<RemoteContent components={{ Callout }} />An Example of Remote Docs’ _meta.js That Will Fill Sidebar Items
import { createCatchAllMeta } from 'nextra/catch-all'
// filePaths: An array of available docs file paths of your remote doc
import { filePaths } from '@somewhere/graphq-eslint-data'
// Note: for remote MDX `_meta.js` should return a function instead of an object
export default () => createCatchAllMeta(filePaths)Click to see the final result.
_app.mdx Is Removed
_app.mdx was an undocumented file, a custom workaround that was added after the v2 release to
avoid OOM (Out of Memory) problems for projects with a lot of MDX pages, he worked pretty well and
showed incredible results in improving bundle size
#1448 and
#1463.
But he confused people since no type definition can be used inside him, and no variable declarations
in your MDX too (only export const ... or export let ...).
With the v3 release, I’m happy to announce that your bundle size is improved with the regular
_app.{js,jsx,ts,tsx} file or even without 🤯!
A good thing to know is that if you don’t use custom _app/_document/_error in your project,
Next.js anyway will use some default
ones 🤓.
MathJax Support
In addition to KaTeX, Nextra 3 can render LaTeX expressions with MathJax to dynamically render math in the browser.
MathJax rendering is enabled by setting renderer: 'mathjax' in your Nextra config:
const withNextra = nextra({
latex: {
renderer: 'mathjax'
}
})With KaTeX, math is pre-rendered which means flicker-free and faster page loads. However, KaTeX does not support all of the features of MathJax, especially features related to accessibility.
Because of MathJax’s accessibility features, the LaTeX formula is tab-accessible and has a context menu that helps screen readers reprocess math for the visually impaired.
Huge thanks to siefkenj who created the initial PR and updated docs for the MathJax section ❤️
Hello ESM, Goodbye CJS
Nextra 2 was a mix of CJS/ESM bundles, Nextra 3 now is built as an ESM-only package, and the CJS
next.config.js file is no longer supported.
To use Nextra 3 you should use an ESM next.config.mjs or add "type": "module" in your
package.json file and use an ESM next.config.js with js extension.
Bumping Minimal Node.js to 18
Node.js 18 is now the minimum supported version since Node.js 16 is EOL and no longer supported 👋.
NextraConfig In next.config.js Now Validated by Zod
To improve the development experience and avoid mistakes when users pass NextConfig option in
NextraConfig nextra config is now validated by Zod.
New Code Blocks for nextra-theme-docs and nextra-theme-blog
Code block styles were inspired by the Next.js docs website, also now they contain icons for some program languages.
Replacement shiki by shikiji
Recently Anthony Fu released
shikiji an ESM-focused rewrite of shiki.
The downside of
shikiis also that withoutcss-variablestheme, your dual themes are always rendered twice and an unneeded theme should be hidden with CSS, which I found very bad 😮💨.
With Nextra 3 shikiji is used instead of shiki. github-light and github-dark themes are used
by default instead of the previously custom css-variables theme.
Migration Guide to Nextra 3
nextra
Ensure to use Node.js >= 18
Node.js 16 is EOL and no longer supported.
Ensure to use React.js >= 18
Ensure to use Next.js >= 13
CJS next.config.js is no longer supported
Use ESM next.config.mjs or add "type": "module" in your package.json file and use ESM
next.config.js.
import nextra from 'nextra'
const withNextra = nextra({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.jsx'
// ... your Nextra config
})
export default withNextra({
// ... your Next.js config
})_app.mdx is no longer supported
Use _app.{js,jsx} or _app.{ts,tsx} for TypeScript projects instead.
_meta.json is no longer supported
Use _meta.{js,jsx,ts,tsx} instead.
nextra/ssg export was removed
useSSG was renamed to useData and moved to nextra/hooks, RemoteContent was moved to
nextra/components.
- import { useSSG, RemoteContent } from 'nextra/data'
+ import { useData } from 'nextra/hooks'
+ import { RemoteContent } from 'nextra/components'pageOpts.headings was removed
To retrieve TOC now you can only via toc prop in the
wrapper component which defines the layout (but a
local layout takes precedence).
import { MDXProvider } from 'nextra/mdx'
import { MySidebar, MyTOC } from '@somewhere/my-components'
export default function MyTheme({ children, pageOpts, themeConfig }) {
return (
<>
<MySidebar />
- <MDXProvider>{children}</MDXProvider>
- <MyTOC toc={pageOpts.headings} />
+ <MDXProvider components={{ wrapper: MyWrapper }}>{children}</MDXProvider>
</>
)
}
+function MyWrapper({ children, toc }) {
+ return (
+ <>
+ {children}
+ <MyTOC toc={toc} />
+ </>
+ )
+}nextra/filter-route-locale and nextra/locales exports were removed
With implementing new i18n there is no need to use
filterRouteLocale helper function and locales middleware that previously redirected to a correct
route based on locale suffix.
Tab, Card exports from nextra/components were removed
Use Tabs.Tab and Cards.Card instead.
-import { Tabs, Cards, Card, Tab } from 'nextra/components'
+import { Tabs, Cards } from 'nextra/components'
<Tabs items={[]}>
- <Tab>...</Tab>
+ <Tabs.Tab>...</Tabs.Tab>
</Tabs>
<Cards>
- <Card title="..." href="..." />
+ <Cards.Card title="..." href="..." />
</Cards>Import md/mdx files that are outside the working directory no longer supported
Use symlinks instead now.
nextraConfig.flexsearch was renamed to nextraConfig.search
$$ syntax no longer works for LaTeX
remark-math was upgraded
to v6 where the support of $$ syntax
was removed. You probably should receive the following error:
TypeError: Cannot read properties of undefined (reading 'mathFlowInside')To fix it math code block language should be used instead of $$:
- $$
+ ```math
x^2
- $$
+ ```nextra-theme-docs
Ensure to use Next.js >= 13
Steps, Callout, Tabs, Cards and FileTree exports were removed
Import them now from nextra/components instead.
-import { Steps, Callout, Tabs, Cards, FileTree } from 'nextra-theme-docs'
+import { Steps, Callout, Tabs, Cards, FileTree } from 'nextra/components'Tailwind CSS classes prefixes now have _ prefix instead of nx-
If you have to select these classes in your custom CSS, consider updating them to new ones.
useConfig hook was split into two hooks useConfig / useThemeConfig
-import { useConfig } from 'nextra-theme-docs'
+import { useThemeConfig } from 'nextra-theme-docs'
function MyComponent() {
// Get options from `theme.config` file
- const { banner, sidebar, toc, ... } = useConfig()
+ const { banner, sidebar, toc, ... } = useThemeConfig()
}Renames of several theme.config options
-
primaryHue→color.hue -
primarySaturation→color.saturation -
i18n.text→i18n.name -
banner.text→banner.content -
editLink.text→editLink.content -
footer.text→footer.content
Removes of several theme.config options
-
serverSideError -
useNextSeoProps(Setup your<head />tags viaheadoption now) -
toc.headingComponent -
sidebar.titleComponent(Use JSX elements in your_metafile instead, see below)
import { MySeparator } from '@somewhere/my-separator'
export default {
// ...
'-- Machina': {
title: <MySeparator>Machina</MySeparator>,
type: 'separator'
}
}<MatchSorterSearch /> was removed
git-url-parse and next-seo were removed
This was made to make the bundle smaller.
sidebar.toggleButton is set to true by default
toc.backToTop should use a JSX element or a React component
To disable it, you can set toc.backToTop to null.
nextra-theme-blog
Ensure to use Next.js >= 13
cusdis option in the theme.config file was removed
You need to pass cusdis options as props in Cusdis component.
Conclusion
I’m very proud and happy to lead Nextra into the future. So many things have improved and so many more improvements are coming in the future!
If you want to help Nextra – spread the word about Nextra in 𝕏 with #nextra hashtag and subscribe
on me in 𝕏 and GitHub.
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.