O erro: build travado no pnpm dev#
Pré-requisitos#
Antes de iniciar a depuração e aplicar as soluções, certifique-se de que seu ambiente atende aos seguintes requisitos:
- Next.js: 13.4+ (com suporte a App Router e Edge Middleware)
- Prisma: 5.x+ (com suporte nativo ao Edge Runtime)
- Node.js: 18+ (ambiente de desenvolvimento e execução)
- pnpm: 8.x+ (ou gerenciador equivalente, como npm ou yarn)
O erro surgiu no momento de subir o ambiente de desenvolvimento. O servidor Next.js nem completou o processo de bootstrap: a compilação abortou imediatamente com o seguinte output no terminal:
⨯ ./node_modules/.../@prisma/client/default.js:2:6
Module not found: Can't resolve '.prisma/client/default'
1 | module.exports = {
> 2 | ...require('.prisma/client/default'),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3 | }
Import trace:
Edge Middleware:
./node_modules/.../@prisma/client/default.js
./src/lib/db-client.ts
./src/lib/auth.ts
./middleware.ts
Dois problemas distintos estão aninhados nesse único output, e tratá-los separadamente é o que define a diferença entre uma correção superficial e uma resolução de arquitetura real.
Problema 1 - o cliente "fantasma": ausência do artefato gerado pelo prisma#
Anatomia do problema#
O pacote @prisma/client que existe no node_modules após um pnpm install é deliberadamente uma casca vazia. O diretório @prisma/client contém apenas o código-base genérico do cliente. O módulo real, que conhece suas tabelas, modelos, relações, tipos TypeScript e o URL do banco de dados: precisa ser gerado localmente pela CLI do Prisma com base no arquivo schema.prisma do projeto.
O artefato gerado é depositado no diretório .prisma/client/ dentro de node_modules. Este diretório não é versionado no Git (está no .gitignore por padrão) e não é criado pelo pnpm install. Ele só existe após a execução explícita do gerador.
Por que esse erro aparece? Cenários comuns:
- Clone limpo do repositório seguido apenas de
pnpm install - Limpeza de cache com
pnpm store pruneou remoção manual denode_modules - Atualização de versão do Prisma sem regenerar o cliente
- Ambiente de CI/CD sem o step
prisma generate
Verificando o estado atual do artefato gerado:
# Se este diretório não existir ou estiver vazio, o erro ocorrerá
ls node_modules/.prisma/client/
# Esperado em estado saudável:
# default.js default.d.ts edge.js edge.d.ts index.js index.d.ts ...
# schema.prisma libquery_engine-*.so.node (ou .dll no Windows)
Resolução da Camada 1: prisma generate#
pnpm prisma generate
Saída esperada de sucesso:
Prisma schema loaded from prisma/schema.prisma
Environment variables loaded from .env
✔ Generated Prisma Client (v5.x.x) to ./node_modules/@prisma/client in 1.2s
Start by importing your Prisma Client (See: https://pris.ly/d/importing-client):
import { PrismaClient } from '@prisma/client'
O que o prisma generate faz exatamente:
- Lê o
schema.prismae valida a sintaxe - Introspecciona os tipos e relações definidos nos models
- Compila o Query Engine binário específico para o OS/arquitetura atual (
.so.nodeno Linux,.dllno Windows,.dylibno macOS) - Gera os tipos TypeScript para todos os models (
PrismaClient,User,Post, etc.) - Deposita tudo em
node_modules/.prisma/client/
Integração no package.json para evitar reincidência:
{
"scripts": {
"dev": "prisma generate && next dev",
"build": "prisma generate && next build",
"postinstall": "prisma generate"
}
}
O script postinstall garante que o prisma generate rode automaticamente após qualquer pnpm install, incluindo em ambientes de CI/CD e Vercel deployments.
Após o prisma generate, o pnpm dev reiniciado compilou com sucesso. Mas a análise do log de erro revelou que o trabalho estava longe de terminar.
Problema 2 - a armadilha arquitetural: prisma client no edge runtime#
Lendo o import trace com atenção forense#
O campo Import trace no erro original é o ponto mais crítico de toda a análise, e o mais ignorado:
Import trace:
Edge Middleware:
./node_modules/.../@prisma/client/default.js ← Prisma Client sendo importado
./src/lib/db-client.ts ← Wrapper do Prisma
./src/lib/auth.ts ← Módulo de autenticação
./middleware.ts ← ARQUIVO DE MIDDLEWARE DO NEXT.JS
O prefixo Edge Middleware: é o alarme de incêndio. Ele indica que o compilador do Next.js está tentando incluir o Prisma Client no bundle do Edge Runtime, e isso é uma incompatibilidade fundamental de ambiente de execução.
O que é o edge runtime do next.js e por que o prisma não funciona lá#
O Edge Runtime é um ambiente de execução JavaScript altamente otimizado para latência, usado pelo middleware.ts do Next.js (e opcionalmente por Route Handlers com export const runtime = 'edge'). Ele é baseado no V8 Isolates, a mesma arquitetura usada pelo Cloudflare Workers, e opera com restrições severas propositais:
| Capacidade | Node.js Runtime | Edge Runtime |
|---|---|---|
fs (File System) | ✅ Disponível | ❌ Ausente |
child_process | ✅ Disponível | ❌ Ausente |
net (TCP sockets) | ✅ Disponível | ❌ Ausente |
Módulos nativos .node | ✅ Disponível | ❌ Ausente |
| Web APIs (fetch, crypto) | ✅ Disponível | ✅ Disponível |
| Tamanho máximo do bundle | ~50 MB | ~1-4 MB |
| Cold start | ~100-500ms | ~1-5ms |
O Prisma Client padrão é fundamentalmente incompatível com o Edge Runtime porque:
- Depende do Query Engine binário - um arquivo
.so.nodeou.dllcompilado nativamente que usachild_processpara comunicação via IPC com o processo Prisma - Usa TCP sockets via
netpara conectar ao banco de dados - Acessa o filesystem para ler o schema e o certificado SSL do banco
- Excede o limite de tamanho do bundle Edge
O error que ocorreria em runtime (após corrigir apenas o prisma generate):
Error: PrismaClient is unable to run in this browser environment, or has been bundled
for the browser (running in `EdgeRuntime`).
In case you want to use Prisma Client in an Edge environment, you can use the Prisma
Accelerate extension: https://pris.ly/d/accelerate.
If this is unexpected, please open an issue: https://pris.ly/prisma-client-env
Ou mais diretamente em ambientes de deploy:
⨯ TypeError: Cannot read properties of undefined (reading 'connect')
⨯ Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported.
Este erro só se manifestaria em produção ou ao testar o middleware em staging: um bug latente invisível durante o desenvolvimento local se a aplicação levantar corretamente.
Por que o middleware estava importando o prisma?#
O padrão mais comum que leva a esta situação:
// middleware.ts (PROBLEMÁTICO)
import { NextRequest, NextResponse } from 'next/server'
import { validateSession } from '@/lib/auth' // ← Importa auth.ts
// auth.ts importa db-client.ts
// db-client.ts importa PrismaClient
// PrismaClient → .prisma/client/default.js → INCOMPATÍVEL COM EDGE
export async function middleware(request: NextRequest) {
const session = await validateSession(request) // Fazendo query no banco no middleware!
if (!session) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
}
O problema é a cadeia de importação transitiva: o middleware não importa o Prisma diretamente, mas importa auth.ts que importa db-client.ts que importa o PrismaClient. O compilador do Next.js detecta toda a árvore de dependências e a inclui no bundle, não apenas o que é diretamente chamado.
As 3 rotas arquiteturais de resolução#
Rota 1 - desacoplamento JWT no middleware (a escolha adotada)#
A estratégia: Remover completamente qualquer chamada a banco de dados do middleware. Em vez de validar a sessão consultando o banco, validar apenas a integridade criptográfica do token de autenticação.
Para isso, usaremos a biblioteca jose, que é otimizada para Web APIs e não possui dependências nativas de Node.js.
Instalação da biblioteca jose:
# Via pnpm
pnpm add jose
# OU via npm
npm install jose
# OU via yarn
yarn add jose
# Verificar se a instalação foi concluída
pnpm list jose
Middleware robusto e otimizado para o edge#
Abaixo está a implementação recomendada do middleware.ts, cobrindo tratamento de erros detalhado, manipulação segura de cookies e cabeçalhos, e cenários de redirecionamento:
// middleware.ts (CORRETO - Zero Dependências Node.js)
import { NextRequest, NextResponse } from 'next/server'
import { jwtVerify } from 'jose'
// No Edge Runtime, process.env é acessível mas com limitações:
// 1. Apenas variáveis prefixadas com NEXT_PUBLIC_ são expostas ao client
// 2. Variáveis sensíveis (como secrets de JWT) devem ser acessadas exclusivamente no server/middleware
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET)
export async function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value
if (!token) {
// Redirecionamento 302 (Temporário - padrão)
// Redireciona o usuário para a tela de login se não houver cookie de sessão
return NextResponse.redirect(new URL('/login', request.url))
}
try {
// Verificação criptográfica pura - sem realizar queries de banco no Edge
const { payload } = await jwtVerify(token, JWT_SECRET)
// Validar se o payload do JWT possui as claims mínimas obrigatórias
if (!payload.sub || !payload.role) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Injetar dados do payload nos headers para leitura nos Server Components
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-user-id', payload.sub as string)
requestHeaders.set('x-user-role', payload.role as string)
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
})
// Exemplo de manipulação de Cookies no Edge Runtime (Sliding Session)
// Atualiza a expiração do cookie a cada requisição válida
response.cookies.set('auth-token', token, {
httpOnly: true, // Proteção contra ataques XSS (leitura de cookie via script)
secure: process.env.NODE_ENV === 'production', // Exige HTTPS em produção
sameSite: 'lax', // Atenuação de ataques CSRF
maxAge: 60 * 60 * 24 * 7, // 7 dias
path: '/',
})
return response
} catch (error: any) {
// Tratamento de erros granular baseado nos códigos de erro da biblioteca jose
console.error('Erro na validação do JWT no Edge:', error.code || error.message)
// ERR_JWT_EXPIRED: Token expirado
if (error.code === 'ERR_JWT_EXPIRED') {
const response = NextResponse.redirect(new URL('/login?expired=true', request.url))
// Limpar o cookie expirado para evitar loops
response.cookies.delete('auth-token')
return response
}
// ERR_JWS_SIGNATURE_VERIFICATION_FAILED ou outros erros (Token inválido/corrompido)
const response = NextResponse.redirect(new URL('/login?invalid=true', request.url))
response.cookies.delete('auth-token')
return response
}
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico|login).*)']
}
Cenários de redirecionamento no middleware#
Ao trabalhar com redirecionamentos no Next.js Middleware, use as opções de status adequadas a cada necessidade:
- Status 302 (Found): Redirecionamento temporário padrão. Ideal para redirecionar usuários não autenticados para
/login. - Status 307 (Temporary Redirect): Redirecionamento temporário estrito que garante que o método HTTP original seja preservado (ex: um POST permanece POST no destino).
NextResponse.redirect(url, { status: 307 }). - Status 301 (Moved Permanently): Redirecionamento permanente. Usado principalmente para migrações de rotas antigas para novas com fins de SEO.
- Redirecionamento Externo: Útil para delegar a autenticação a um provedor externo (SSO/OAuth):
NextResponse.redirect('https://auth.provider.com/login').
Variáveis de ambiente no edge runtime#
No Edge Runtime do Next.js:
- No lado do servidor (Middleware / Server Components): Variáveis de ambiente sensíveis (como
JWT_SECRET) são acessíveis através deprocess.env.JWT_SECRET, contanto que estejam definidas nas variáveis de ambiente da VPS ou do arquivo.env. - No lado do cliente (Client Components): Somente variáveis que possuem o prefixo
NEXT_PUBLIC_são injetadas no bundle do cliente. Variáveis secretas sem esse prefixo retornarãoundefinedno navegador para garantir que dados confidenciais não vazem.
Organização de múltiplos middlewares (pipeline)#
Como o Next.js suporta apenas um único arquivo middleware.ts global na raiz do projeto, a melhor prática arquitetural para gerenciar múltiplos middlewares (ex: autenticação, logging e rate limiting) é utilizar um pipeline wrapper:
// middleware.ts - Middleware Principal (Orquestrador)
import { NextRequest, NextResponse } from 'next/server'
import { authMiddleware } from './middleware/auth'
import { rateLimitMiddleware } from './middleware/rate-limit'
export async function middleware(request: NextRequest) {
// 1. Executa rate limiting
const rateLimitResponse = await rateLimitMiddleware(request)
if (rateLimitResponse) return rateLimitResponse
// 2. Executa validação de autenticação
const authResponse = await authMiddleware(request)
if (authResponse) return authResponse
return NextResponse.next()
}
Configurações recomendadas no next.config.js#
Configurações e cabeçalhos de segurança úteis para APIs que rodam no Next.js:
// next.config.js
module.exports = {
// Headers globais de segurança contra clickjacking, MIME sniffing e vazamento de referer
async headers() {
return [
{
source: '/api/:path*',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }
],
},
];
},
};
// src/lib/auth.ts (REFATORADO - lógica pesada movida para Server Components)
import { prisma } from '@/lib/db-client'
// Esta função SÓ deve ser chamada em Server Components ou Route Handlers (Node.js Runtime)
export async function getFullUserSession(userId: string) {
return await prisma.user.findUnique({
where: { id: userId },
include: { permissions: true, organization: true }
})
}
// app/dashboard/layout.tsx (Server Component - Node.js Runtime)
import { headers } from 'next/headers'
import { getFullUserSession } from '@/lib/auth'
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
const headersList = headers()
const userId = headersList.get('x-user-id')
// Aqui o Prisma funciona perfeitamente - estamos no Node.js Runtime
const user = await getFullUserSession(userId!)
if (!user?.permissions?.canAccessDashboard) {
redirect('/unauthorized')
}
return <>{children}</>
}
Trade-offs desta abordagem:
- ✅ Zero latência adicional no middleware (verificação criptográfica é ~0.1ms)
- ✅ Funciona em qualquer edge (Vercel, Cloudflare, AWS Lambda@Edge)
- ⚠️ O JWT pode estar assinado mas o usuário pode ter sido desativado no banco - a validação completa acontece nos Server Components
Rota 2 - prisma accelerate ou drivers HTTP serverless#
Quando usar: Se a regra de negócio exige rejeitar a requisição no middleware com dados do banco (ex: verificar se um IP está banido, se um slug existe, se uma feature flag está ativa para o usuário).
Prisma Accelerate é o serviço gerenciado da Prisma que expõe o banco de dados via HTTP em vez de TCP, tornando-o compatível com Edge:
// prisma/schema.prisma - adicionar extensão Accelerate
generator client {
provider = "prisma-client-js"
previewFeatures = ["driverAdapters"]
}
pnpm add @prisma/extension-accelerate
// src/lib/edge-db-client.ts (Para uso EXCLUSIVO no middleware/edge)
import { PrismaClient } from '@prisma/client/edge'
import { withAccelerate } from '@prisma/extension-accelerate'
// URL do Prisma Accelerate - diferente da DATABASE_URL padrão
const prismaEdge = new PrismaClient({
datasourceUrl: process.env.DATABASE_URL_ACCELERATE, // prisma://...
}).$extends(withAccelerate())
export { prismaEdge }
Alternativa com Neon (PostgreSQL serverless via HTTP):
// Para Neon Database - driver HTTP nativo
import { neon } from '@neondatabase/serverless'
const sql = neon(process.env.DATABASE_URL!)
// Uso no middleware - query HTTP direta sem TCP
const result = await sql`SELECT id FROM banned_ips WHERE ip = ${clientIP}`
Rota 3 - reconfigurar o runtime do middleware para Node.js#
Não é possível. O middleware.ts do Next.js é obrigatoriamente executado no Edge Runtime. Não existe uma configuração export const runtime = 'nodejs' válida para arquivos middleware.ts - isso é uma limitação de design intencional do framework para garantir cold starts ultra-rápidos.
Se você tentar:
// middleware.ts - NÃO FUNCIONA
export const runtime = 'nodejs' // ← Ignorado silenciosamente ou gera erro de build
O Next.js ignora ou rejeita esta declaração especificamente no contexto do middleware global.
Checklist de diagnóstico: import trace como ferramenta forense#
Sempre que um erro de compilação aparecer no contexto do Next.js, o Import trace é o ponto de partida:
# Forçar output verboso do compilador para ver o trace completo
NEXT_TELEMETRY_DISABLED=1 pnpm dev 2>&1 | grep -A 20 "Import trace"
Sinais de alerta no Import trace:
| Prefixo | Significado | Ação |
|---|---|---|
Edge Middleware: | Bundle do middleware/Edge | Remover dependências Node.js |
Server Component: | Server Component (OK para Node.js) | Nenhuma ação necessária |
Client Component: | Bundle do browser | Remover dependências server-side |
Route Handler: | API Route (OK para Node.js) | Nenhuma ação necessária |
O erro original como caso pedagógico:
Import trace: ← LEIA ISSO PRIMEIRO
Edge Middleware: ← CONTEXTO DE EXECUÇÃO - Edge Runtime
.../@prisma/client/default.js ← MÓDULO INCOMPATÍVEL
./src/lib/db-client.ts ← ORIGEM DA IMPORTAÇÃO
./src/lib/auth.ts ← INTERMEDIÁRIO
./middleware.ts ← ARQUIVO QUE IMPORTOU A CADEIA
A cadeia se lê de baixo para cima: middleware.ts → auth.ts → db-client.ts → @prisma/client. Cada elo da corrente é um candidato ao desacoplamento.
Fluxo de decisão: resolução do prisma no next.js#
Tabela de comparação de capacidades: runtimes next.js#
| Capacidade / API | Node.js Runtime | Edge Runtime | Alternativa Recomendada no Edge |
|---|---|---|---|
fs (File System) | ✅ Disponível | ❌ Ausente | Utilizar armazenamento em nuvem/S3 |
child_process | ✅ Disponível | ❌ Ausente | Utilizar microsserviços / API externa |
net (TCP Sockets) | ✅ Disponível | ❌ Ausente | Consultas HTTP via fetch |
Módulos nativos .node | ✅ Disponível | ❌ Ausente | Compilações em WASM compatíveis |
| Web APIs (fetch, crypto) | ✅ Disponível | ✅ Disponível | - |
| Limite de tamanho de bundle | ~50MB | ~1MB - 4MB | Otimizar imports / Tree shaking |
| Tempo de inicialização (Cold Start) | ~100-500ms | ~1-5ms | - |
Testes e homologação de correções#
Antes de enviar as correções para produção, certifique-se de validar a conformidade através do roteiro abaixo.
Roteiro de testes do middleware via cURL#
Use os comandos abaixo em seu terminal local para validar a resposta do servidor:
# 1. Tentar acessar rota protegida (/dashboard) sem fornecer cookies
curl -I http://localhost:3000/dashboard
# Saída esperada: Status 302 Found com cabeçalho Location apontando para /login
# 2. Tentar acessar com um token inválido ou modificado
curl -I -H "Cookie: auth-token=token_invalido" http://localhost:3000/dashboard
# Saída esperada: Status 302 Found redirecionando para /login (e limpando o cookie)
# 3. Tentar acessar com um token válido e assinado
# Nota: Você pode gerar um JWT temporário contendo { "sub": "123", "role": "admin" }
curl -I -H "Cookie: auth-token=SEU_JWT_VALIDO" http://localhost:3000/dashboard
# Saída esperada: Status 200 OK
Checklist de homologação pós-build#
Execute estas verificações para confirmar que a correção foi aplicada na integridade do projeto:
# 1. Verificar se a pasta do artefato do Prisma Client existe e contém os drivers
ls -la node_modules/.prisma/client/
# Deve exibir os arquivos default.js, edge.js, index.js, etc.
# 2. Iniciar o servidor de desenvolvimento para certificar o bootstrap sem panics
pnpm dev 2>&1 | head -20
# 3. Ler logs de depuração do servidor Next.js em caso de falhas
tail -n 50 .next/server/server-app.log 2>/dev/null
Este artigo foi útil?
Deixe uma reação rápida para apoiar o conteúdo:
Este post está licenciado sob CC BY-NC.



Comentários
Participe da discussão abaixo.
0 comentários