Volver al inicio

SSR para SPA: configuración de indexación

El artículo cubre la configuración de Renderizado del Lado del Servidor para indexar aplicaciones SPA por los motores de búsqueda. Cubre Next.js, Nuxt.js, Angular Universal con ejemplos de código. Mejora Core Web Vitals y tráfico orgánico.

SSR para indexación de SPA: React, Vue, Angular
Advertisement 728x90

Renderizado del lado del servidor para SPAs: Solución definitiva para la indexación en motores de búsqueda

Los rastreadores como Googlebot y YandexBot suelen encontrar HTML vacío al explorar SPAs basadas en renderizado del lado del cliente (CSR). Sin renderizado del lado del servidor (SSR), estos rastreadores no esperan a que se ejecute JavaScript y abandonan las páginas sin indexarlas. El SSR resuelve este problema generando HTML completamente renderizado en el servidor antes de enviarlo al navegador. Esto acelera el FCP, el LCP y el TTFB, mejorando los Core Web Vitals y la visibilidad orgánica.

Comparativa de estrategias de renderizado

| Parámetro | CSR | SSR | Pre-renderizado | SSG |

|----------|-----|-----|------------------|-----|

Google AdInline article slot

| Ubicación del renderizado | Navegador | Servidor (por cada petición) | Preconstruido para URLs específicas | Preconstruido para todas las páginas |

| FCP/LCP | Lento | Rápido | Rápido | Muy rápido |

| SEO | Deficiente | Excelente | Bueno | Excelente |

Google AdInline article slot

| Actualización de datos | En tiempo real | En tiempo real | Obsoleto | Obsoleto |

| Carga en el servidor | Mínima | Alta | Baja | Mínima |

El SSR funciona mejor en páginas dinámicas: catálogos de productos, feeds de noticias o paneles de control. El pre-renderizado es ideal para URLs con actualizaciones poco frecuentes (p. ej., páginas de marketing). El SSG es la opción perfecta para contenido estático como documentación o páginas de destino.

Google AdInline article slot

React + Next.js: Implementación paso a paso

Crea un nuevo proyecto:

npx create-next-app@latest mi-app-ssr
cd mi-app-ssr
npm run dev

Para páginas dinámicas, usa getServerSideProps:

// pages/producto/[id].jsx
export async function getServerSideProps(context) {
  const { id } = context.params;
  const res = await fetch(`https://api.ejemplo.com/productos/${id}`);
  const producto = await res.json();

  if (!res.ok) {
    return { notFound: true };
  }

  return {
    props: { producto },
  };
}

export default function PaginaProducto({ producto }) {
  return (
    <main>
      <h1>{producto.nombre}</h1>
      <p>{producto.descripcion}</p>
    </main>
  );
}

Agrega etiquetas meta optimizadas para SEO:

import Head from 'next/head';

export default function PaginaProducto({ producto }) {
  return (
    <> 
      <Head>
        <title>{producto.nombre} | Tienda</title>
        <meta name="description" content={producto.descripcionCorta} />
        <meta property="og:title" content={producto.nombre} />
        <meta property="og:image" content={producto.urlImagen} />
      </Head>
      <main>
        <h1>{producto.nombre}</h1>
      </main>
    </>
  );
}

Despliegue: Vercel, Netlify o Node.js con PM2.

Hidratación sin errores

La hidratación es el proceso mediante el cual React del lado del cliente «revive» el HTML renderizado en el servidor. Para evitar discrepancias:

  • Mantén sincronizados los datos entre servidor y cliente.
  • Verifica el entorno de ejecución antes de usar window o document.
  • Supervisa las advertencias en la consola del navegador.
const esNavegador = typeof window !== 'undefined';

Vue + Nuxt.js: Renderizado del lado del servidor

Inicia un proyecto:

npx nuxi@latest init mi-app-nuxt
cd mi-app-nuxt
npm install
npm run dev

Obtén datos del lado del servidor:

<!-- pages/producto/[id].vue -->
<script setup>
const ruta = useRoute();
const { data: producto, error } = await useFetch(
  `/api/productos/${ruta.params.id}`
);

if (error.value) {
  throw createError({ statusCode: 404, message: 'No encontrado' });
}

useHead({
  title: producto.value?.nombre,
  meta: [
    { name: 'description', content: producto.value?.descripcionCorta },
  ],
});
</script>

<template>
  <main>
    <h1>{{ producto?.nombre }}</h1>
    <p>{{ producto?.descripcion }}</p>
  </main>
</template>

Configura nuxt.config.ts:

export default defineNuxtConfig({
  ssr: true,
  app: {
    head: {
      htmlAttrs: { lang: 'es' },
      link: [{ rel: 'canonical', href: 'https://ejemplo.com' }],
    },
  },
  nitro: {
    preset: 'node-server',
  },
});

Angular Universal: Integración SSR

Para Angular 17+:

ng add @angular/ssr

Define el endpoint del servidor en server.ts:

import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
// ... otros imports

const servidor = express();
// ...

servidor.get('*', (req, res, next) => {
  commonEngine
    .render({
      bootstrap: AppServerModule,
      documentFilePath: join(browserDistFolder, 'index.html'),
      url: req.originalUrl,
      publicPath: browserDistFolder,
      providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
    })
    .then((html) => res.send(html))
    .catch(next);
});

Construye y sirve:

npm run build:ssr
npm run serve:ssr

Detección segura de plataforma:

import { isPlatformBrowser } from '@angular/common';
import { inject, PLATFORM_ID } from '@angular/core';

const platformId = inject(PLATFORM_ID);

if (isPlatformBrowser(platformId)) {
  console.log(window.location.href);
}

Integración con CMS desacoplado

Para Next.js + Contentful GraphQL:

export async function getServerSideProps({ params }) {
  const consulta = `
    query ObtenerArticulo($slug: String!) {
      articleCollection(where: { slug: $slug }) {
        items {
          titulo
          cuerpo
          seo {
            titulo
            descripcion
          }
        }
      }
    }
  `;

  const res = await fetch(
    `https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${process.env.CONTENTFUL_TOKEN}`,
      },
      body: JSON.stringify({ consulta, variables: { slug: params.slug } }),
    }
  );

  const { data } = await res.json();
  // ...
}

Conclusiones clave

  • El SSR entrega HTML completamente renderizado a los rastreadores, resolviendo el principal problema de indexación de las aplicaciones CSR.
  • Next.js, Nuxt.js y Angular Universal son frameworks probados en producción para SSR.
  • Las discrepancias durante la hidratación provocan re-renderizados completos: valida siempre el entorno de ejecución.
  • Los Core Web Vitals (FCP, LCP, TTFB) mejoran entre un 50 % y un 80 % tras migrar a SSR.
  • Usa caché Redis para mantener el TTFB por debajo de 200 ms incluso durante picos de tráfico.

— Editorial Team

Advertisement 728x90

Leer después