Next.js
Redirects, rewrites y headers
Compara redirects, rewrites y headers en configuración, Proxy y runtime, junto con canonicales, seguridad, caché y migraciones de URL.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Next.js
Compara redirects, rewrites y headers en configuración, Proxy y runtime, junto con canonicales, seguridad, caché y migraciones de URL.
Redirects, rewrites y headers actúan en el borde HTTP, pero producen resultados diferentes. Un redirect indica al cliente que utilice otra URL; un rewrite conserva la URL pública y resuelve otro destino interno; un header añade metadata o políticas a la request o response. Elegir mal afecta SEO, caché, seguridad, analítica y navegación.
redirect
request /old
→ response 307/308 Location: /new
→ browser requests /new
→ visible URL changes
rewrite
request /public
→ Next.js internally resolves /internal
→ response represents internal destination
→ visible URL remains /public
header
request or response
→ metadata/policy added
→ destination may remain unchangedAdecuado para reglas conocidas al construir la aplicación:
Estas reglas se evalúan antes del routing normal y no deberían consultar una base de datos.
Adecuado cuando la decisión depende del request:
redirect(), permanentRedirect(), NextResponse o Response permiten decidir después de cargar datos o validar una operación.
La frontera mínima reduce complejidad: una redirect de slug canónico pertenece a la page o DAL, no a una lista global difícil de mantener.
Configuración:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
async redirects() {
return [
{
source: "/old-pricing",
destination: "/pricing",
permanent: true,
},
];
},
};
export default nextConfig;Next.js utiliza redirects que preservan el método: normalmente 307 temporal y 308 permanente. Esto evita que un POST se convierta accidentalmente en GET, un problema histórico de 301/302 en algunos clientes.
Úsalo cuando la URL antigua dejó de ser canónica:
Navegadores, caches y buscadores pueden conservarla durante mucho tiempo. No la uses para sesiones, feature flags ni mantenimiento temporal.
Úsalo cuando la decisión puede revertirse:
import { permanentRedirect } from "next/navigation";
export default async function ProductPage({
params,
}: PageProps<"/products/[slug]">) {
const { slug } = await params;
const product = await resolveProductSlug(slug);
if (!product) notFound();
if (product.slug !== slug) {
permanentRedirect(`/products/${product.slug}`);
}
return <ProductDetails product={product} />;
}Aquí la decisión necesita datos, por eso pertenece al runtime y no al config.
redirect() y permanentRedirect() interrumpen el flujo mediante una excepción interna; no las captures en un catch genérico.
Código peligroso:
redirect(searchParams.next);Un atacante puede construir una URL hacia un dominio malicioso o un esquema no seguro.
Valida destinos internos:
function safeInternalPath(value: unknown): string {
if (typeof value !== "string") return "/dashboard";
if (!value.startsWith("/") || value.startsWith("//")) return "/dashboard";
return value;
}Para destinos externos usa allowlist de protocolo y host.
const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: "/legacy/:path*",
destination: "https://legacy.example.com/:path*",
},
];
},
};El navegador conserva /legacy/..., pero Next.js sirve el destino externo.
Cuando el usuario y los buscadores deben conocer la URL nueva. Un rewrite mantendría dos URLs aparentes o una canonical incoherente.
Objeto avanzado:
async rewrites() {
return {
beforeFiles: [],
afterFiles: [],
fallback: [],
};
}beforeFiles: antes de filesystem y puede sobrescribir rutas.afterFiles: después de rutas estáticas, antes de dynamic matches finales.fallback: último intento antes de 404; útil para migraciones.Una regla demasiado amplia puede capturar assets, endpoints o rutas que debían ser 404.
/a → rewrite /b
/b → rewrite /aO redirects entre dominios que se devuelven mutuamente. Prueba todas las combinaciones de trailing slash, locale, host y protocolo.
Con rewrite:
async headers() {
return [
{
source: "/:path*",
headers: [
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
],
},
];
}Son response headers aplicados por patrón. Úsalos para políticas estáticas.
Reduce XSS limitando fuentes de scripts, estilos, imágenes, conexiones y frames. Una política estricta puede requerir nonce generado por request, por lo que puede volver dinámica la respuesta y pertenece a Proxy/runtime.
No empieces con una CSP copiada y bloquees scripts críticos. Despliega primero Content-Security-Policy-Report-Only, observa y endurece.
Strict-Transport-Security: max-age=31536000; includeSubDomainsObliga HTTPS después de la primera visita. includeSubDomains y preload pueden romper subdominios no preparados; solo habilítalos cuando todo el dominio esté listo.
Prefiere CSP frame-ancestors. X-Frame-Options puede mantenerse por compatibilidad. Decide si la app puede ser embebida por socios.
Limita cámara, micrófono, geolocalización y otras capacidades. No deshabilites features que una ruta realmente necesita.
Next.js administra headers de assets inmutables y muchas respuestas del framework. No intentes sobrescribirlos globalmente sin entender la capa.
Para un Route Handler público puedes definir:
return Response.json(data, {
headers: {
"Cache-Control": "public, s-maxage=300, stale-while-revalidate=3600",
},
});Una respuesta privada debería usar private o no-store según la semántica. Vary debe incluir cualquier header que realmente cambia la representación, pero cardinalidad amplia destruye el cache hit ratio.
En Proxy:
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-request-id", requestId);
const response = NextResponse.next({
request: { headers: requestHeaders },
});
response.headers.set("x-request-id", requestId);
return response;El primero llega a la app; el segundo llega al cliente.
No reenvíes headers internos al navegador accidentalmente. Sobrescribe cualquier x-user-id enviado por el cliente.
/nicolas-garzon.vercel.app/projects/domisys
→ permanent redirect https://nicoo.dev/portfolio/domisys
/legacy-blog/:slug
→ rewrite backend antiguo mientras migra contenidoEl redirect consolida dominio/canonical. El rewrite conserva una ruta pública mientras el servicio detrás cambia.
Location.El navegador nunca aprende la canonical nueva.
Puede quedar cacheado y bloquear usuarios autenticados.
Open redirect.
Debilita la protección.
Filtra o impide caché de assets.
El sitio depende silenciosamente de otro servicio.
next sin validar?Cookies y headers explica cómo leer request context, escribir respuestas y proteger sesiones sin romper streaming o caché.