LumisDocs
Integrations

Next.js

Use Lumis in Next.js MDX through rehype-lumis, and in Server Components.

Full example on GitHub

Next.js MDX already supports rehypePlugins. That makes @lumis-sh/rehype-lumis the right Lumis integration point.

Install

npm install @next/mdx @mdx-js/loader \
  @mdx-js/react @types/mdx
npm install @lumis-sh/rehype-lumis \
  @lumis-sh/lumis @lumis-sh/themes \
  @lumis-sh/wasm-bundle-web

Configure next.config.mjs

Wrap the configured plugin in a local module so Turbopack only sees a plain plugin reference:

import createMDX from '@next/mdx'
import {fileURLToPath} from 'node:url'

const rehypeLumisPath = fileURLToPath(
  new URL('./rehype-lumis.mjs', import.meta.url),
)

const withMDX = createMDX({
  options: {
    rehypePlugins: [rehypeLumisPath],
  },
})

export default withMDX({
  pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
})
rehype-lumis.mjs
import rehypeLumis from '@lumis-sh/rehype-lumis'
import {htmlInline} from '@lumis-sh/lumis/formatters'
import web from '@lumis-sh/wasm-bundle-web'
import latte from '@lumis-sh/themes/catppuccin_latte'

export default function configuredRehypeLumis() {
  return rehypeLumis.call(this, {
    formatter: (language) =>
      htmlInline({language, theme: latte}),
    languages: [web],
  })
}

This avoids passing non-serializable formatter callbacks directly through the MDX loader options.

You also need app/layout.tsx and mdx-components.tsx at the project root for Next.js App Router MDX to work.

Server Components

The MDX setup above highlights while Next.js compiles each page. A Server Component that imports @lumis-sh/lumis, directly or through renderCodeBlock(), highlights on the server when the route renders. That needs two settings:

next.config.mjs
export default {
  serverExternalPackages: ['@lumis-sh/lumis'],
  outputFileTracingIncludes: {
    '/*': ['./node_modules/@lumis-sh/wasm-*/**/*'],
  },
}

If you also use the MDX setup above, add these settings to the existing withMDX({...}) options.

You can import a language from a package such as @lumis-sh/wasm-css in a Server Component without adding that package to serverExternalPackages. Both Node runtimes read the installed parser package, even if Next.js rewrites the imported WASM URL. Keep the parser packages installed and included in file tracing.

serverExternalPackages leaves Lumis out of the server bundle and loads it with Node's require instead. On Node, Lumis highlights through a native addon that it loads with createRequire, and Turbopack, the default bundler since Next.js 16, can't place that addon in a chunk. Without this setting the build fails:

Error: non-ecmascript placeable asset
asset is not placeable in ESM chunks, so it doesn't have a module id

outputFileTracingIncludes adds the installed parser packages to the files traced for each route. Lumis looks those packages up at runtime from the project directory, which file tracing can't follow. A deployment that ships only the traced files, such as output: 'standalone', would leave them out, and the route would fail to load its language. '/*' adds them to every route; a narrower route glob limits them to the routes that highlight.

On this page