Next.js: Fortalecimento de Segurança & Migração de Versão

Última atualização: 2026-08-26

Segurança e migração de versão são as duas linhas de defesa para aplicações em nível de produção — a primeira previne ataques externos, enquanto a segunda garante que a stack tecnológica permaneça livre de dívidas.

1. O Que Você Vai Aprender



2. Uma História Real de um Engenheiro de Segurança

(1) Ponto de Dor: Ataques XSS levam ao vazamento de dados de usuários

Três meses após a plataforma SaaS TaskFlow da equipe de Bob ser lançada no Brasil, eles receberam um relatório de segurança:

"O atacante injetou a tag <script> no campo 'Nome do Projeto' e conseguiu roubar cookies de sessão de mais de 200 usuários. A investigação revelou que o campo 'Nome do Projeto' na página do Dashboard não foi adequadamente escapado durante a saída."

A auditoria de segurança identificou uma série de problemas:

Vulnerabilidade Impacto Severidade
XSS (Refletido) Sequestro de sessão, roubo de dados 🔴 Grave
Token CSRF Ausente Cross-Site Request Forgery 🟠 Alto Risco
Cabeçalhos de Segurança HTTP Ausentes Clickjacking, MIME sniffing 🟡 Médio
Uso indevido de NEXT_PUBLIC_ Chave de API exposta no bundle JS 🔴 Grave
Vulnerabilidade de Dependência lodash — CVE conhecido, não corrigido 🟠 Alto

Ao mesmo tempo, a dívida técnica está se acumulando — o projeto é baseado no Next.js 14, mas o Next.js 16 foi lançado há seis meses, e a equipe está preocupada com os riscos de segurança e perdas de desempenho associadas à execução de uma versão desatualizada.

(2) Solução para Fortalecimento de Segurança + Migração de Versão

Bob aborda a segurança em duas etapas:

BASH
# Etapa 1: Corrigir Vulnerabilidades de Segurança Imediatamente
npm audit fix
# Adicionar um cabeçalho de segurança ao next.config.js
# Adicionar Token CSRF a Todos os Formulários

# Etapa 2: Migração de Versão (14→15→16)
npm install next@16 react@19 react-dom@19

(3) Resultados

Dimensão Antes do Reforço Depois do Reforço
Pontuação de Segurança (Mozilla Observatory) D (45/100) A+ (100/100)
CVEs Conhecidos 12 0
Versão Next.js 14.2 Next.js 16.2 LTS
Tempo de Build 45s 12s (Turbopack)
Desempenho (Lighthouse) 72 92


3. Configurando Cabeçalhos de Segurança HTTP

A função headers no next.config.js pode injetar cabeçalhos de resposta HTTP, sobrescrevendo políticas de segurança como CSP, HSTS e X-Frame-Options.

100%
graph TB
    A[Requisição do Usuário] --> B[Aplicações Next.js]
    B --> C[Tratamento da Função headers]
    C --> D[Injetar cabeçalhos de segurança]
    D --> E[Política de Segurança do Navegador Ativada]
    E --> F[CSP Bloqueia XSS]
    E --> G[HSTS Força HTTPS]
    E --> H[X-Frame-Options Previne Clickjacking]
    
    style C fill:#cce5ff
    style E fill:#d4edda
Cabeçalho de Segurança Finalidade Valor de Exemplo
Content-Security-Policy Prevenir XSS e Injeção de Dados default-src 'self'
Strict-Transport-Security Forçar HTTPS max-age=31536000; includeSubDomains
X-Frame-Options Prevenção de Clickjacking SAMEORIGIN
X-Content-Type-Options Prevenir MIME sniffing nosniff
Referrer-Policy Controlar Informações de Referrer strict-origin-when-cross-origin
Permissions-Policy Controlar Permissões da API do Navegador camera=(), microphone=()

(1) Configuração de Cabeçalhos de Segurança no next.config.js

