Lançar uma app Next.js com o Cursor, o v0 ou o Bolt é rápido, mas a velocidade esconde falhas que não vês até alguém as encontrar por ti. Esta checklist percorre o que há a rever antes de carregares em deploy, com o porquê de cada ponto, como o verificar e o código para o corrigir. No fim tens a versão de dois minutos.
Como o Next.js pensa a fronteira entre servidor e cliente
Quase todas as falhas de segurança no Next.js são a mesma falha: algo que devia ficar no servidor atravessou para o browser. Os Server Components executam no servidor e nunca enviam o seu código para o cliente; assim que um ficheiro leva 'use client', tudo o que importa acaba no bundle público. Os Route Handlers (app/api/*/route.ts) e as Server Actions são URLs públicos mesmo que não os vejas. O middleware corre antes de tudo, mas não é uma muralha. Com essa fronteira clara, o resto da lista é mecânica.
1. Variáveis de ambiente: cuidado com NEXT_PUBLIC
Porque importa. Tudo o que começa por NEXT_PUBLIC_ é embutido no JavaScript do browser no momento do build. Qualquer pessoa o lê com as ferramentas de programador. Nunca ponhas aí uma chave de API privada, um token de serviço nem o segredo da tua base de dados.
Como verificar. Abre o teu .env e o painel da Vercel e lê cada variável com prefixo público a perguntar-te: acontecia alguma coisa se a publicasse no Twitter? Se a resposta é sim, tira-lhe o prefixo e passa-a para o servidor.
Como corrigir. As variáveis sem prefixo só existem no servidor: lê-as a partir de Server Components, Route Handlers ou Server Actions. Para que um módulo com segredos nunca possa ser importado a partir de um componente de cliente, acrescenta import 'server-only' na primeira linha: se alguém o importar do lado errado, o build falha em vez de deixar fugir a chave. Mais detalhe em os segredos que a tua app deixa fugir no browser.
2. Route Handlers e Server Actions: valida e autoriza sempre
Porque importa. Uma rota de API não é privada por estar na tua pasta. É um URL público que qualquer pessoa pode chamar com curl. E uma Server Action, mesmo que seja invocada a partir de um botão, também: o Next.js expõe-a como um endpoint POST com um identificador. Um botão que não aparece a um utilizador não impede que alguém bata no endpoint diretamente.
Como corrigir. Verifica a sessão e as permissões dentro de cada handler e de cada action, não só na interface. Valida o corpo do pedido com um esquema (zod ou semelhante) antes de tocar na base de dados. Nunca aceites o identificador do utilizador vindo do cliente: tira-o da sessão. E põe um limite de pedidos ao que custe dinheiro ou envie emails.
// app/actions/update-profile.ts
'use server'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
const Schema = z.object({ name: z.string().min(1).max(80) })
export async function updateProfile(input: unknown) {
const session = await getSession()
if (!session) throw new Error('Unauthorized')
const data = Schema.parse(input)
// usa session.user.id, nunca um id que venha do cliente
await db.user.update({ where: { id: session.user.id }, data })
}
3. O middleware não é a tua única autenticação
Porque importa. É tentador proteger todo o /dashboard a partir do middleware.ts e esquecer o assunto. Em março de 2025 foi publicada a CVE-2025-29927: um header interno (x-middleware-subrequest) permitia saltar o middleware por completo nas versões afetadas, e com ele qualquer verificação de sessão que vivesse só ali. Foi corrigida no Next.js 15.2.3 e 14.2.25 (com patches para 13 e 12), mas a lição fica.
Como corrigir. Atualiza o Next.js para uma versão corrigida e usa o middleware como primeira camada (redirecionar quem não tem sessão), nunca como a única. A verificação que protege mesmo os dados vai na página, no handler ou na action que os serve.
4. Headers de segurança
Porque importa. Por defeito o Next.js não envia headers defensivos. Sem eles a tua app fica exposta a clickjacking, a que o browser execute como script o que não o é e a downgrade para HTTP.
Como corrigir. No next.config.js:
// next.config.js
const securityHeaders = [
{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
]
module.exports = {
poweredByHeader: false,
productionBrowserSourceMaps: false,
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }]
},
}
A Content-Security-Policy merece tratamento à parte: o Next.js injeta scripts inline para hidratar a página, por isso uma CSP rigorosa precisa de um nonce gerado no middleware e passado aos componentes. Começa em modo Report-Only. O detalhe de cada header está em os headers de segurança explicados para humanos.
5. Segredos que atravessam para Client Components
Porque importa. É fácil uma chave privada acabar num componente de cliente por engano, sobretudo quando a IA reescreve ficheiros e move imports. Se aparece no JavaScript servido, já está comprometida.
Como verificar. Depois de next build, procura em .next/static strings como sk_live, service_role, sk-ant ou URLs de webhooks. Se aparecerem, roda-as e passa-as para o servidor com import 'server-only' como seguro.
6. Imagens remotas e pedidos a URLs do utilizador
Porque importa. O next/image otimiza imagens através do teu servidor. Se em images.remotePatterns permitires qualquer host, qualquer pessoa pode usar o teu servidor como proxy de imagens e gastar a tua quota. O mesmo com uma rota de API que descarrega o URL que o utilizador lhe passa: pode apontar para serviços internos da tua infraestrutura (SSRF).
Como corrigir. Limita remotePatterns aos domínios que usas mesmo. Se uma rota tiver de descarregar URLs externos, valida o protocolo (só https), rejeita IPs privados e nomes internos, e põe um tempo máximo.
7. Redirecionamentos abertos
Porque importa. O clássico redirect(searchParams.next) depois do login permite construir links do teu domínio que acabam num site de phishing: oteudominio.com/login?next=https://mau.example.
Como corrigir. Aceita só caminhos relativos que comecem por / (e não por //), ou compara com uma lista de destinos permitidos.
8. Ficheiros que não deviam estar acessíveis
Porque importa. Tudo o que está em public/ é servido tal e qual: um .env copiado para lá por engano, um dump da base de dados ou um service-account.json ficam à distância de um URL. E os source maps em produção permitem ler o teu código de cliente com os nomes originais.
Como verificar. Experimenta oteudominio.com/.env, /.git/HEAD e abre um ficheiro JS do bundle para ver se termina numa linha sourceMappingURL a apontar para um .map acessível. Olha para a pasta public/ com os olhos de um desconhecido.
9. Cookies de sessão e bibliotecas de autenticação
Porque importa. O Auth.js (NextAuth), o Clerk ou o Supabase com o pacote para servidor configuram bem as cookies por defeito. O que falha é o que acrescentas tu: uma cookie própria sem HttpOnly, um AUTH_SECRET fraco ou copiado de um tutorial, ou a sessão guardada em localStorage.
Como corrigir. Gera o segredo com openssl rand -base64 32, guarda-o sem prefixo público, e para qualquer cookie própria usa Secure, HttpOnly e SameSite=Lax.
10. Dependências e a versão do Next.js
Porque importa. A tua app arrasta centenas de pacotes, e o próprio Next.js publica correções de segurança várias vezes por ano. O caso do middleware de 2025 afetou quem não atualizava.
Como corrigir. npm audit antes de cada publicação, Dependabot ou Renovate no repositório, e uma regra simples: atualizar o Next.js no mesmo mês em que sai um patch de segurança.
Publicação na Vercel: três detalhes
- Ambientes. As variáveis têm âmbito (Production, Preview, Development). Não dês às publicações de preview as credenciais de produção: um preview é um URL público com o teu código mais recente.
- Proteção dos previews. Ativa a Deployment Protection para que os URLs de preview exijam login da Vercel; senão, qualquer pessoa com o link vê o branch em curso.
- Headers. Os do
next.config.jsaplicam-se na Vercel sem mais; se também usaresvercel.json, não dupliques.
A tua checklist de 2 minutos
- Nenhuma variável
NEXT_PUBLIC_contém um segredo; os módulos com chaves levamimport 'server-only'. - Cada Route Handler e Server Action verifica a sessão e valida a entrada; o id do utilizador vem da sessão.
- Next.js numa versão corrigida; o middleware não é a única camada de auth.
- Headers de segurança no
next.config.js;poweredByHeader: false; CSP pelo menos em modo de relatório. - Bundle sem
sk_live,service_rolenem webhooks; source maps desativados. remotePatternslimitados; nada descarrega URLs do utilizador sem validar.- Redirecionamentos só para caminhos relativos.
public/sem ficheiros sensíveis;.enve.gitinacessíveis.- Cookies com Secure, HttpOnly e SameSite;
AUTH_SECRETaleatório. npm auditlimpo; previews protegidos e sem segredos de produção.
Verifica tudo isto em 30 segundos
Rever esta lista à mão em cada publicação é aborrecido e esquece-se. O Scanaris faz verificações só de leitura sobre o teu site publicado: headers, segredos expostos no JavaScript, ficheiros sensíveis acessíveis, source maps, cookies e CORS. Cada resultado chega com um prompt pronto a colar no Cursor ou no v0 com a correção exata para Next.js. Se o deixares em monitorização, avisa-te quando uma sessão de vibe coding voltar a abrir algo. Para aprofundar na tua stack, há a página de segurança para Next.js e a checklist geral de lançamento.
Perguntas frequentes
As Server Actions são seguras por defeito?
São mais cómodas do que uma rota de API, não mais seguras. O Next.js expõe-as como endpoints públicos; a autenticação e a validação pões-las tu dentro de cada uma.
O middleware protege as minhas rotas?
Serve para redirecionar e para verificações baratas, mas não deve ser a única barreira. Depois do caso de 2025, a regra é verificar a sessão também onde os dados são servidos.
Preciso de CSP se uso Next.js?
Sim, mas com nonce, e a começar em modo de relatório. Sem nonce, uma CSP rigorosa bloqueia os scripts inline da própria framework e a página fica em branco.