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
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.