Introducción a SSR

Intermedio
Angular
Angular
Actualizado: 24/09/2025

SSR nativo en Angular y configuración inicial

Server-Side Rendering (SSR) representa un cambio fundamental en cómo Angular genera y entrega las aplicaciones web. Mientras que las aplicaciones SPA tradicionales se ejecutan completamente en el navegador del cliente, SSR pre-renderiza las páginas en el servidor antes de enviarlas al usuario.

¿Qué es SSR y por qué utilizarlo?

En una aplicación Angular tradicional, el navegador recibe un HTML mínimo con un <app-root></app-root> vacío y después descarga y ejecuta JavaScript para renderizar todo el contenido. Con SSR, el servidor ejecuta la aplicación Angular y genera HTML completo con contenido, enviando páginas ya renderizadas al navegador.

Los beneficios principales de implementar SSR incluyen:

  • SEO optimizado: Los motores de búsqueda reciben HTML completo con contenido, mejorando significativamente la indexación
  • Core Web Vitals mejorados: Métricas como LCP (Largest Contentful Paint) se optimizan al mostrar contenido inmediatamente
  • Perceived performance: Los usuarios ven contenido instantáneamente, aunque la interactividad llegue después
  • Mejor experiencia en dispositivos lentos: Reduce la carga de procesamiento en el cliente

Configuración de SSR en proyectos nuevos

Angular 20+ incluye SSR nativo que se configura fácilmente desde el CLI. Para proyectos nuevos, utiliza el flag --ssr:

ng new mi-app-ssr --ssr

Este comando genera automáticamente toda la infraestructura necesaria para SSR, incluyendo:

  • Configuración de build para servidor y cliente
  • Archivo server.ts con servidor Express
  • Scripts de package.json optimizados
  • Configuración de hydration automática

Agregando SSR a proyectos existentes

Si ya tienes una aplicación Angular, puedes agregar SSR ejecutando:

ng add @angular/ssr

Este comando modifica tu proyecto existente añadiendo:

// package.json (scripts adicionales)
{
  "scripts": {
    "serve:ssr": "node dist/mi-app/server/server.mjs",
    "build:ssr": "ng build && ng run mi-app:server",
    "prerender": "ng run mi-app:prerender"
  }
}

El archivo server.ts y Express

Cuando habilitas SSR, Angular genera un archivo server.ts que contiene un servidor Express responsable del renderizado:

import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import AppServerModule from './src/main.server';

const app = express();
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');

// Motor de renderizado Angular Universal
const commonEngine = new CommonEngine();

// Servir archivos estáticos
app.get('*.*', express.static(browserDistFolder));

// Renderizar páginas Angular
app.get('*', (req, res, next) => {
  const { protocol, originalUrl, baseUrl, headers } = req;

  commonEngine
    .render({
      bootstrap: AppServerModule,
      documentFilePath: join(browserDistFolder, 'index.html'),
      url: `${protocol}://${headers.host}${originalUrl}`,
      publicPath: browserDistFolder,
      providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
    })
    .then((html) => res.send(html))
    .catch((err) => next(err));
});

const port = process.env['PORT'] || 4000;
app.listen(port, () => {
  console.log(`Servidor ejecutándose en http://localhost:${port}`);
});

Funcionamiento del servidor Express

El servidor Express integrado maneja el proceso de renderizado siguiendo estos pasos:

1 - Recepción de request: Express recibe la petición HTTP del navegador

2 - Renderizado Angular: CommonEngine ejecuta la aplicación Angular en el servidor, generando HTML completo

3 - Respuesta con contenido: El servidor envía HTML pre-renderizado con todo el contenido visible

4 - Hydration en cliente: El navegador recibe el HTML y posteriormente "hidrata" la aplicación para hacerla interactiva

Builds y estructura de archivos

Con SSR habilitado, el proceso de build genera dos bundles separados:

ng build:ssr

Esto produce:

