How to Fix: Build: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got undefined.
The build fails because React is trying to render a component that resolves to undefined during the Next.js 14.2.15 production build. In this case, the trigger is the interaction between MDX compilation, component exports, and how the imported Designsystemet components are bundled and evaluated at build time.
Table of Contents
Understanding the Root Cause
The error message:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
means that JSX compiled successfully, but one of the values used like a component was not actually a valid React component at runtime.
In a Next.js + MDX setup, this typically happens for one of these reasons:
- A component is imported as a named export when the library only provides a default export, or the reverse.
- The package entry used by MDX during server-side build does not expose the same symbols as the one used in development.
- A component map passed into MDX contains keys whose values are undefined.
- A package uses conditional exports or build output that works in dev, but breaks in production bundling.
For the reported repository, the important detail is that the failure appears during build, not normal local rendering. That strongly suggests a mismatch between what the MDX compiler expects and what the Designsystemet package actually exports in the build pipeline.
When MDX compiles content, tags and shortcodes are converted into function calls that reference a component registry. If one of those registry entries points to an unresolved import, React receives undefined instead of a valid function or string tag and throws this exact error.
In practice, the bug usually comes down to one of these patterns:
// Wrong: assuming named export exists when it does not exist at build time
import { Button } from '@navikt/ds-react'
// Or wrong in the opposite direction
import Button from '@navikt/ds-react'
or inside MDX component wiring:
import { Button, Heading } from '@navikt/ds-react'
export const mdxComponents = {
Button,
Heading,
}
If Button or Heading resolves to undefined in the production bundle, the page builds until React attempts to render the MDX tree.
Step-by-Step Solution
The fix is to verify the exact export shape used by the package and then make the MDX component mapping explicit and safe.
1. Inspect the failing component imports
Start with every component used inside MDX or injected into an MDX provider. Confirm whether each one is a named export or a default export from the installed package version.
// Check your current import style
import { Alert, BodyLong, Button, Heading } from '@navikt/ds-react'
If the package version or subpath export changed, update imports to the package’s documented API. Avoid guessing based on editor autocomplete alone.
2. Add a defensive MDX component registry
Create or update the file where MDX components are registered and make sure only valid values are exported.
import { Alert, BodyLong, Button, Heading } from '@navikt/ds-react'
export const mdxComponents = {
Alert,
BodyLong,
Button,
Heading,
}
Then temporarily validate the registry during debugging:
for (const [name, component] of Object.entries(mdxComponents)) {
if (!component) {
throw new Error(`MDX component mapping failed: ${name} is undefined`)
}
}
This turns a vague React runtime failure into a direct signal about which export is broken.
3. Avoid barrel imports if one export is unstable
If the root package export is the issue, import from the stable subpath exposed by the library, if available and documented.
// Example pattern only; use the actual documented subpaths for the package version
import { Button } from '@navikt/ds-react'
// or
import Button from '@navikt/ds-react/Button'
The exact correct form depends on the package version in the reproduction repository. The key is consistency with the package’s actual build artifacts.
4. Verify MDX integration in Next.js
If the project uses a custom MDX setup, ensure the provider passes the component map correctly.
import { MDXProvider } from '@mdx-js/react'
import { mdxComponents } from './mdx-components'
export function Providers({ children }) {
return (
<MDXProvider components={mdxComponents}>
{children}
</MDXProvider>
)
}
For App Router projects, also verify that the provider is used in the correct layout boundary and that any component requiring client behavior is declared appropriately.
5. Mark client-only wrappers when necessary
If the mapped components depend on browser APIs, hooks, or interactive behavior, the wrapper file may need to be a client component.
'use client'
import { MDXProvider } from '@mdx-js/react'
import { mdxComponents } from './mdx-components'
export function Providers({ children }) {
return <MDXProvider components={mdxComponents}>{children}</MDXProvider>
}
This does not fix undefined imports by itself, but it prevents a second class of App Router integration errors from masking the real problem.
6. Rebuild from a clean state
After fixing imports and MDX mappings, remove stale build artifacts and reinstall dependencies.
rm -rf .next node_modules
rm -f package-lock.json pnpm-lock.yaml yarn.lock
npm install
npm run build
If you use pnpm or yarn, replace the commands accordingly.
7. Confirm package version compatibility
The issue is specifically reported against Next 14.2.15. Check whether the Designsystemet package version in the reproduction branch is known to work with the current React, MDX, and Next.js versions. If not, pin or upgrade deliberately.
npm ls next react react-dom @mdx-js/react @navikt/ds-react
If the package recently changed its exports, align the imports with that version rather than mixing code samples from older docs.
8. A practical fix strategy for this issue
If you want the shortest path to resolution in the reproduction repository, use this sequence:
- Find the file that maps components into MDX.
- Log or assert every mapped import.
- Replace any ambiguous import style with the package’s documented export style.
- Rebuild after clearing
.nextand dependency lock drift.
import * as DS from '@navikt/ds-react'
console.log(Object.keys(DS))
console.log({
Alert: DS.Alert,
BodyLong: DS.BodyLong,
Button: DS.Button,
Heading: DS.Heading,
})
This is a fast way to confirm whether the named exports actually exist in the production-resolved module.
Common Edge Cases
- Incorrect re-exports: A local wrapper file re-exports components, but one symbol was removed or renamed. MDX imports from the wrapper, not the package directly.
- Server/client boundary issues: A provider or wrapper using hooks is rendered as a server component, causing confusing secondary failures.
- Transpile differences: A dependency works in dev but needs explicit transpilation or a compatible module target in production.
- Version skew: react and react-dom versions do not match, or the MDX integration package is out of sync with Next.js.
- Default vs named MDX shortcodes: The MDX file references
<Button />, but the registry exposes it under a different key. - Tree-shaking surprises: A barrel file or conditional export causes one component to disappear from the final build path.
If the build still fails after correcting imports, temporarily replace the full MDX component registry with a minimal set and add components back one by one. That isolates the exact undefined symbol quickly.
export const mdxComponents = {
p: 'p',
h2: 'h2',
}
If this builds, the bug is definitely inside the custom component mapping rather than MDX itself.
FAQ
Why does this happen only during next build and not in development?
Development and production do not always resolve modules the same way. Next.js production build applies different optimization, bundling, and server rendering steps. A fragile export pattern can survive in dev and fail when the final bundle is evaluated.
How can I identify which component is actually undefined?
Add assertions or logs to the MDX component registry before rendering. Throwing a custom error for each missing entry is the fastest way to replace the generic React message with the exact component name.
Should I fix this by adding 'use client' everywhere?
No. Use 'use client' only when a wrapper genuinely needs to run on the client. This issue is primarily about invalid imports or broken component mapping, not about client rendering alone.
The core fix is simple: make sure every component referenced by MDX resolves to a real React component in the production build. Once the imports and registry are aligned with the package’s actual exports, the Next.js build error disappears.
For the original reproduction, review the linked repository branch and trace the MDX component registration first. That is the most likely failure point behind this specific build crash.