the-guild / blog
all posts
~/the-guild/blog

Nextra 3 โ€“ Your Favourite MDX Framework, Now on ๐Ÿงช Steroids

Dimitri POSTOLOV #nextra#nextjs#react

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.

My first PR in Nextra

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 Blog

Whatโ€™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:

  1. only the default locale was produced in sitemap #712

  2. index files were not working for subfolders #1263

  3. build output directory for each locale contained all pages locales #1922

  4. 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

pages/
โ”œโ”€โ”€ index.en.mdx
โ”œโ”€โ”€ index.fr.mdx
โ”œโ”€โ”€ index.ru.mdx
โ”œโ”€โ”€ getting-started.en.mdx
โ”œโ”€โ”€ getting-started.fr.mdx
โ”œโ”€โ”€ getting-started.ru.mdx
โ”œโ”€โ”€ _meta.en.json
โ”œโ”€โ”€ _meta.fr.json
โ””โ”€โ”€ _meta.ru.json
pages/
โ”œโ”€โ”€ en/
โ”‚   โ”œโ”€โ”€ index.mdx
โ”‚   โ”œโ”€โ”€ getting-started.mdx
โ”‚   โ””โ”€โ”€ _meta.js
โ”œโ”€โ”€ fr/
โ”‚   โ”œโ”€โ”€ index.mdx
โ”‚   โ”œโ”€โ”€ getting-started.mdx
โ”‚   โ””โ”€โ”€ _meta.js
โ””โ”€โ”€ ru/
    โ”œโ”€โ”€ index.mdx
    โ”œโ”€โ”€ getting-started.mdx
    โ””โ”€โ”€ _meta.js

You should know also a few notes:

  1. By default, Next.js doesnโ€™t support project structure like above with the i18n property enabled in your next.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.
  1. useRouter from next/router returns incorrect locale and defaultLocale values (since i18n was unset), so instead you should use useRouter from nextra/hooks which will return correct values.

  2. Popular i18n libraries like react-intl and react-i18next or others will not work either.

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.js or _meta.jsx with JSX support or _meta.{ts,tsx} for TypeScript projects instead
  • you can use ESLintโ€™s built-in rule sort-keys with /* eslint sort-keys: error */ comment to sort your sidebar items alphabetically
  • you can export and import everything that you want in your _meta files
  • you can have typesafe _meta.{ts,tsx} files while asserting type definition from the nextra package
  • you can add a banner/footer for your repeated content in _meta files, for individual pages, or for all nested pages in * key

More Powerful TOC

There is no MDX framework where TOC can be better than in Nextra 3 (Correct me if I am wrong ๐Ÿ™‚).

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! ๐Ÿงฎ

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 kB

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 kB

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:

  1. buildDynamicMDX and buildDynamicMeta from nextra/remote

  2. RemoteContent from nextra/components - React component which evaluates your compiled remote MDX.

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 ๐Ÿคฏ!

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'{:js} 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.

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 shiki is also that without css-variables theme, 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 via head option now)

  • toc.headingComponent

  • sidebar.titleComponent (Use JSX elements in your _meta file 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.