dist/
├── browser/          # Bundle para navegador (cliente)
│   ├── main.js
│   ├── polyfills.js
│   └── index.html
└── server/           # Bundle para servidor
    ├── server.mjs    # Servidor Express compilado
    └── main.js       # Aplicación Angular para servidor

El bundle de servidor contiene tu aplicación Angular optimizada para ejecutarse en Node.js, mientras que el bundle de navegador se descarga después para la hidration.

Cuándo usar SSR vs SPA tradicional

Utiliza SSR cuando:

  • SEO es crítico para tu aplicación
  • Necesitas optimizar Core Web Vitals
  • Los usuarios tienen conexiones lentas o dispositivos limitados
  • El contenido inicial es importante para la primera impresión

Mantén SPA tradicional cuando:

  • La aplicación es principalmente interna o privada
  • La interactividad inmediata es más importante que el contenido inicial
  • Los recursos de servidor son limitados
  • La aplicación es altamente dinámica con poco contenido estático

La configuración nativa de Angular 20+ hace que implementar SSR sea significativamente más simple que en versiones anteriores, eliminando la complejidad de configuración manual y proporcionando una experiencia de desarrollo fluida.

Hydration y consideraciones prácticas

¿Qué es la hydration en Angular?

La hydration es el proceso mediante el cual Angular toma el HTML estático pre-renderizado en el servidor y lo convierte en una aplicación Angular completamente interactiva en el navegador. Durante este proceso, Angular conecta los event listeners, inicializa la detección de cambios y restaura el estado de la aplicación.

En Angular 20+, la hydration es automática y optimizada. Una vez que el navegador recibe el HTML pre-renderizado, Angular identifica los elementos existentes y los "reutiliza" en lugar de re-renderizar todo desde cero.

Proceso de hydration automática

El flujo de hydration sigue estos pasos fundamentales:

1 - Recepción del HTML: El navegador recibe y muestra el HTML pre-renderizado instantáneamente

2 - Descarga de JavaScript: Se descargan los bundles de cliente en paralelo

3 - Bootstrapping: Angular se inicializa y identifica elementos existentes en el DOM

4 - Event binding: Se conectan todos los event listeners a los elementos correspondientes

5 - Reactividad: Se activa la detección de cambios y la reactividad de signals

Durante la hydration, Angular utiliza hydration hints para optimizar el proceso:

// Angular detecta automáticamente elementos hidratables
@Component({
  selector: 'app-product',
  standalone: true,
  template: `
    <div>
      <h2>{{ product().name }}</h2>
      <button (click)="addToCart()">Agregar al carrito</button>
      @if (showDetails()) {
        <div class="details">{{ product().description }}</div>
      }
    </div>
  `
})
export class ProductComponent {
  product = input.required<Product>();
  showDetails = signal(false);

  addToCart() {
    // Event listener se conecta durante hydration
    this.showDetails.set(true);
  }
}

Diferencias entre entorno browser y server

Uno de los aspectos críticos de SSR es manejar las diferencias entre el entorno de servidor (Node.js) y el navegador. Durante el renderizado en servidor, ciertas APIs del navegador no están disponibles.

APIs no disponibles en servidor:

  • window, document, localStorage, sessionStorage
  • APIs de geolocalización, cámara, micrófono
  • setTimeout, setInterval (aunque Node.js los tiene, pueden comportarse diferente)
  • APIs de medición como getBoundingClientRect()

Manejo seguro de APIs browser-only

Para manejar estas diferencias, Angular proporciona varias estrategias:

Inyección de PLATFORM_ID:

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

@Component({
  selector: 'app-browser-specific',
  standalone: true,
  template: `
    <div>
      @if (canUseStorage) {
        <p>Datos guardados: {{ savedData }}</p>
      } @else {
        <p>Cargando datos...</p>
      }
    </div>
  `
})
export class BrowserSpecificComponent implements OnInit {
  private platformId = inject(PLATFORM_ID);
  canUseStorage = false;
  savedData = '';

