# Next.js (/integrations/nextjs)

[Full example on GitHub](https://github.com/leandrocp/lumis/tree/main/packages/javascript/rehype-lumis/examples/nextjs)

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

## Install

```bash
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:

```js
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'],
})
```

```js title="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()`](/integrations/react#server-rendering), highlights on the server when the route renders. That needs two settings:

```js title="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`](https://nextjs.org/docs/app/api-reference/config/next-config-js/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:

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

[`outputFileTracingIncludes`](https://nextjs.org/docs/app/api-reference/config/next-config-js/output) adds the installed [parser packages](/usage/javascript#where-parsers-come-from) 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.