Inicialização bloqueada no `pnpm dev`
Voltar para blog

Inicialização bloqueada no `pnpm dev`

07/06/2026 · 8 min · Desenvolvimento

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:

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:

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:

  1. Lê o schema.prisma e valida a sintaxe
  2. Introspecciona os tipos e relações definidos nos models
  3. Compila o Query Engine binário específico para o OS/arquitetura atual (.so.node no Linux, .dll no Windows, .dylib no macOS)
  4. Gera os tipos TypeScript para todos os models (PrismaClient, User, Post, etc.)
  5. 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:

CapacidadeNode.js RuntimeEdge 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:

  1. Depende do Query Engine binário - um arquivo .so.node ou .dll compilado nativamente que usa child_process para comunicação via IPC com o processo Prisma
  2. Usa TCP sockets via net para conectar ao banco de dados
  3. Acessa o filesystem para ler o schema e o certificado SSL do banco
  4. 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:

Variáveis de ambiente no edge runtime#

No Edge Runtime do Next.js:

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:

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:

PrefixoSignificadoAção
Edge Middleware:Bundle do middleware/EdgeRemover dependências Node.js
Server Component:Server Component (OK para Node.js)Nenhuma ação necessária
Client Component:Bundle do browserRemover 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#

flowchart TD A[Erro: Module not found Prisma] --> B{Import trace mostra Edge?} B -->|Sim| C[Prisma incompatível com Edge Runtime] B -->|Não| D[Prisma Client não gerado] D --> E[Executar prisma generate] E --> F[Adicionar postinstall script] C --> G{Requer dados do banco no middleware?} G -->|Não| H[Usar JWT/jose no Edge] G -->|Sim| I{Tipo de consulta?} I -->|Simples| J[Prisma Accelerate ou Neon HTTP] I -->|Complexa| K[Reavaliar arquitetura] H --> L[Middleware sem dependências Node.js] J --> M[Client Edge com driver HTTP] K --> N[Mover lógica para Server Components] style C fill:#7f1d1d,stroke:#ef4444,color:#fff style H fill:#1e293b,stroke:#10b981,color:#fff style J fill:#1e293b,stroke:#f59e0b,color:#fff

Tabela de comparação de capacidades: runtimes next.js#

Capacidade / APINode.js RuntimeEdge RuntimeAlternativa Recomendada no Edge
fs (File System)✅ Disponível❌ AusenteUtilizar armazenamento em nuvem/S3
child_process✅ Disponível❌ AusenteUtilizar microsserviços / API externa
net (TCP Sockets)✅ Disponível❌ AusenteConsultas HTTP via fetch
Módulos nativos .node✅ Disponível❌ AusenteCompilações em WASM compatíveis
Web APIs (fetch, crypto)✅ Disponível✅ Disponível-
Limite de tamanho de bundle~50MB~1MB - 4MBOtimizar 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:

CC BY-NC

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários