nextjs-optimizacion-seo-imagenes

SEO, imágenes y fuentes en Next.JS

  • 5 min

Hacer que una aplicación funcione es una parte del trabajo. Hacer que cargue rápido y que Google entienda qué hay dentro es la siguiente.

En las SPAs tradicionales (Vite), el SEO era una pesadilla. Teníamos que usar librerías como react-helmet para intentar inyectar etiquetas en el <head> dinámicamente, y aun así, muchos crawlers (robots de búsqueda) veían la página en blanco antes de que ejecutara el JavaScript.

Next.js soluciona esto de raíz. Al renderizar en servidor, el HTML que viaja por la red ya lleva toda la información semántica.

Además, nos ofrece herramientas nativas para optimizar los dos recursos más pesados de la web: Imágenes y Fuentes.

Metadata API

Next.js nos ofrece una API tipada para definir el título, la descripción, las imágenes de Open Graph (Twitter/Facebook) y más.

En cualquier layout.tsx o page.tsx, podemos exportar un objeto constante llamado metadata.

// src/app/layout.tsx
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: {
    template: '%s | Mi Empresa',
    default: 'Mi Empresa - Soluciones Tech', // Título por defecto
  },
  description: 'La mejor aplicación desarrollada con Next.js',
  openGraph: {
    title: 'Mi Empresa',
    description: 'La mejor aplicación...',
    images: ['/og-image.jpg'],
  },
};
Copied!

La propiedad template es genial: si en una página hija definís title: 'Contacto', el título final será “Contacto | Mi Empresa”.

¿Qué pasa en la página de un producto /productos/[id]? No podemos poner un título fijo. Necesitamos el nombre del producto que viene de la base de datos.

Para esto usamos la función generateMetadata.

// src/app/productos/[id]/page.tsx
import { Metadata } from 'next';
import db from '@/lib/db';

type Props = {
  params: Promise<{ id: string }>
};

// Esta función se ejecuta ANTES que el componente de la página
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { id } = await params;

  // 1. Buscamos los datos
  const product = await db.product.findUnique({ where: { id } });

  // 2. Devolvemos los metadatos personalizados
  return {
    title: product?.name,
    description: product?.description,
    openGraph: {
      images: [product?.imageUrl || ''],
    },
  };
}

export default function Page({ params }: Props) {
  // ... renderizado normal ...
}
Copied!

Next.js deduplica automáticamente las peticiones fetch. Si llamáis a la API del producto en generateMetadata y luego otra vez en el componente Page, Next.js solo hace una petición real a la base de datos/API.

Componente <Image />

Las imágenes son las principales culpables de que la web vaya lenta. Si usáis la etiqueta estándar <img src="foto-gigante.jpg" />, estáis obligando al usuario a descargar el archivo original (que puede pesar 5MB) aunque lo esté viendo en un móvil pequeño.

Además, las imágenes causan Cumulative Layout Shift (CLS): el texto salta de repente cuando la imagen termina de cargar y empuja el contenido hacia abajo. Google penaliza esto severamente.

Next.js nos da el componente <Image /> para solucionar esto automáticamente.

import Image from 'next/image';
import fotoPerfil from '../../public/perfil.jpg'; // Imagen local

export default function Perfil() {
  return (
    <div>
      {/* Opción 1: Imagen Local (Importada) */}
      {/* Next.js calcula el ancho y alto automáticamente */}
      <Image
        src={fotoPerfil}
        alt="Foto de Luis"
        placeholder="blur" // Efecto de desenfoque mientras carga
      />

      {/* Opción 2: Imagen Remota (URL) */}
      {/* OBLIGATORIO definir width y height para reservar el espacio */}
      <Image
        src="https://miservidor.com/imagen.jpg"
        alt="Producto"
        width={500}
        height={300}
        className="object-cover rounded-md"
      />
    </div>
  );
}

Copied!

¿Qué hace por nosotros?

  1. Conversión de Formato: Transforma automáticamente vuestro JPG/PNG a WebP o AVIF (formatos modernos que pesan un 30% menos) si el navegador del usuario lo soporta.
  2. Redimensionado (Resizing): Si el usuario entra desde un móvil, Next.js genera y sirve una versión pequeña de la imagen. No sirve la 4K.
  3. Lazy Loading: Las imágenes que no están en pantalla no se descargan hasta que el usuario hace scroll hacia ellas.
  4. Prevención de CLS: Obliga a reservar el espacio, evitando que la web “baile”.

Para usar imágenes remotas (URLs externas), debéis autorizar el dominio en vuestro next.config.mjs por seguridad.

Fuentes: next/font

Otro gran cuello de botella son las tipografías. Tradicionalmente, poníamos un <link> a Google Fonts. Esto es malo porque:

El navegador tiene que hacer una petición DNS a otro servidor (fonts.googleapis.com).

Produce un “parpadeo” (FOIT/FOUT) donde el texto es invisible o cambia de fuente de golpe.

Next.js introduce next/font. Este sistema descarga la fuente en tiempo de compilación (build time) y la aloja junto con vuestros archivos estáticos.

// src/app/layout.tsx
import { Roboto, Open_Sans } from 'next/font/google'; // Importamos de Google, pero se alojará local

// Configuramos la fuente
const roboto = Roboto({
  weight: ['400', '700'],
  subsets: ['latin'],
  variable: '--font-roboto', // Opcional: para usarla con Tailwind
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="es">
      {/* Aplicamos la clase al body */}
      <body className={roboto.className}>
        {children}
      </body>
    </html>
  );
}

Copied!

Al hacer esto, la fuente es parte de vuestro HTML inicial. Cero peticiones a Google. Privacidad total. Carga instantánea.