| name | nextjs-client-server-boundary-dns-error |
| description | Fix "Module not found: Can't resolve 'dns'" (or 'fs', 'net', 'tls') errors in Next.js with Prisma.
Use when: (1) Build fails with "Can't resolve 'dns'" from pg/connection-parameters.js,
(2) Error trace shows "[Client Component Browser]" importing server modules,
(3) Using Prisma with @prisma/adapter-pg and getting Node.js module errors,
(4) Barrel exports cause unexpected server code in client bundles.
Covers: barrel export patterns, Prisma enum imports, direct component imports, serverExternalPackages.
|
| author | Claude Code |
| user-invocable | false |
Next.js Client/Server Boundary: DNS Module Error
Problem
Next.js build fails with errors like:
Module not found: Can't resolve 'dns'
./node_modules/pg/lib/connection-parameters.js
Import trace:
Client Component Browser:
./node_modules/@prisma/adapter-pg/dist/index.mjs [Client Component Browser]
./src/lib/db/client.ts [Client Component Browser]
...
Similar errors occur for fs, net, tls, and other Node.js-only modules.
Context / Trigger Conditions
This error occurs when server-only code (like Prisma with pg adapter) is accidentally bundled into client components. The error message is misleading—it says "dns not found" but the actual problem is a client/server boundary violation.
Common triggers:
-
Barrel exports that include server modules:
export * from './services/alerts';
export * from './components';
import { BudgetChart } from '@/features/budget';
-
Importing Prisma enums in client components:
import { BudgetType } from '@/generated/prisma/client';
-
Service files that import DB client being re-exported:
import { prisma } from '@/lib/db/client';
export function formatWorkHours() { ... }
import { formatWorkHours } from './work-hours-calculator';
Solution
Fix 1: Import Directly from Component Files (Not Barrels)
import { BudgetChart } from '@/features/budget';
import { BudgetChart } from '@/features/budget/components/BudgetChart';
Fix 2: Split Server/Client Code in Service Files
export function formatWorkHours(hours: number): string { ... }
export function calculateWorkHoursSync(amount: number, rate: number): number { ... }
import { prisma } from '@/lib/db/client';
export { formatWorkHours, calculateWorkHoursSync } from './work-hours-utils';
export async function getAfterTaxHourlyRate(): Promise<number> {
const settings = await prisma.settings.findUnique(...);
}
Then client components import from the utils file:
import { formatWorkHours } from './work-hours-utils';
Fix 3: Use String Literals Instead of Prisma Enums
import { BudgetType } from '@/generated/prisma/client';
trpc.budget.list.useQuery({ type: BudgetType.MONTHLY });
const MONTHLY_BUDGET_TYPE = 'MONTHLY' as const;
trpc.budget.list.useQuery({ type: MONTHLY_BUDGET_TYPE });
import type { BudgetType } from '@/generated/prisma/client';
Fix 4: Configure serverExternalPackages (Alternative)
In next.config.mjs:
const nextConfig = {
experimental: {
serverExternalPackages: ['@prisma/client', 'pg'],
},
};
export default nextConfig;
This tells Turbopack to treat these as external server packages.
Fix 5: Separate Barrel Exports by Type
export { BudgetChart } from './BudgetChart';
export { BudgetProgress } from './BudgetProgress';
export * from './alerts';
export * from './comparison';
export * from './components';
export * from './types';
Verification
- Run
npm run build - should complete without "Module not found" errors
- Check that the import trace in any errors doesn't show
[Client Component Browser] for server modules
Example: Complete Fix Pattern
Before (broken):
'use client';
import { BudgetChart, calculateBudget } from '@/features/budget';
import { BudgetType } from '@/generated/prisma/client';
After (working):
'use client';
import { BudgetChart } from '@/features/budget/components/BudgetChart';
const MONTHLY_TYPE = 'MONTHLY' as const;
Notes
- The
'use client' directive creates a boundary—everything imported becomes part of the client bundle
- Tree shaking doesn't help because barrel files import everything before shaking
- Type imports (
import type { X }) are always safe—they're removed at compile time
- This issue is more common with Turbopack than webpack due to stricter bundling
- If using
serverExternalPackages, you still need to ensure the import paths are correct
References