@amamo/mdx
On This Page

Next 16

The Next integration separates compilation from loading. The config wrapper builds the content set; the bundler loader verifies index.json and returns the compiled module from its cache record.

Requirements

  • Keep persistent caching enabled. cache: false leaves the loader with no module record to read.
  • Use .mdx for files imported through Next. The built-in Turbopack and Webpack rules are fixed to *.mdx, even if a collection lists another extension.
  • Run on a supported native target during next dev and next build.

Configure Next

TypeScript
// next.config.ts
import { withAmamoMdx } from '@amamo/mdx/next'

import amamo from './amamo.config.mjs'

export default withAmamoMdx(amamo)({
  reactStrictMode: true,
})

withAmamoMdx(amamo)(nextConfig) returns an async Next config function. Next later invokes it with the current phase and { defaultConfig }; it does not return a resolved config immediately.

The inner input may be a config object, a promise, or a config function:

TypeScript
import { withAmamoMdx } from '@amamo/mdx/next'

import amamo from './amamo.config.mjs'

export default withAmamoMdx(amamo)(async (_phase, { defaultConfig }) => ({
  ...defaultConfig,
  images: {
    remotePatterns: [{ hostname: 'cdn.example.com', protocol: 'https' }],
  },
}))

The wrapper awaits the input, preserves its fields and existing webpack callback, then adds its own Turbopack and Webpack rules.

Phase behavior

PhasePreparation
PHASE_DEVELOPMENT_SERVERBuild once, then start the compiler's recursive watcher.
PHASE_PRODUCTION_BUILDBuild once without starting a watcher.
Any other phaseSkip build and watch preparation, but still return config with loader rules.

One wrapper invocation owns one lazy compiler and one shared build promise. Create the wrapper once in the Next config rather than recreating it for individual requests.

Loader flow

The same loader is registered at:

  • turbopack.rules['*.mdx'], with output declared as JavaScript.
  • A test: /\.mdx$/ Webpack module rule.

For each imported source path, the loader:

  1. Reads <generatedDirectory>/index.json.
  2. Checks that its config fingerprint matches the wrapper's current normalized config.
  3. Resolves the source's cache key and validates its hexadecimal shape.
  4. Reads the corresponding JSON cache record.
  5. Verifies the record key and returns its compiled module string.

Any mismatch rejects with AMAMO_NEXT_CACHE_MISS and asks for a coordinated rebuild. The loader is read-only; parsing, Shiki, manifest writing, and cache pruning happen in the wrapper-owned compiler.

Import MDX

TSX
import Post, { frontmatter } from '../content/posts/hello.mdx'

export default function Page() {
  return (
    <main>
      <h1>{frontmatter.title}</h1>
      <Post />
    </main>
  )
}

Turbopack and Webpack read the same index and record format.

Development behavior

In development, the compiler watches config.root recursively. A recognized create or change runs transform; a deleted known record runs remove. This refreshes cache and generated outputs independently of Next's file watcher; the two watchers have no ordering handshake.

Restart next dev after changing the configuration itself. The wrapper's normalized config, fingerprint, and loader options are fixed when withAmamoMdx(amamo) is evaluated.

Troubleshooting

SymptomCheck
AMAMO_NEXT_CACHE_MISSCache is enabled, startup build completed, and generatedDirectory is shared with the loader.
Config fingerprint mismatchRestart Next after editing the config and remove stale generated output if necessary.
A custom extension is not transformedRename it to .mdx or provide a separate Next loader; the built-in rule is fixed.
Native binding unavailableReinstall on a supported target instead of copying node_modules.
Copyright © 2026 白熱.