How to Fix: Exported value is undefined sometimes when using `export * from ‘module’`
Why export * from 'module' Can Make an Exported Value Randomly Become undefined in Next.js
If a value works when imported directly but becomes undefined when re-exported with export * from 'module', the problem is usually not randomness at all. It is almost always caused by a module evaluation order problem, often amplified by barrel exports, CommonJS/ESM interop, or a circular dependency introduced during bundling.
In the reported issue, the failure appears after generating the Prisma client and then re-exporting symbols through an index file using export *. That pattern looks harmless, but it can break when the generated module, the consuming module, and the re-export layer are evaluated in an order that exposes an uninitialized binding.
Understanding the Root Cause
export * from 'module' creates a re-export of all named exports from another module. In modern JavaScript, those exports are live bindings, not copied values. That matters because the binding exists before the value is fully initialized.
There are three common technical reasons this bug shows up:
1. Circular dependency through a barrel file
The most common cause is a dependency graph like this:
// prisma/index.ts
export * from './client'
// some-feature.ts
import { prisma } from '@/prisma'
// client.ts
import { something } from './some-feature'
export const prisma = createClient()
Even if the cycle is indirect, the result is the same: during module initialization, one file reads a binding from another file before that value has been assigned. The import succeeds syntactically, but the runtime value can be undefined.
2. Generated Prisma client interacting with re-export layers
Prisma generated code is not handwritten source. It may include runtime initialization that depends on evaluation happening in a specific order. When you add an extra barrel layer such as:
export * from './generated/prisma'
you introduce another module boundary. In development or production builds, Next.js and its bundler can optimize, split, or reorder module execution differently from what you expect.
3. ESM and CommonJS interop quirks
If a generated or external module is emitted in a way that behaves like CommonJS internally, then wildcard re-exports can behave differently than direct imports, especially when tools transform the code. In those cases, direct named exports are safer than broad wildcard forwarding.
So the root issue is not that export * is broken by itself. The real issue is that wildcard re-exporting can hide dependency cycles and make initialization timing bugs easier to trigger.
Step-by-Step Solution
The most reliable fix is to stop re-exporting the Prisma value through export * and instead use direct explicit exports or direct imports from the source module.
Step 1: Identify the barrel file
Look for an index file or shared module that contains code like this:
// bad pattern for this case
export * from './prisma'
export * from './generated/client'
If your app imports Prisma from that barrel, you are increasing the chance of a cycle or evaluation-order bug.
Step 2: Replace wildcard re-exports with explicit named exports
Instead of this:
// index.ts
export * from './client'
use this:
// index.ts
export { prisma } from './client'
export type { Prisma, User } from './client'
This reduces ambiguity and avoids forwarding every symbol through a wildcard layer.
Step 3: Prefer importing Prisma directly from its defining module
Create a dedicated module for the Prisma client:
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as typeof globalThis & {
prisma?: PrismaClient
}
export const prisma =
globalForPrisma.prisma ??
new PrismaClient()
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}
Then import it directly wherever needed:
// app/api/users/route.ts
import { prisma } from '@/lib/prisma'
export async function GET() {
const users = await prisma.user.findMany()
return Response.json(users)
}
Avoid this pattern:
// avoid importing through broad barrels
import { prisma } from '@/lib'
Step 4: Remove circular imports
Check whether your Prisma module imports anything that eventually imports Prisma again. A problematic chain may look like this:
// lib/prisma.ts
import { logger } from '@/lib/logger'
// lib/logger.ts
import { prisma } from '@/lib'
That creates a cycle. Break it by moving shared utilities into separate modules that do not depend on Prisma.
A safer structure is:
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
export const prisma = new PrismaClient()
// lib/logger.ts
export function logger(message: string) {
console.log(message)
}
// features/user-service.ts
import { prisma } from '@/lib/prisma'
import { logger } from '@/lib/logger'
Step 5: Regenerate Prisma and restart the dev server
After changing exports, regenerate the client and fully restart Next.js so stale compiled output is removed.
npx prisma generate
rm -rf .next
npm run dev
If you use another package manager or script, run the equivalent clean build command.
Step 6: Validate with direct import vs re-export test
If you want to confirm the root cause quickly, compare these two imports:
// test A: direct import
import { prisma } from '@/lib/prisma'
// test B: re-exported import
import { prisma } from '@/lib'
If test A works consistently and test B sometimes returns undefined, the barrel export is the trigger.
Recommended final pattern
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as typeof globalThis & {
prisma?: PrismaClient
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
// use direct imports only
import { prisma } from '@/lib/prisma'
If you still want a barrel file, keep Prisma out of it unless you are certain there is no cycle and no bundler transform issue affecting evaluation order.
Common Edge Cases
Type exports vs value exports
TypeScript type exports are erased at runtime, but value exports are not. A file that works for:
export type { Prisma } from '@prisma/client'
may still fail for:
export { PrismaClient } from '@prisma/client'
because only the second one affects runtime module loading.
Default exports are not included in export *
If the underlying module uses a default export, wildcard re-export will not forward it automatically. That can look like an undefined export bug when it is actually an export mismatch.
// this does not re-export default
export * from './client'
Use:
export { default as client } from './client'
Next.js dev vs production differences
Fast Refresh, server bundling, and production optimization can expose cycles differently. A bug that appears “sometimes” often means the dependency graph is fragile and behaves differently across rebuilds.
Path aliases can hide the same file behind multiple import paths
If one file imports Prisma with @/lib/prisma and another imports the same module through a different relative path, bundling can become harder to reason about. Keep import paths consistent.
Server-only modules imported into shared code
Prisma should stay in server-only code. If a shared barrel is used by both client and server modules, Next.js may produce unexpected behavior or hard build errors.
FAQ
1. Why does direct import work but export * from fail?
Direct imports reduce the number of module boundaries and often avoid a circular dependency exposed by the barrel file. The direct path loads the defining module immediately, while the re-export path can involve extra evaluation steps.
2. Is this a Prisma bug or a Next.js bug?
Usually it is neither in isolation. It is most often a module graph issue triggered by how generated Prisma code, wildcard re-exports, and Next.js bundling interact. Prisma generation makes the issue visible, but the core problem is the runtime import chain.
3. Can I still use barrel files safely?
Yes, but use them carefully. Prefer explicit exports, avoid placing initialization-heavy modules like Prisma in broad barrels, and check for cycles. Barrel files are best for simple stateless utilities, types, and pure functions.
The practical fix is simple: do not re-export your Prisma client through export *. Export it explicitly or import it directly from its source module. That removes the hidden initialization trap and makes the undefined value issue disappear in a predictable way.
For reproduction details, review the linked repository in the issue description through the provided example project and compare its barrel export structure against a direct-import Prisma setup.