Despliegue y Optimización
Despliega en Vercel, optimiza imágenes, fuentes y mejora Core Web Vitals
Desplegando Next.js
Next.js puede desplegarse en cualquier plataforma de hosting que soporte Node.js, o como un sitio estático. Vercel (los creadores de Next.js) ofrece la experiencia de despliegue más fluida con configuración cero.
🚀 Desplegar en Vercel
El CLI de Vercel puede desplegar instantáneamente, mientras que los despliegues basados en Git proporcionan vistas previas automáticas y builds de producción en cada push.
# Instalar Vercel CLI
npm i -g vercel
# Desplegar (desde la raíz del proyecto)
vercel
# Desplegar a producción
vercel --prod
# O conectar a Git para despliegues automáticos
# 1. Push a GitHub/GitLab/Bitbucket
# 2. Importar proyecto en vercel.com/new
# 3. ¡Cada push despliega automáticamente!
Optimización de Imágenes
El componente next/image maneja dimensionamiento responsive, lazy loading y formatos modernos
automáticamente, mejorando el rendimiento y Core Web Vitals.
import Image from 'next/image';
// Uso básico
export default function Hero() {
return (
<Image
src="/hero.jpg"
alt="Imagen hero"
width={1200}
height={600}
priority // Cargar inmediatamente (above the fold)
/>
);
}
// Imágenes responsive
function ResponsiveImage() {
return (
<Image
src="/photo.jpg"
alt="Foto"
fill // Llenar contenedor padre
sizes="(max-width: 768px) 100vw, 50vw"
style={{ objectFit: 'cover' }}
/>
);
}
// Imágenes externas (configurar en next.config.js)
// next.config.js
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com',
},
{
protocol: 'https',
hostname: '*.amazonaws.com',
},
],
},
};
// Placeholder blur
<Image
src="/large-image.jpg"
alt="Imagen grande"
width={800}
height={400}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..." // O usar importación estática
/>
Optimización de Fuentes
Usa next/font para auto-hospedar fuentes con optimización automática y evitar cambios de layout.
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google';
// Cargar Google Fonts
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
const robotoMono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-roboto-mono',
});
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html
lang="es"
className={`${inter.variable} ${robotoMono.variable}`}
>
<body className={inter.className}>{children}</body>
</html>
);
}
// Usar en Tailwind CSS
// tailwind.config.js
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)'],
mono: ['var(--font-roboto-mono)'],
},
},
},
};
// Fuentes locales
import localFont from 'next/font/local';
const myFont = localFont({
src: './fonts/MyFont.woff2',
display: 'swap',
});
Metadata y SEO
Next.js te permite definir metadata estática en layouts y metadata dinámica por página, además de agregar datos estructurados para resultados de búsqueda más ricos.
// app/layout.tsx - Metadata global
import { Metadata } from 'next';
export const metadata: Metadata = {
title: {
template: '%s | Mi Sitio',
default: 'Mi Sitio',
},
description: 'Bienvenido a mi sitio web',
metadataBase: new URL('https://misitio.com'),
openGraph: {
title: 'Mi Sitio',
description: 'Bienvenido a mi sitio web',
images: ['/og-image.jpg'],
},
twitter: {
card: 'summary_large_image',
},
robots: {
index: true,
follow: true,
},
};
// app/blog/[slug]/page.tsx - Metadata dinámica
import { Metadata } from 'next';
interface Props {
params: Promise<{ slug: string }>;
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const post = await getPost(slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
};
}
// Datos estructurados JSON-LD
export default function Page() {
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: 'Mi Artículo',
author: { '@type': 'Person', name: 'Juan Pérez' },
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>...</article>
</>
);
}
Optimización de Rendimiento
Combina ajustes de configuración con importaciones dinámicas y code splitting para mantener bundles pequeños y páginas rápidas.
// next.config.js
const nextConfig = {
// Analizador de bundle
experimental: {
optimizePackageImports: ['lucide-react', '@heroicons/react'],
},
// Compresión
compress: true,
// Source maps de producción (opcional)
productionBrowserSourceMaps: false,
};
// Importaciones dinámicas para code splitting
import dynamic from 'next/dynamic';
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <p>Cargando...</p>,
ssr: false, // Solo del lado del cliente
});
// Lazy load contenido below-the-fold
const Comments = dynamic(() => import('./Comments'));
export default function Post() {
return (
<article>
<h1>Título del Post</h1>
<p>Contenido...</p>
{/* Solo carga cuando se hace scroll a la vista */}
<Suspense fallback={<CommentsSkeleton />}>
<Comments />
</Suspense>
</article>
);
}
Sitemap y Robots
Usa Metadata Routes para generar un sitemap y reglas de robots dinámicamente, manteniendo metadata SEO en sincronía con el contenido.
// app/sitemap.ts
import { MetadataRoute } from 'next';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getPosts();
const blogUrls = posts.map(post => ({
url: `https://misitio.com/blog/${post.slug}`,
lastModified: post.updatedAt,
changeFrequency: 'weekly' as const,
priority: 0.8,
}));
return [
{
url: 'https://misitio.com',
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 1,
},
{
url: 'https://misitio.com/about',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.5,
},
...blogUrls,
];
}
// app/robots.ts
import { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: ['/admin/', '/api/'],
},
],
sitemap: 'https://misitio.com/sitemap.xml',
};
}
Otras Opciones de Hosting
Puedes desplegar con Docker para características completas del servidor o exportar un sitio estático para cualquier host estático, dependiendo de tus necesidades de runtime.
# Despliegue con Docker
# Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
# Exportación estática (sin características de servidor)
# next.config.js
const nextConfig = {
output: 'export', // Genera HTML estático
};
# Build y desplegar a cualquier host estático
npm run build
# Subir carpeta 'out' a Netlify, GitHub Pages, etc.
✅ Checklist de Despliegue
- ☑️ Usar componente Image para todas las imágenes
- ☑️ Optimizar fuentes con next/font
- ☑️ Agregar metadata a todas las páginas
- ☑️ Generar sitemap y robots.txt
- ☑️ Habilitar compresión
- ☑️ Usar importaciones dinámicas para componentes grandes
- ☑️ Probar Core Web Vitals con Lighthouse
- ☑️ Configurar variables de entorno