El sitio anda perfecto en localhost. Lo subís a producción y aparecen los problemas que no viste en dev: Google indexa páginas sin título, las imágenes del hero pesan 1,5 MB, el preview de WhatsApp sale genérico y nadie sabe por qué la home tarda tanto. No es mala suerte: es la diferencia entre una app que compila y un sitio configurado para producción.
La configuración de producción en Next.js es una checklist, y en este artículo la recorremos completa: metadata y sitemap, imágenes, headers, variables de entorno, bundle y monitoreo, con cada fragmento listo para copiar. Son los ajustes que aplicamos en Gambit Lab en cada sitio que sale a producción.
Metadata, sitemap y robots: que Google entienda tu sitio
Un sitio sin metadata es un sitio invisible: Google no sabe qué título mostrar, las redes muestran un preview genérico y el SEO arranca perdiendo. En el App Router de Next.js todo se declara desde el layout raíz, con un solo export const metadata:
// app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
metadataBase: new URL('https://tusitio.com'),
title: {
default: 'Nombre de tu empresa',
template: '%s | Nombre de tu empresa',
},
description: 'Descripción de tu sitio en menos de 160 caracteres.',
openGraph: {
type: 'website',
locale: 'es_AR',
url: 'https://tusitio.com',
siteName: 'Nombre de tu empresa',
images: ['/og-image.webp'],
},
robots: { index: true, follow: true },
};metadataBase es el que casi nadie configura y el que rompe los Open Graph con URLs relativas. El template hace que cada página herede el nombre de tu empresa sin repetirlo a mano.
Después, sitemap y robots: dos archivos en app/:
// app/sitemap.ts
import type { MetadataRoute } from 'next';
export default function sitemap(): MetadataRoute.Sitemap {
return [
{ url: 'https://tusitio.com', lastModified: new Date(), changeFrequency: 'weekly', priority: 1 },
{ url: 'https://tusitio.com/servicios', lastModified: new Date(), changeFrequency: 'monthly', priority: 0.8 },
];
}// app/robots.ts
import type { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: { userAgent: '*', allow: '/' },
sitemap: 'https://tusitio.com/sitemap.xml',
};
}Si tu sitio genera páginas (un blog, un catálogo), el sitemap puede construirse con fetch o desde la base de datos dentro de la misma función. Eso evita el sitemap desactualizado que Google termina ignorando.
next/image en producción: priority y sizes para no arruinar el LCP
next/image ya resuelve lo básico: convierte a WebP/AVIF, sirve el tamaño correcto para cada pantalla y difiere la carga fuera de la vista. Pero en producción hay dos props que hacen la diferencia entre un LCP bueno y uno malo: priority y sizes.
El componente <Image> no se optimiza solo si no le decís cuál es la imagen que manda en el ranking de rendimiento:
import Image from 'next/image';
// El hero es el LCP: priority + sizes correctos
<Image
src="/hero.jpg"
alt="Descripción real de la imagen"
fill
priority
sizes="(min-width: 1024px) 50vw, 100vw"
quality={80}
/>priority carga la imagen apenas arranca el documento, sin esperar a que entre en viewport (el lazy loading por defecto es justo lo que mata al LCP si se aplica al hero). sizes le dice al navegador qué ancho reservar para elegir el archivo correcto. Sin sizes, una imagen fill con contenedor ancho puede servir el mismo archivo a un celular.
Poner priority en todas las imágenes de la home anula el propósito: el navegador descarga todo de una y el LCP mejora el de una imagen a costa del resto. La regla es una sola imagen con priority por vista, la del LCP.
Headers de cache y seguridad: la configuración que nadie ve
Next.js aplica defaults razonables, pero producción exige dos cosas que vienen desactivadas: caché larga para assets estáticos y headers de seguridad. Todo se declara en next.config.ts:
// next.config.ts
const securityHeaders = [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
];
const nextConfig = {
async headers() {
return [
{
source: '/images/:path*',
headers: [{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }],
},
{
source: '/(.*)',
headers: securityHeaders,
},
];
},
};
export default nextConfig;El immutable en imágenes y fuentes evita que el navegador las vuelva a pedir en cada visita: el usuario vuelve y la página carga desde caché local. Los headers de seguridad bloquean ataques básicos: clickjacking, MIME sniffing y el robo de referrer en links externos.
El Content-Security-Policy es el header más potente y el más fácil de romper (bloquea scripts inline, estilos, fuentes). Arrancá con una política básica y revisá la consola del navegador antes de ir a la versión estricta: un CSP que rompe el sitio en producción es peor que no tenerlo.
Variables de entorno, bundle y monitoreo: cerrar el círculo
Tres frentes que no se ven hasta que el sitio está en el aire. Primero, las variables de entorno: en Next.js, solo las que llevan el prefijo NEXT_PUBLIC_ quedan expuestas al navegador. Las que no lo llevan viven solo en el servidor — esa separación protege tus claves de API.
# .env.production
DATABASE_URL=postgres://usuario:pass@host:5432/db # solo servidor
NEXT_PUBLIC_ANALYTICS_ID=G-XXXXXXX # visible en el bundleRegla simple: lo que empiece con NEXT_PUBLIC_ asumí que lo ve cualquiera que abra las DevTools.
Segundo, el bundle. next build te muestra el tamaño de cada chunk, pero para ver qué módulo pesa de más necesitás el analyzer:
pnpm add -D @next/bundle-analyzer// next.config.ts
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
export default withBundleAnalyzer(nextConfig);Con ANALYZE=true pnpm run build se abre un reporte interactivo de cada bundle y encontrás el lodash que coló un componente o el chart que nadie importó donde debía. Tercero, el monitoreo: los errores en producción no se ven en tu máquina. Un servicio como Sentry (o el monitor de Vercel si hosteás ahí) te avisa del primer crash antes que el cliente. Diez minutos de setup, y es lo que separa “el sitio se cayó” de “el sitio se cayó y ya sabemos por qué”.
La configuración de producción no es glamorosa, pero es la diferencia entre un sitio que anda y uno que anda bien: metadata con metadataBase, sitemap y robots, imágenes con priority y sizes, headers de caché y seguridad, variables públicas separadas de las privadas y monitoreo activo. Si querés ver cómo estas decisiones se miden en el sitio real, tenés la guía completa de Core Web Vitals y rendimiento web, y si te interesa por qué un desarrollo a medida con Next.js termina con mejor configuración que una plantilla, este análisis compara ambos caminos.