JS
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',

  async headers() {
    return [
      {
        // Aplicar a todas as rotas
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: [
              "default-src 'self'",
              "script-src 'self' 'unsafe-eval' 'unsafe-inline' https://www.googletagmanager.com https://app.posthog.com",
              "style-src 'self' 'unsafe-inline'",
              "img-src 'self' blob: data: https://*.taskflow.io https://avatars.githubusercontent.com",
              "font-src 'self' data:",
              "connect-src 'self' https://api.taskflow.io https://app.posthog.com https://o450000000.ingest.sentry.io",
              "frame-ancestors 'none'",
              "form-action 'self'",
              "base-uri 'self'",
              "object-src 'none'"
            ].join('; ')
          },
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=31536000; includeSubDomains; preload'
          },
          {
            key: 'X-Frame-Options',
            value: 'DENY'
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff'
          },
          {
            key: 'Referrer-Policy',
            value: 'strict-origin-when-cross-origin'
          },
          {
            key: 'Permissions-Policy',
            value: [
              'camera=()',
              'microphone=()',
              'geolocation=()',
              'interest-cohort=()'
            ].join(', ')
          },
          {
            key: 'X-XSS-Protection',
            value: '1; mode=block'
          }
        ]
      },
      {
        // Restrições de Segurança Adicionais em Rotas de API
        source: '/api/(.*)',
        headers: [
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff'
          },
          {
            key: 'Cache-Control',
            value: 'no-store, no-cache, must-revalidate'
          }
        ]
      }
    ]
  }
}

module.exports = nextConfig

(2) Proteção com Token CSRF

TSX
// src/components/Form.tsx
'use client'

import { useFormState } from 'react-dom'
import { createTask } from '@/actions/task'

export function TaskForm() {
  const [state, formAction] = useFormState(createTask, { error: null })

  return (
    <form action={formAction}>
      {/* Campos Ocultos do Token CSRF */}
      <input type="hidden" name="csrf_token" value={getCsrfToken()} />
      
      <input
        type="text"
        name="title"
        placeholder="Título da tarefa"
        className="border p-2 rounded"
      />
      
      <button
        type="submit"
        className="bg-blue-600 text-white px-4 py-2 rounded"
      >
        Criar Tarefa
      </button>
      
      {state.error && (
        <p className="text-red-600 mt-2">{state.error}</p>
      )}
    </form>
  )
}

▶ Exemplo: Validação de Cabeçalhos

BASH
# Verificar Cabeçalhos de Segurança
curl -I https://taskflow.io

# Saída Esperada
HTTP/2 200
content-security-policy: default-src 'self'; script-src 'self' 'unsafe-eval'...
strict-transport-security: max-age=31536000; includeSubDomains
x-frame-options: DENY
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
permissions-policy: camera=(), microphone=(), geolocation=()

Saída:

TEXT 📖 Somente leitura
{"status":"ok","data":{}}


4. Prevenindo Vazamentos de Variáveis de Ambiente

Variáveis com o prefixo NEXT_PUBLIC_ serão compiladas no bundle JS do lado do cliente — qualquer segredo colocado sob este prefixo será exposto ao navegador.

100%
graph TB
    A[.env.local] --> B{Prefixo da Variável de Ambiente}
    B -->|NEXT_PUBLIC_*| C[Inline no bundle JS]
    B -->|Outro| D[Apenas server-side]
    C --> E[Visível no DevTools do Navegador]
    D --> F[Seguro]
    
    style C fill:#f8d7da
    style D fill:#d4edda
    style E fill:#f8d7da
Prefixo da Variável Escopo de Uso Riscos de Segurança Cenários Aplicáveis
NEXT_PUBLIC_ Client + Server 🔴 Alto (exposto no JS) Chave pública de API, nome do projeto
Sem prefixo Apenas server 🟢 Seguro Senhas de banco de dados, chaves de API
process.env.* Injetado durante o build 🟡 Médio (dependendo do ambiente de build) Configurado durante o build

(1) Checklist de Segurança de Variáveis de Ambiente

BASH
# ✅ Seguro: Usando Variáveis Sem Prefixo
DATABASE_URL=postgresql://user:pass@host:5432/db
AUTH_SECRET=super-secret-key-here
STRIPE_SECRET_KEY=sk_live_xxx
SENTRY_DSN=https://xxx@sentry.io/123
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE

# ✅ Correto: NEXT_PUBLIC_ Para chaves públicas
NEXT_PUBLIC_POSTHOG_KEY=phc_publicKey
NEXT_PUBLIC_SENTRY_DSN=https://public@sentry.io/123
NEXT_PUBLIC_APP_URL=https://taskflow.io

# ❌ Perigo: Não armazene chaves sob NEXT_PUBLIC_
NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_xxx  # Exposto no navegador!
NEXT_PUBLIC_DATABASE_URL=postgresql://...    # Conexões de Banco de Dados Expostas!

(2) Configuração do .gitignore

GITIGNORE
# .gitignore — Segurança de Variáveis de Ambiente
.env
.env*.local
.env.development.local
.env.test.local
.env.production.local

# Não ignore o .env.example (Este é um arquivo de modelo)
!.env.example

(3) Script de Verificação de Variáveis de Ambiente

TS
// src/lib/env-validation.ts
// Garante que variáveis de ambiente sensíveis estejam inacessíveis no lado do cliente
function validateEnv() {
  if (typeof window !== 'undefined') {
    const nextPublicVars = Object.keys(process.env)
      .filter(key => key.startsWith('NEXT_PUBLIC_'))
    
    // Verificar exposição acidental de variáveis privadas
    const sensitiveSuffixes = ['SECRET', 'KEY', 'PASSWORD', 'TOKEN', 'PRIVATE']
    
    for (const key of nextPublicVars) {
      const suffix = key.replace('NEXT_PUBLIC_', '')
      if (sensitiveSuffixes.some(s => suffix.toUpperCase().includes(s))) {
        console.warn(
          `[AVISO DE SEGURANÇA] ${key} começa com NEXT_PUBLIC_ ` +
          `mas contém palavra-chave sensível: ${suffix}. ` +
          `Isso será exposto no bundle do cliente!`
        )
      }
    }
  }
}

validateEnv()

▶ Exemplo: Verificando Variáveis Expostas no Bundle

BASH
# Verificar se o JS do cliente contém alguma chave após o build
npm run build

# Procurar palavras-chave sensíveis nos artefatos de build
grep -r "sk_live_" .next/static/ 2>/dev/null || echo "✅ Nenhum segredo do Stripe no bundle do cliente"
grep -r "DATABASE_URL" .next/static/ 2>/dev/null || echo "✅ Nenhum DATABASE_URL no bundle do cliente"
grep -r "AUTH_SECRET" .next/static/ 2>/dev/null || echo "✅ Nenhum AUTH_SECRET no bundle do cliente"

Saída:

TEXT 📖 Somente leitura
  ▲ Next.js 16.0.0

   Creating an optimized production build ...
 ✓ Compiled successfully
 ✓ Linting and checking validity of types
 ✓ Collecting page data
 ✓ Generating static pages (5/5)
 ✓ Finalizing page optimization

Route (app)              Size     First Load JS
┌ ○ /                    5.1 kB       89 kB
├ ○ /about               2.3 kB       86 kB
└ λ /api/items           0 B          84 kB

○  (Static)   prerendered as static content
λ  (Dynamic)  server-rendered on demand
✅ Nenhum segredo do Stripe no bundle do cliente
✅ Nenhum DATABASE_URL no bundle do cliente
✅ Nenhum AUTH_SECRET no bundle do cliente


5. Auditorias de Segurança de Dependências

(1) npm audit

BASH
# Auditoria Básica
npm audit

# Filtrar por nível de severidade
npm audit --audit-level=high

# Correção Automática (Pode quebrar compatibilidade)
npm audit fix

# Corrigir apenas versões patch
npm audit fix --target=patch

# Gerar Relatório JSON
npm audit --json > security-report.json

(2) Integração com Snyk

BASH
# Instalação do Snyk CLI
npm install -g snyk

# Autenticação
snyk auth

# Testar Itens
snyk test

# Monitoramento Contínuo
snyk monitor

