Prisma v7 Migration on Next.js 16 — Turbopack Fix Guide
Step-by-step migration from Prisma v6 to v7 in Next.js 16, resolving Turbopack module errors and configuring…

⚡ Next.js Implementation Guides
In-depth Next.js guides covering App Router, RSC, ISR, and deployment. Get code examples, optimization checklists, and prompts to accelerate development.
I recently upgraded a production Next.js application to version 16, and with it came the need to migrate from Prisma v6 to v7. What should have been a straightforward package upgrade turned into several hours of debugging cryptic module resolution errors. The official Prisma v7 migration guide doesn't address the specific compatibility issues with Next.js 16's Turbopack bundler, which resulted in "Cannot find module '.prisma/client/default'" errors that completely broke the application.
After working through the issues and getting everything running smoothly, I wanted to document the exact process. This guide walks you through migrating from Prisma v6 to Prisma v7 in a Next.js 16 application using Turbopack, with proper PostgreSQL adapter configuration. By the end, you'll have a working Prisma v7 setup that's fully compatible with Turbopack's module bundling system.
Understanding the Prisma v7 Changes
Prisma v7 introduces several breaking changes that fundamentally alter how the client is instantiated and configured. The most significant change is the requirement for driver adapters. In previous versions, Prisma handled database connections internally through its query engine. Version 7 moves to an adapter-based architecture where you explicitly provide a database driver, making the main Prisma Client leaner and more flexible.
For PostgreSQL users, this means installing the @prisma/adapter-pg package and the pg driver, then creating a connection pool that gets passed to the Prisma Client. The schema configuration also changes with the introduction of prisma.config.ts for runtime configuration, separating concerns between schema definition and connection management. Additionally, the generator provider name changes from prisma-client-js to prisma-client in the official documentation, though as we'll see, this creates issues with Turbopack that require a specific workaround.
Upgrading the Prisma Packages
The first step is upgrading both the Prisma CLI and client packages to version 7. The CLI is a dev dependency used for migrations and client generation, while the client package is the runtime dependency your application uses to query the database.
pnpm add @prisma/client@7 pnpm add -D prisma@7
Along with Prisma itself, you'll need to install the PostgreSQL adapter and driver packages, as well as dotenv for environment variable management in the new config system.
pnpm add @prisma/adapter-pg pg dotenv pnpm add -D @types/pg
These packages work together to provide the connection layer that Prisma v7 requires. The adapter translates between Prisma's query interface and the pg driver's native connection pooling.
Configuring the Prisma Schema
The schema file structure changes in Prisma v7. The datasource block no longer contains the database URL directly, as this configuration moves to the new prisma.config.ts file. This separation allows for more flexible configuration patterns and better alignment with TypeScript-based configuration systems.
Update your schema file to remove the URL configuration:
// File: prisma/schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" } // Your models remain unchanged model User { id String @id @default(uuid()) email String @unique name String // ... rest of your schema }
The key change here is removing the url and directUrl properties from the datasource block. Notice we're using prisma-client-js as the provider rather than the new prisma-client name mentioned in the official docs. This is intentional and critical for Turbopack compatibility, which I'll explain in detail later.
Creating the Prisma Configuration File
Prisma v7 introduces a new TypeScript configuration file that handles runtime settings including database URLs, migration paths, and other CLI behaviors. This file uses the defineConfig helper and supports type-safe environment variable access.
Create the configuration file at your project root:
// File: prisma.config.ts
import 'dotenv/config';
import { defineConfig, env } from 'prisma/config';
export default defineConfig({
schema: 'prisma/schema.prisma',
datasource: {
url: env('DATABASE_URL_UNPOOLED'),
},
migrations: {
path: 'prisma/migrations',
},
});
This configuration tells Prisma where to find your schema file and where to generate migrations. The env() helper provides type-safe access to environment variables. I'm using DATABASE_URL_UNPOOLED here because my project uses connection pooling with a service like Neon or Supabase, where the unpooled URL is needed for migrations. If you're running a standard PostgreSQL instance, you can use env('DATABASE_URL') instead.
The dotenv import at the top ensures environment variables are loaded when the Prisma CLI runs. This is necessary because Prisma v7 no longer automatically loads environment variables.
Updating the Prisma Client Instantiation
The way you create and export your Prisma Client changes significantly in version 7. Instead of directly instantiating PrismaClient, you now create a database adapter first, then pass it to the client constructor. This adapter pattern gives you more control over connection pooling and allows Prisma to support multiple database drivers more efficiently.
Update your Prisma client file:
// File: lib/db/prisma.ts
import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import pg from 'pg';
const { Pool } = pg;
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
pool: pg.Pool | undefined;
};
// Create connection pool for the adapter
const pool = globalForPrisma.pool ?? new Pool({
connectionString: process.env.DATABASE_URL,
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.pool = pool;
}
// Create Prisma adapter
const adapter = new PrismaPg(pool);
// Create Prisma Client with adapter
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
adapter,
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
export default prisma;
This code creates a PostgreSQL connection pool using the pg driver, wraps it in the Prisma adapter, and passes that adapter to the PrismaClient constructor. The global caching pattern prevents Next.js hot reload from creating multiple client instances during development. Notice we're caching both the pool and the prisma client to the global object, ensuring connection reuse across hot reloads.
The adapter acts as a bridge between Prisma's query interface and the native PostgreSQL driver. When Prisma needs to execute a query, it translates it into the format expected by the pg driver, which then handles the actual database communication through the connection pool.
Handling the Turbopack Compatibility Issue
Here's where things get tricky. The official Prisma v7 migration guide recommends changing your generator provider from prisma-client-js to prisma-client. However, this creates a module resolution error with Next.js 16's Turbopack bundler. When you run your application, you'll encounter an error about missing the .prisma/client/default module.
The issue stems from how Turbopack resolves and bundles the Prisma client. The new prisma-client provider generates client code in a structure that Turbopack doesn't correctly resolve during server-side rendering. The generated client files exist on disk, but Turbopack's module resolution algorithm can't find them at runtime.
The solution is to stick with the prisma-client-js provider name in your schema, even though you're using Prisma v7. This provider generates the client in the traditional structure that Turbopack knows how to handle. Importantly, you should not specify a custom output path in the generator block. Let Prisma use its default output location.
Your schema's generator block should look exactly like this:
generator client { provider = "prisma-client-js" }
Do not add an output field, and do not use prisma-client as the provider. While this seems to contradict the official migration guide, it's the configuration that actually works with Turbopack in Next.js 16.
Generating and Testing the Client
With your schema and configuration files updated, generate the Prisma client:
npx prisma generate
You should see output indicating the client was generated successfully. The output will show the path where the client was created, typically in your node_modules under the .pnpm/@prisma+client@7.x.x/node_modules/@prisma/client directory when using pnpm.
If you have existing migrations, apply them with:
npx prisma migrate deploy
For new migrations during development, use:
npx prisma migrate dev --name your_migration_name
The migration commands now read their configuration from prisma.config.ts, using the database URL you specified there. This means you can have different URLs for migrations versus runtime queries, which is useful when working with connection pooling services.
Start your development server and test that database queries work correctly:
pnpm dev
Navigate to any route that performs database operations. If you see the "Cannot find module" error at this point, double-check that your generator uses prisma-client-js and doesn't have an output path specified. That combination is what makes it work with Turbopack.
Why This Configuration Works
The reason prisma-client-js works while prisma-client doesn't comes down to module resolution paths and how different bundlers handle them. The older provider name generates client code with a module structure that's been battle-tested across many bundler configurations, including webpack and Turbopack. The new provider name uses a different output structure that's optimized for modern ESM imports, but Turbopack's current implementation doesn't correctly resolve these paths during the server-side rendering phase.
Prisma v7's client is still fully functional using the old provider name. The internal implementation is identical, it's purely a difference in how the generated code is structured and exported. By using prisma-client-js, you get all the performance improvements and new features of Prisma v7, including the adapter architecture and improved query engine, while maintaining compatibility with Next.js 16's bundler.
This is likely a temporary issue that will be resolved as Turbopack matures and Prisma updates its generator to better support different bundler configurations. For now, using the older provider name is a practical workaround that lets you use Prisma v7 in production without issues.
Verifying Your Migration
After completing the migration, verify that everything works correctly by testing your application's database operations. Check that queries execute successfully, that connections are properly pooled, and that there are no runtime errors in your server logs. Pay particular attention to server actions and API routes, as these are where Prisma client usage is most common.
You should also verify that your development workflow still functions as expected. Hot reload should work without creating connection pool exhaustion, migrations should apply cleanly, and the Prisma Studio should connect properly if you use it.
Summary
Migrating to Prisma v7 on Next.js 16 with Turbopack requires careful attention to generator configuration. The key insight is using the prisma-client-js provider rather than the new prisma-client name to maintain Turbopack compatibility. Combined with proper adapter configuration through the @prisma/adapter-pg package and the new prisma.config.ts file, you can successfully run Prisma v7 with all its improvements while avoiding module resolution issues.
The adapter architecture in Prisma v7 provides better performance and more flexibility in how you manage database connections. Even though we're using a workaround for the generator provider, you still get the full benefits of the new version including faster queries, smaller bundle sizes, and more efficient connection pooling.
If you run into issues during your migration or have questions about specific configuration scenarios, let me know in the comments. I'll be sharing more guides on working with Prisma and Next.js as I continue building production applications with these tools.
Thanks, Matija