-
-
Notifications
You must be signed in to change notification settings - Fork 10k
[Bug]: <Meta of={} /> fails in production build when CSF4 story module is split into a separate chunk #34373
Description
Describe the bug
When using CSF4 stories (preview.meta() / meta.story()) without export default together with MDX docs files that reference them via <Meta of={Stories} />, production builds (storybook build) fail at runtime with:
Error: <Meta of={} /> must reference a CSF file module export or meta export.
Did you mistakenly reference your component instead of your CSF file?
This only occurs in production builds when the bundler splits the story module into a separate chunk. storybook dev works correctly.
Reproduction link
https://github.com/yuheiy/storybook-mdx-meta-without-default-export-bug
Reproduction steps
npm install
npm run build-storybook
npx http-server storybook-static
# Open http://localhost:8080/iframe.html?viewMode=docs&id=button--docsKey files:
src/Button.stories.tsx— CSF4 story withoutexport default meta, with top-levelawait(async module)src/Button.mdx— MDX doc page using<Meta of={ButtonStories} />.storybook/main.ts— usescodeSplitting.groupsto force the story module into a separate chunk (simulates what happens naturally in large projects with many shared dependencies)
System
│
│ Storybook Environment Info:
│
│ System:
│ OS: macOS 15.7.4
│ CPU: (10) arm64 Apple M1 Max
│ Shell: 5.9 - /bin/zsh
│ Binaries:
│ Node: 24.14.0 -
│ /Users/yuhei.yasuda/.local/share/mise/installs/node/24.14.0/bin/node
│ Yarn: 2.4.3 -
│ /Users/yuhei.yasuda/.local/share/mise/installs/node/24.14.0/bin/yarn
│ npm: 11.9.0 -
│ /Users/yuhei.yasuda/.local/share/mise/installs/node/24.14.0/bin/npm
│ <----- active
│ pnpm: 10.33.0 - /Users/yuhei.yasuda/Library/pnpm/pnpm
│ Browsers:
│ Chrome: 146.0.7680.165
│ Firefox: 148.0
│ Safari: 26.3.1
│ npmPackages:
│ @storybook/addon-docs: ^10.3.3 => 10.3.3
│ @storybook/react-vite: ^10.3.3 => 10.3.3
│ storybook: ^10.3.3 => 10.3.3Additional context
Root cause
DocsContext.resolveModuleExport() uses a Map keyed by object identity (===) to look up CSF files:
// referenceCSFFile registers the module namespace object
referenceCSFFile(csfFile) {
this.exportsToCSFFile.set(csfFile.moduleExports, csfFile); // full namespace
this.exportsToCSFFile.set(csfFile.moduleExports.default, csfFile); // default export
}
// resolveModuleExport looks it up
resolveModuleExport(moduleExportOrType) {
let csfFile = this.exportsToCSFFile.get(moduleExportOrType);
// Fallback: check default export
if (!csfFile && moduleExportOrType && typeof moduleExportOrType == "object"
&& "default" in moduleExportOrType) {
csfFile = this.exportsToCSFFile.get(moduleExportOrType.default);
}
if (csfFile) return { type: "meta", csfFile };
// Falls through to { type: "component" } → error
}When the bundler splits a story module into a chunk file + re-export file:
- Storybook's runtime dynamically imports the re-export file → gets namespace object
A referenceCSFFile()registersAin the Map- The compiled MDX statically imports from the chunk file → gets a different namespace object
B <Meta of={B} />callsresolveModuleExport(B)→Map.get(B)fails becauseB !== A
The fallback checks "default" in B, but CSF4 modules have no export default, so the fallback is also ineffective.
Workaround
Add export default meta to CSF4 story files. This enables the existing fallback in resolveModuleExport because both namespace objects (A.default and B.default) reference the same meta object instance.
const meta = preview.meta({ component: Button });
+ export default meta;