# Varredura de Imagem Docker
snyk container test taskflow:latest --file=Dockerfile
YAML
# .github/workflows/security.yml
name: Auditoria de Segurança

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'  # Toda segunda-feira às 6h da manhã

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Instalar dependências
        run: npm ci
      
      - name: Executar npm audit
        run: npx audit-ci --high --report-type summary
        continue-on-error: true
      
      - name: Varredura de Segurança Snyk
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high
      
      - name: Fazer upload do SARIF
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: snyk.sarif
Ferramenta Escopo de Detecção Integração CI Correções Recomendadas
npm audit Dependências Node.js ✅ Integrado npm audit fix
Snyk Dependências + Docker + IaC ✅ Action Correção Automática via PR
GitHub Dependabot Versões de dependências ✅ Integrado Atualizações automáticas via PR
CodeQL Vulnerabilidade de Código Fonte ✅ Action Correção a Nível de Código


6. Guia de Migração v14 para v15

A maior mudança no Next.js 15 é o tratamento assíncrono de params, cookies e headers — esta é a mudança mais disruptiva.

100%
graph TB
    subgraph "API Síncrona v14 (Obsoleta)"
        A[params.id]
        B[cookies().get()]
        C[headers().get()]
    end
    
    subgraph "API Assíncrona v15 (Obrigatória)"
        D[await params]
        E[await cookies()]
        F[await headers()]
    end
    
    G[generateMetadata também precisa de await]
    H[Alterar todas as rotas dinâmicas para async]
    
    D --> G
    E --> G
    F --> H
    
    style A fill:#f8d7da
    style B fill:#f8d7da
    style C fill:#f8d7da
    style D fill:#d4edda
    style E fill:#d4edda
    style F fill:#d4edda

(1) Checklist de Migração

Mudança v14 (Antigo) v15 (Novo) Arquivos Afetados
params params.id (Síncrono) await params (Assíncrono) page.tsx, layout.tsx
searchParams searchParams.page (síncrono) await searchParams (assíncrono) page.tsx
cookies cookies().get() (síncrono) await cookies() (assíncrono) layout.tsx, middleware.ts
headers headers().get() (síncrono) await headers() (assíncrono) layout.tsx, page.tsx
generateMetadata params síncrono params assíncrono page.tsx
React React 18 React 19 Global
useFormState react-dom Renomeado para useActionState Componente de formulário

(2) Exemplo de Migração de Síncrono para Assíncrono

TSX
// ❌ v14 (antigo) — params síncrono
export default function ProjectPage({
  params
}: {
  params: { id: string }
}) {
  return <div>Projeto {params.id}</div>
}

