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: falseleaves the loader with no module record to read. - Use
.mdxfor 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 devandnext build.
Configure Next
// 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:
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
| Phase | Preparation |
|---|---|
PHASE_DEVELOPMENT_SERVER | Build once, then start the compiler's recursive watcher. |
PHASE_PRODUCTION_BUILD | Build once without starting a watcher. |
| Any other phase | Skip 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:
- Reads
<generatedDirectory>/index.json. - Checks that its config fingerprint matches the wrapper's current normalized config.
- Resolves the source's cache key and validates its hexadecimal shape.
- Reads the corresponding JSON cache record.
- Verifies the record key and returns its compiled
modulestring.
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
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
| Symptom | Check |
|---|---|
AMAMO_NEXT_CACHE_MISS | Cache is enabled, startup build completed, and generatedDirectory is shared with the loader. |
| Config fingerprint mismatch | Restart Next after editing the config and remove stale generated output if necessary. |
| A custom extension is not transformed | Rename it to .mdx or provide a separate Next loader; the built-in rule is fixed. |
| Native binding unavailable | Reinstall on a supported target instead of copying node_modules. |