Middleware
Middleware executa antes de cada requisição correspondente, permitindo redirecionamentos, reescritas, modificações de cabeçalho e verificações de autenticação na edge.
Busque em todas as páginas da documentação
Middleware executa antes de cada requisição correspondente, permitindo redirecionamentos, reescritas, modificações de cabeçalho e verificações de autenticação na edge.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
// middleware.ts (raiz do projeto, mesmo nível de app/)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// Redireciona, reescreve ou modifica cabeçalhos
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/api/:path*"],
};Quando usar: Guardas de autenticação, detecção de idioma, testes A/B, redirecionamentos e qualquer lógica em nível de requisição que precise ser executada antes da renderização.
// middleware.ts - Guarda de autenticação com detecção de idioma
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
const protectedPaths = ["/dashboard", "/settings", "/billing"];
const defaultLocale = "en";
const supportedLocales = ["en", "fr", "de", "es"];
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// 1. Detecção de idioma - redireciona se não houver prefixo de idioma
const pathnameHasLocale = supportedLocales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);
if (!pathnameHasLocale) {
const locale =
request.headers.get("accept-language")?.split(",")[0]?.split("-")[0] ?? defaultLocale;
const detectedLocale = supportedLocales.includes(locale) ? locale : defaultLocale;
return NextResponse.redirect(
new URL(`/${detectedLocale}${pathname}`, request.url)
);
}
// 2. Verificação de autenticação - redireciona usuários não autenticados
const isProtected = protectedPaths.some((path) =>
pathname.includes(path)
);
const token = request.cookies.get("session-token")?.value;
if (isProtected && !token) {
const loginUrl = new URL("/en/login", request.url);
loginUrl.searchParams.set("callbackUrl", pathname);
return NextResponse.redirect(loginUrl);
}
// 3. Adiciona cabeçalhos personalizados
const response = NextResponse.next();
response.headers.set("x-pathname", pathname);
return response;
}
export const config = {
matcher: [
// Corresponde a todos os caminhos, exceto arquivos estáticos e internos do Next.js
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};// middleware.ts - Mapa simples de redirecionamentos
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
const redirects: Record<string, string> = {
"/old-blog": "/blog",
"/docs/v1": "/docs",
"/legacy-api": "/api/v2",
};
export function middleware(request: NextRequest) {
const redirect = redirects[request.nextUrl.pathname];
if (redirect) {
return NextResponse.redirect(new URL(redirect, request.url), 301);
}
return NextResponse.next();
}
export const config = {
matcher: ["/old-blog", "/docs/v1", "/legacy-api"],
};middleware.ts por projeto. Ele deve estar na raiz (ao lado de app/ ou src/). Você não pode ter arquivos de middleware por rota.matcher filtra quais rotas acionam o middleware. Sem um matcher, o middleware executa em todas as requisições, incluindo ativos estáticos.NextResponse.next() continua para a rota correspondente. Você pode modificar cabeçalhos de requisição/resposta ao passar.NextResponse.redirect(url) envia uma resposta de redirecionamento (302 por padrão, passe 301 para permanente).NextResponse.rewrite(url) serve o conteúdo de outra rota sem alterar a URL do navegador.fs, path e a maioria dos pacotes npm não estão disponíveis. Use apenas APIs da Web.request.cookies.get() e response.cookies.set() para gerenciamento de sessão.// Teste A/B baseado em reescrita
export function middleware(request: NextRequest) {
const bucket = request.cookies.get("ab-bucket")?.value;
const response = NextResponse.next();
if (!bucket) {
const newBucket = Math.random() > 0.5 ? "a" : "b";
response.cookies.set("ab-bucket", newBucket, { maxAge: 60 * 60 * 24 * 30 });
}
const currentBucket = bucket ?? "a";
if (request.nextUrl.pathname === "/pricing") {
return NextResponse.rewrite(
new URL(`/pricing/${currentBucket}`, request.url)
);
}
return response;
}// Limitação de taxa com cabeçalhos
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("/api/")) {
const ip = request.headers.get("x-forwarded-for") ?? "unknown";
// Verifica limite de taxa (usando armazenamento externo como Upstash Redis)
// Isto é pseudocódigo - use @upstash/ratelimit em produção
const response = NextResponse.next();
response.headers.set("X-RateLimit-Limit", "100");
return response;
}
return NextResponse.next();
}// Padrões de matcher
export const config = {
matcher: [
"/dashboard/:path*", // /dashboard e todos os sub-caminhos
"/api/:path*", // Todas as rotas de API
"/blog/:slug", // Segmento dinâmico único
"/((?!_next|static|api).*)", // Tudo exceto _next, static, api
],
};import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
// Assinatura da função de middleware
export function middleware(request: NextRequest): NextResponse | Response | undefined;
// NextRequest estende a API Web Request
// Propriedades chave:
// request.nextUrl - URL analisada com pathname, searchParams
// request.cookies - Instância de RequestCookies
// request.headers - objeto Headers padrão
// request.geo - { city, country, region } (apenas Vercel)
// request.ip - IP do cliente (apenas Vercel)
// Tipo de configuração
export const config: {
matcher: string | string[];
};request.nextUrl.pathname dentro de uma única função de middleware.bcrypt, fs ou drivers de banco de dados não funcionarão - use alternativas compatíveis com edge.matcher. Sempre configure um matcher para evitar execução desnecessária./login para /en/login e o middleware também corresponde a /en/login, você obtém um loop. Exclua os caminhos de destino do matcher.NextResponse.next() não interrompe a execução. Outra lógica de middleware após ele ainda é executada. Retorne antecipadamente se necessário.cookies() de next/headers.| Abordagem | Quando Usar |
|---|---|
next.config.js redirects | Regras de redirecionamento estáticas que não precisam de lógica em tempo de execução |
next.config.js rewrites | Regras de reescrita estáticas sem lógica condicional |
redirect() em nível de rota | Redirecionar dentro de um Server Component após verificações de dados |
| Server Actions com autenticação | Proteger mutações em vez de rotas |
| Guardas de autenticação em nível de layout | Verificar autenticação em um layout e redirecionar |
De uma aplicação SaaS Next.js 15 / React 19 em produção (SystemsArchitect.io).
// Exemplo de produção: Middleware com reescritas, redirecionamentos, atualização de sessão e proteção de administrador
// Arquivo: middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { updateSession } from '@/utils/supabase/middleware';
const legacyRedirects: Record<string, string> = {
'/old-docs': '/docs',
'/blog/legacy-post': '/articles',
'/pricing-old': '/pricing',
};
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// 1. Reescrita de docs: serve /docs/[section] de uma rota interna diferente
if (pathname.startsWith('/docs/')) {
const url = request.nextUrl.clone();
url.pathname = `/docs-internal${pathname.replace('/docs', '')}`;
return NextResponse.rewrite(url);
}
// 2. Redirecionamentos legados (permanente 301)
const redirect = legacyRedirects[pathname];
if (redirect) {
return NextResponse.redirect(new URL(redirect, request.url), 301);
}
// 3. Atualização de sessão - mantém a sessão de autenticação Supabase ativa
const response = await updateSession(request);
// 4. Proteção de rota de administrador
if (pathname.startsWith('/admin')) {
const session = request.cookies.get('sb-access-token')?.value;
if (!session) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
return response;
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
};O que isso demonstra em produção:
301 nos redirecionamentos legados informa aos mecanismos de busca que a mudança é permanente. Usar o 302 padrão preservaria a URL antiga nos índices de pesquisa.updateSession(request) é uma utilidade do Supabase que atualiza o cookie de sessão de autenticação a cada requisição. Isso evita que a sessão expire enquanto o usuário está navegando ativamente.matcher /((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*) exclui ativos estáticos e imagens do processamento do middleware. Sem isso, o middleware seria executado em cada requisição de imagem e arquivo CSS, adicionando latência desnecessária.middleware.ts por projeto. Toda a lógica específica da rota deve ser tratada com verificações condicionais em pathname.Na raiz do projeto, ao lado do diretório app/ ou src/. Existe apenas um arquivo middleware.ts por projeto. Você não pode ter arquivos de middleware por rota.
fs, path e a maioria dos pacotes npm não estão disponíveisO middleware executa em todas as requisições, incluindo ativos estáticos como imagens, CSS e arquivos JavaScript. Sempre configure um matcher para evitar execução desnecessária.
Se você redireciona /login para /en/login e o middleware também corresponde a /en/login, você obtém um loop infinito. Exclua os caminhos de destino do matcher ou adicione verificações condicionais para pular caminhos já processados.
redirect() envia uma resposta de redirecionamento (302 por padrão, 301 para permanente) e altera a URL do navegadorrewrite() serve o conteúdo de uma rota diferente sem alterar a URL do navegadorexport function middleware(request: NextRequest) {
// Lê um cookie
const token = request.cookies.get("session")?.value;
// Define um cookie na resposta
const response = NextResponse.next();
response.cookies.set("visited", "true", { maxAge: 3600 });
return response;
}Sim. Cookies definidos no middleware estão disponíveis via cookies() de next/headers em Server Components.
Use verificações condicionais em request.nextUrl.pathname dentro da função de middleware, já que você só pode ter um arquivo de middleware.
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname.startsWith("/api/")) { /* Lógica da API */ }
if (pathname.startsWith("/admin")) { /* Lógica do admin */ }
return NextResponse.next();
}import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(
request: NextRequest
): NextResponse | Response | undefined;
export const config: {
matcher: string | string[];
};request.nextUrl -- URL analisada com pathname e searchParamsrequest.cookies -- Instância de RequestCookiesrequest.headers -- objeto Headers padrãorequest.geo -- cidade, país, região (apenas Vercel)request.ip -- IP do cliente (apenas Vercel)Não. O código após NextResponse.next() ainda é executado. Se você quiser parar o processamento, deve explicitamente return a resposta.
next.config.js para regras estáticas que não precisam de lógica em tempo de execuçãoredirect() e navegação programáticamatcher para segmentos dinâmicosRevisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