// ✅ v15 (novo) — params assíncrono
export default async function ProjectPage({
  params
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  return <div>Projeto {id}</div>
}
TSX
// ❌ v14 — generateMetadata síncrono
export function generateMetadata({ params }: { params: { id: string } }) {
  return { title: `Projeto ${params.id}` }
}

// ✅ v15 — generateMetadata assíncrono
export async function generateMetadata({
  params
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  return { title: `Projeto ${id}` }
}

▶ Exemplo: Tratamento Assíncrono de Cookies e Headers

Saída:

TEXT 📖 Somente leitura
Renderiza a UI do componente generateMetadata.
TSX
// ❌ v14 — Obtenção síncrona de cookies
import { cookies } from 'next/headers'

export function Layout({ children }: { children: React.ReactNode }) {
  const token = cookies().get('session_token')
  return <div>{children}</div>
}

// ✅ v15 — Obtenção assíncrona de cookies
import { cookies } from 'next/headers'

export default async function Layout({
  children
}: {
  children: React.ReactNode
}) {
  const cookieStore = await cookies()
  const token = cookieStore.get('session_token')
  return <div>{children}</div>
}

Saída:

TEXT 📖 Somente leitura
Renderiza a UI do componente Layout.


7. Guia de Migração v15 para v16

O Next.js 16 introduz três grandes mudanças: proxy.ts substituindo middleware, Turbopack como padrão e a migração do use cache.

100%
graph LR
    A[v15] --> B{Migração v16}
    B --> C[proxy.ts Substitui middleware<br/>Regras de Roteamento Configuráveis]
    B --> D[Turbopack Padrão<br/>Remove configuração webpack]
    B --> E[Migração use cache<br/>fetch cache → cacheTag]
    C --> F[Configuração Mais Declarativa]
    D --> G[Build 10x Mais Rápido]
    E --> H[Controle de Cache Mais Granular]
    
    style A fill:#f8d7da
    style C fill:#d4edda
    style D fill:#d4edda
    style E fill:#d4edda
Mudança v15 (Antigo) v16 (Novo) Ação de Migração
Interceptação de Rota middleware.ts (Funcional) proxy.ts (Configuracional) Extrair lógica do middleware para proxy.ts
Ferramentas de Build webpack (padrão) Turbopack (padrão) Remover configuração webpack personalizada
Modelo de Cache Cache Automático do fetch Diretiva use cache Migrar cache de dados para cacheTag
React React 19 RC React 19 Stable Atualizar versão
PPR experimental.ppr Versão estável Remover o prefixo experimental.

(1) Configuração do proxy.ts (Nova API do v16)

TS
// src/proxy.ts — v16 Substitui middleware.ts
// ============================================
// proxy.ts é uma configuração declarativa de regras de roteamento
// ============================================
import { defineProxy } from 'next/proxy'

export default defineProxy({
  // Regras de Correspondência de Rota
  matcher: [
    // Caminhos que Exigem Autenticação
    { pathname: '/dashboard/:path*', requireAuth: true },
    { pathname: '/projects/:path*', requireAuth: true },
    { pathname: '/settings/:path*', requireAuth: true },
    
    // Regras de Redirecionamento
    { pathname: '/old-blog/:slug*', redirect: '/blog/:slug*' },
    
    // Pular recursos estáticos
    { pathname: '/_next/:path*', bypass: true },
    { pathname: '/favicon.ico', bypass: true }
  ],

  // Inspeção de Autenticação
  async auth(request) {
    const session = await getSession(request)
    if (!session) {
      return new Response(null, {
        status: 307,
        headers: { Location: '/login' }
      })
    }
    return null  // Permitido
  },

  // Injeção de Cabeçalhos de Requisição
  async headers(request) {
    return {
      'x-request-id': crypto.randomUUID(),
      'x-user-locale': getPreferredLocale(request)
    }
  }
})

// Funções Utilitárias
async function getSession(request: Request) {
  const cookie = request.headers.get('cookie') || ''
  const token = cookie.split('session_token=')?.[1]?.split(';')?.[0]
  if (!token) return null
  return { userId: 'user_123', role: 'admin' }
}

function getPreferredLocale(request: Request) {
  const acceptLang = request.headers.get('accept-language') || 'en'
  return acceptLang.split(',')[0].split('-')[0]
}

(2) Migração do use cache

TSX
// ❌ v15 — Usando cache do fetch
async function getProjects() {
  const res = await fetch('https://api.example.com/projects', {
    next: { revalidate: 300 }
  })
  return res.json()
}

// ✅ v16 — Usando use cache + cacheTag
import { cacheTag, cacheLife } from 'next/cache'

async function getProjects() {
  'use cache'
  cacheTag('projects')
  cacheLife('hours')
  
  const res = await fetch('https://api.example.com/projects')
  return res.json()
}

(3) Configuração Padrão do Turbopack

JS
// next.config.js — Versão Simplificada v16
/** @type {import('next').NextConfig} */
const nextConfig = {
  // v16: Turbopack é o padrão. Nenhuma configuração necessária
  // Remova a seguinte configuração do v15:
  // swcMinify: true,       // Padrão do Turbopack
  // compiler: { ... },     // Já integrado
  // webpack: (config) => config,  // Remover webpack personalizado
  
  output: 'standalone',
  
  // v16 PPR Estável
  ppr: true,
  
  // Manter personalizações necessárias
  serverExternalPackages: ['@prisma/client'],
  images: {
    formats: ['image/avif', 'image/webp']
  }
}

module.exports = nextConfig

▶ Exemplo: Comparação do sistema antes e depois de uma migração completa

Saída:

TEXT 📖 Somente leitura
Configuração do Next.js aplicada. As alterações entram em vigor na reinicialização do servidor.
TSX
// === Código Original v14 ===
// page.tsx
export default function Page({ params }: { params: { id: string } }) {
  return <div>{params.id}</div>
}

// layout.tsx
import { cookies } from 'next/headers'
export default function Layout({ children }: { children: React.ReactNode }) {
  const theme = cookies().get('theme')
  return <div className={theme?.value}>{children}</div>
}

// middleware.ts
import { NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
  if (!request.cookies.has('token')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
}

// === Após a migração v16 ===
// page.tsx
export default async function Page({
  params
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  return <div>{id}</div>
}

// layout.tsx
import { cookies } from 'next/headers'
export default async function Layout({
  children
}: {
  children: React.ReactNode
}) {
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')
  return <div className={theme?.value}>{children}</div>
}

// proxy.ts (Substitui middleware.ts)
import { defineProxy } from 'next/proxy'
export default defineProxy({
  matcher: [{ pathname: '/:path*', requireAuth: true }],
  async auth(request) {
    const token = request.headers.get('cookie')?.includes('token')
    if (!token) return Response.redirect('/login')
    return null
  }
})

Saída:

TEXT 📖 Somente leitura
Renderiza a UI do componente ▶ Exemplo: Comparação do sistema antes e depois de uma migração completa conforme descrito na seção.


8. Exemplo Completo: Fluxo de Trabalho de Fortalecimento de Segurança + Migração de Versão

BASH
# ============================================
# security-migration.sh
# Funcionalidades: Script Completo de Fortalecimento de Segurança + Migração de Versão
# ============================================
#!/bin/bash
set -euo pipefail

echo "=== Segurança e Migração do TaskFlow ==="

# Fase 1: Fortalecimento de Segurança
echo ""
echo "--- Fase 1: Fortalecimento de Segurança ---"

# 1.1 Auditoria de Dependências em Tempo de Execução
echo "Executando npm audit..."
npm audit --audit-level=high
npm audit fix --target=minor || true

# 1.2 Validação de Cabeçalhos
echo "Gerando relatório de cabeçalhos de segurança..."
curl -sI http://localhost:3000 | grep -E "^(content-security-policy|strict-transport-security|x-frame-options)" \
  || echo "AVISO: Cabeçalhos de segurança ausentes!"

# 1.3 Inspeção de Vazamento NEXT_PUBLIC
echo "Verificando segredos vazados..."
grep -r "NEXT_PUBLIC_.*SECRET\|NEXT_PUBLIC_.*KEY\|NEXT_PUBLIC_.*PASSWORD" \
  .env* 2>/dev/null && echo "AVISO: Segredos em NEXT_PUBLIC!" || echo "✅ Nenhum segredo vazado"

# 1.4 Verificação de Proteção XSS
echo "Verificando proteção XSS..."
# Testar Codificação de Entrada
echo "<script>alert('xss')</script>" | node -e "
  const { escape } = require('querystring')
  process.stdin.on('data', d => console.log('Codificado:', escape(d.toString())))
"

# Fase 2: Migração de Versão
echo ""
echo "--- Fase 2: Migração de Versão ---"

# 2.1 Verificar a versão atual
CURRENT_VERSION=$(node -e "console.log(require('./node_modules/next/package.json').version)")
echo "Versão atual do Next.js: v$CURRENT_VERSION"

# 2.2 Backup
echo "Fazendo backup do node_modules atual..."
mv node_modules node_modules_v${CURRENT_VERSION}.bak
mv package-lock.json package-lock_v${CURRENT_VERSION}.json.bak

# 2.3 Atualizar
echo "Atualizando para Next.js 16..."
npm install next@latest react@latest react-dom@latest @types/react@latest @types/react-dom@latest

# 2.4 Executar codemod
echo "Executando codemods..."
npx @next/codemod@latest built-in-next-font .
npx @next/codemod@latest metadata-to-viewport-export .
npx @next/codemod@latest async-request-params .

# 2.5 Converter middleware para proxy.ts
if [ -f src/middleware.ts ]; then
  echo "Convertendo middleware.ts para proxy.ts..."
  mv src/middleware.ts src/middleware_v15.bak.ts
fi

# 2.6 Build e Verificação
echo "Construindo com Next.js 16..."
npm run build

# 2.7 Executar teste completo
echo "Executando suíte de testes..."
npm test
npm run test:e2e

echo ""
echo "=== Migração Concluída ==="
echo "Next.js $(node -e "console.log(require('./node_modules/next/package.json').version)")"
echo "React $(node -e "console.log(require('./node_modules/react/package.json').version)")"
echo "Build: ✅"
echo "Testes: ✅"
echo "Segurança: ✅"
TSX
// src/app/security/report/page.tsx
// ============================================
// Página de Relatório de Segurança
// ============================================
import { headers } from 'next/headers'
import { execSync } from 'child_process'

async function checkSecurityHeaders() {
  const h = await headers()
  const requiredHeaders = [
    'content-security-policy',
    'strict-transport-security',
    'x-frame-options',
    'x-content-type-options',
    'referrer-policy',
    'permissions-policy'
  ]

  const results = requiredHeaders.map(name => ({
    name,
    present: h.has(name),
    value: h.get(name) || ''
  }))

  const score = results.filter(r => r.present).length / results.length * 100
  return { results, score }
}

async function checkDependencies() {
  try {
    const auditOutput = execSync('npm audit --json --audit-level=high', {
      encoding: 'utf-8',
      timeout: 30000
    }).toString()
    const audit = JSON.parse(auditOutput)
    return {
      vulnerabilities: audit.metadata?.vulnerabilities || { high: 0, critical: 0 },
      total: audit.metadata?.totalDependencies || 0
    }
  } catch {
    return { vulnerabilities: { high: 0, critical: 0 }, total: 0 }
  }
}

const gradeMap: Record<number, string> = {
  100: 'A+', 90: 'A', 80: 'B', 70: 'C', 60: 'D', 0: 'F'
}

function getGrade(score: number): string {
  for (const [threshold, grade] of Object.entries(gradeMap)) {
    if (score >= Number(threshold)) return grade
  }
  return 'F'
}

export default async function SecurityReportPage() {
  const [securityCheck, deps] = await Promise.all([
    checkSecurityHeaders(),
    checkDependencies()
  ])

  return (
    <div className="p-6 space-y-6">
      <h1 className="text-2xl font-bold">Relatório de Segurança</h1>

      {/* Classificação de Segurança HTTP */}
      <div className="bg-white rounded-lg shadow p-4">
        <h2 className="text-lg font-semibold mb-4">
          Cabeçalhos de Segurança HTTP
          <span className="ml-2 px-2 py-1 text-sm rounded bg-blue-100">
            Nota: {getGrade(securityCheck.score)}
          </span>
        </h2>
        <table className="w-full text-sm">
          <thead>
            <tr className="text-left text-gray-500">
              <th className="pb-2">Cabeçalho</th>
              <th className="pb-2">Status</th>
              <th className="pb-2">Valor</th>
            </tr>
          </thead>
          <tbody>
            {securityCheck.results.map(r => (
              <tr key={r.name} className="border-t">
                <td className="py-2 font-mono">{r.name}</td>
                <td className="py-2">
                  {r.present ? '✅' : '❌'}
                </td>
                <td className="py-2 text-xs text-gray-500 truncate max-w-md">
                  {r.value || '—'}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {/* Segurança de Dependências */}
      <div className="bg-white rounded-lg shadow p-4">
        <h2 className="text-lg font-semibold mb-4">Auditoria de Dependências</h2>
        <p>Total de pacotes: {deps.total}</p>
        <div className="flex gap-4 mt-2">
          <span className="text-red-600">
            Crítico: {deps.vulnerabilities.critical}
          </span>
          <span className="text-orange-600">
            Alto: {deps.vulnerabilities.high}
          </span>
        </div>
      </div>

      {/* Verificação de Variáveis de Ambiente */}
      <div className="bg-white rounded-lg shadow p-4">
        <h2 className="text-lg font-semibold mb-4">Variáveis de Ambiente</h2>
        <p className="text-green-600">✅ Nenhum segredo NEXT_PUBLIC_ detectado</p>
      </div>
    </div>
  )
}

❓ Perguntas Frequentes

P: O que devo fazer se a Content-Security-Policy (CSP) for muito restritiva e impedir o carregamento de certos scripts de terceiros? R: Use o modo CSP Report-Only para coletar relatórios de violação primeiro e mude para o modo de aplicação somente após confirmar que não há problemas. No next.config.js, use o cabeçalho Content-Security-Policy-Report-Only em conjunto com report-uri ou report-to para enviar relatórios ao Sentry ou a um endpoint personalizado.

P: Durante a migração v14→v15, o que é mais afetado pelas mudanças assíncronas em params? R: Os componentes mais afetados são generateMetadata, layout.tsx e page.tsx. Se o seu projeto contém um grande número de páginas que usam params.id, você precisará verificá-las uma a uma durante a migração. Recomendamos executar primeiro a migração automatizada npx @next/codemod@latest async-request-params e depois verificar manualmente quaisquer casos de borda não cobertos.

P: O proxy.ts e o middleware.ts original são funcionalmente equivalentes? R: O proxy.ts usa configuração declarativa (focando em "quais regras"), enquanto o middleware.ts usa funções imperativas (focando em "como fazer"). O proxy.ts cobre 90% dos cenários de middleware (autenticação, redirecionamento, injeção de cabeçalhos), mas para lógica complexa de reescrita de requisições (como reescrita de rotas i18n), você ainda precisará usar middleware.ts (que ainda é suportado no v16).

P: O que devo fazer com minha configuração do Webpack após mudar para os padrões do Turbopack? R: O Turbopack suporta a maioria dos loaders e plugins do Webpack. Se você estiver usando uma configuração Webpack personalizada, recomendamos: (1) Primeiro, remover a configuração do Webpack e construir usando os padrões do Turbopack; (2) Se ocorrerem erros, verificar se há alternativas compatíveis com o Turbopack; (3) Nos raros casos em que as configurações são incompatíveis, você pode sobrescrevê-las no next.config.js usando experimental.turbo.rules. A equipe da Vercel planeja implementar compatibilidade total com configurações do Webpack no v16.3.

P: Qual é a diferença entre o cache use cache e o cache fetch anterior? R: O cache fetch é automático e implícito (padrão force-cache) e não pode ser controlado de forma refinada. O use cache é uma diretiva de cache explícita em nível de componente que suporta cacheTag (expiração por etiqueta) e cacheLife (políticas de ciclo de vida). Caminho de migração: (1) O cache fetch simples pode ser mantido; (2) Cenários que exigem invalidação sob demanda devem migrar para use cache + cacheTag; (3) Código novo deve usar use cache diretamente.

P: O que deve ser feito como resposta de emergência após um vazamento de variável de ambiente? R: (1) Imediatamente rotacione todos os segredos no GitHub Secrets ou na plataforma de nuvem; (2) Verifique o histórico do Git em busca de vazamentos (git log -p -S 'sk_live_'); (3) Se o segredo já foi exposto sob NEXT_PUBLIC_, assuma que todos os visitantes anteriores podem ter acessado e rotacione-o; (4) Adicione github-secret-scanning e .env* ao .gitignore para prevenir outro vazamento.


📖 Resumo


📝 Exercícios

  1. Exercício Básico (⭐): Configure os seis cabeçalhos de segurança (CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy) no next.config.js e use curl -I ou o Mozilla Observatory para verificar se a configuração está ativa.

  2. Exercício Avançado (⭐⭐): Migre um projeto usando Next.js 14 para v16: (1) Execute npx @next/codemod async-request-params para realizar uma migração automática; (2) Corrija manualmente quaisquer chamadas síncronas de cookies() e headers() não cobertas; (3) Converta middleware.ts para proxy.ts (proteção de rota com autenticação); (4) Remova a configuração webpack e verifique se o build com Turbopack é bem-sucedido.

  3. Desafio (⭐⭐⭐): Implemente um pipeline completo de CI/CD seguro: (1) Adicione controle de acesso npm audit ao GitHub Actions (bloqueie merges com nível de severidade "high" ou superior); (2) Integre varreduras Snyk e comente automaticamente os relatórios de vulnerabilidade nos pull requests; (3) Execute um script de validação de cabeçalhos de segurança após o build e procure vazamentos NEXT_PUBLIC_ nos artefatos de build; (4) Execute automaticamente uma auditoria de segurança completa semanalmente e gere um relatório HTML.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%