TechLead

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',
  };
}

📖 Documentación de Despliegue →

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