  ngOnInit() {
    if (isPlatformBrowser(this.platformId)) {
      this.canUseStorage = true;
      this.savedData = localStorage.getItem('userData') || '';
    }
  }
}

Uso de afterNextRender para operaciones de cliente:

import { afterNextRender } from '@angular/core';

@Component({
  selector: 'app-client-operations',
  standalone: true,
  template: `
    <div>
      <canvas #canvas width="400" height="200"></canvas>
      <p>Dimensiones: {{ dimensions }}</p>
    </div>
  `
})
export class ClientOperationsComponent {
  canvas = viewChild<ElementRef<HTMLCanvasElement>>('canvas');
  dimensions = '';

  constructor() {
    // Solo se ejecuta después de hydration en el cliente
    afterNextRender(() => {
      if (this.canvas()) {
        const rect = this.canvas()!.nativeElement.getBoundingClientRect();
        this.dimensions = `${rect.width}x${rect.height}`;
      }
    });
  }
}

Consideraciones de estado y sincronización

Durante la hydration, es crucial que el estado del servidor coincida con el estado inicial del cliente para evitar inconsistencias visuales:

@Component({
  selector: 'app-data-sync',
  standalone: true,
  template: `
    <div>
      @for (item of items(); track item.id) {
        <div class="item">{{ item.name }} - {{ item.timestamp | date }}</div>
      }
    </div>
  `
})
export class DataSyncComponent implements OnInit {
  items = signal<Item[]>([]);

  ngOnInit() {
    // Usar datos estáticos o deterministas durante SSR
    // Evitar Date.now() o Math.random() que cambian entre servidor y cliente
    this.loadItems();
  }

  private loadItems() {
    // Datos que deben ser consistentes entre servidor y cliente
    const staticItems = [
      { id: 1, name: 'Producto A', timestamp: new Date('2024-01-01') },
      { id: 2, name: 'Producto B', timestamp: new Date('2024-01-02') }
    ];
    this.items.set(staticItems);
  }
}

Debugging y herramientas de desarrollo

Para diagnosticar problemas de hydration, Angular proporciona herramientas específicas:

Habilitación de debugging de hydration:

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { withDebugTracing } from '@angular/core';

bootstrapApplication(AppComponent, {
  providers: [
    // Habilita logging detallado de hydration
    withDebugTracing()
  ]
});

Identificación de hydration mismatches:

Cuando el estado del servidor no coincide con el cliente, Angular muestra warnings específicos en la consola:

Hydration mismatch: expected element <div> but found <span>

Optimizaciones de performance

Para maximizar el rendimiento de hydration, considera estas prácticas:

  • Minimiza JavaScript inicial: Usa lazy loading para componentes no críticos
  • Prioriza contenido above-the-fold: Hidrata primero el contenido visible
  • Evita cálculos pesados durante SSR: Delega operaciones complejas al cliente
@Component({
  selector: 'app-optimized',
  standalone: true,
  template: `
    <div>
      <!-- Contenido crítico se hidrata primero -->
      <header>{{ title }}</header>
      
      <!-- Contenido secundario se puede deferir -->
      @defer (on viewport) {
        <app-heavy-component />
      } @placeholder {
        <div>Cargando contenido adicional...</div>
      }
    </div>
  `
})
export class OptimizedComponent {
  title = 'Mi aplicación';
}

La hydration automática de Angular 20+ simplifica significativamente el desarrollo de aplicaciones SSR, pero entender estos conceptos es fundamental para crear aplicaciones robustas que funcionen correctamente tanto en servidor como en cliente.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Angular

Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Comprender qué es SSR y sus beneficios en aplicaciones Angular.
  • Aprender a configurar SSR en proyectos nuevos y existentes con Angular CLI.
  • Entender el funcionamiento del servidor Express y el proceso de renderizado en SSR.
  • Conocer el proceso de hydration automática y cómo manejar diferencias entre entorno servidor y navegador.
  • Aplicar buenas prácticas para optimizar el rendimiento y evitar problemas durante la hidratación.