Puntos clave
- El renderizado del lado del servidor en Angular genera la página en un servidor Node.js y envía el código HTML totalmente renderizado al navegador, utilizando hidratación no destructiva y reproducción de eventos para evitar el parpadeo de la interfaz de usuario.
- Las plataformas sin servidor, como Vercel y AWS Lambda, provocan arranques en frío, lo que añade entre 2 y 3 segundos al tiempo hasta el primer byte y anula las ventajas de velocidad que se supone que ofrece el SSR.
- En esta guía crearás una aplicación Angular con SSR basada en datos que recupera información de una API externa en el momento de la renderización y la serializa directamente en el código HTML.
- La app terminada se implementa en Cloudways Velocity, un entorno Node.js gestionado y persistente sin arranques en frío y con implementaciones automáticas desde GitHub.
Por defecto, una app de Angular se ejecuta en el navegador. El servidor envía una etiqueta prácticamente vacía y, a continuación, el usuario tiene que esperar a que se descarguen y se ejecuten los paquetes de JavaScript antes de que aparezca nada en la página.
Con una conexión lenta, eso se traduce en una pantalla en blanco durante un segundo o dos. Y los rastreadores que no gestionan bien el JavaScript acaban sin ver casi nada de tu contenido.
El renderizado del lado del servidor en Angular soluciona esto ejecutando ese primer renderizado en un servidor Node.js. El servidor genera la página, recoge los datos que necesita y devuelve el HTML completamente renderizado al instante. Así, el usuario ve el contenido real casi al instante y los rastreadores obtienen una página completa que leer.
La cuestión es que la forma en que lo implementes es tan importante como activarlo. Si lo haces mal, o bien pierdes la velocidad que buscabas, o bien el HTML renderizado nunca llega al navegador tal y como querías.
En esta guía, te explicaré por qué merece la pena usar el SSR de Angular y cómo funciona. Después, crearé una aplicación de Angular basada en datos con SSR activado y desplegaré su salida prerenderizada en un entorno gestionado de Node.js (Cloudways Velocity) para que los usuarios y los rastreadores obtengan un código HTML completamente completado desde la primera carga.
¿Por qué usar el renderizado del lado del servidor en Angular?
El renderizado del lado del cliente (CSR) deja todo el trabajo en manos del navegador del usuario. El servidor le envía un archivo HTML básico y, hasta que se ejecuten tus paquetes de JavaScript y lleguen las respuestas a tus llamadas a la API, el usuario se queda mirando una pantalla en blanco.
El SSR le da la vuelta a eso y devuelve el trabajo al servidor.
Lo que quiero decir es que, cuando llega una solicitud a tu backend de Node.js, Angular recorre su ciclo de vida, recupera los datos de la API, construye el árbol de componentes y, a continuación, el servidor convierte ese DOM en HTML sin formato y lo devuelve directamente.
El navegador muestra la página al instante. Pero Angular, en el lado del cliente, todavía tiene que intervenir para que todo sea interactivo, y la versión moderna de Angular gestiona ese traspaso con dos características que vale la pena conocer.
La primera es la hidratación no destructiva. En lugar de borrar el HTML renderizado por el servidor y volver a renderizarlo todo desde cero (que es lo que provoca ese molesto parpadeo en la interfaz de usuario), Angular analiza el DOM que ya está ahí y simplemente le añade sus detectores de eventos.
La segunda es la repetición de eventos. Imagina que un usuario hace clic en un botón antes de que el JavaScript del lado del cliente haya terminado de cargarse. Angular guarda ese clic y lo repite en cuanto termina la hidratación, para que no se pierda nada.
Pero todo esto depende de una sola cosa: una respuesta rápida del servidor. Si tu proceso Node se queda atascado al reactivarse tras un arranque en frío sin servidor, ninguna de estas optimizaciones sirve de nada, porque la carga de la página ya se ha echado a perder antes de que tengan tiempo de surtir efecto.
El problema del arranque en frío en el SSR sin servidor
Si echas un vistazo a plataformas como Vercel, Netlify o AWS Amplify, verás que apuestan muy fuerte por la arquitectura sin servidor. Quieren que despliegues tu aplicación Angular con SSR como una función de AWS Lambda (o una función de Edge).
Y la experiencia del desarrollador es realmente genial. Conectas tu repositorio de GitHub, subes los cambios a la rama principal y ellos se encargan de la compilación por ti. Pero, en lo que respecta específicamente al SSR, la arquitectura tiene un fallo fundamental.
Te lo explico.
Las funciones sin servidor son efímeras. Para ahorrar dinero y recursos informáticos, los proveedores de servicios en la nube no mantienen tu servidor Node.js en marcha las 24 horas del día, los 7 días de la semana. Así que, si tu aplicación pasa 10 o 15 minutos sin recibir visitas, el proveedor apaga el contenedor. Se pone en modo de suspensión.
Luego, el siguiente usuario hace clic en un enlace a tu sitio web, y ahora el proveedor tiene que poner en marcha un nuevo microcontenedor, cargar el entorno de ejecución de Node.js, descargar tu paquete «server.mjs» de Angular, ejecutar el servidor Express y, solo entonces, ejecutar tu lógica de SSR de Angular para generar realmente el HTML.
Todo ese proceso de arranque se conoce como «arranque en frío» y, por lo general, añade entre 2 y 3 segundos a tu «tiempo hasta el primer byte» (TTFB).
Piénsalo un momento. Si te has pasado días consiguiendo que tu app de Angular tenga un TTFB de 200 ms y luego la has integrado en una función sin servidor que se queda inactiva y añade un retraso de 3000 ms, básicamente has echado por la borda toda la ventaja de rendimiento que ofrece el SSR.
Así que, para que las ventajas de velocidad se noten de verdad, tu servidor Node tiene que ser persistente. Con esto me refiero a que esté activo las 24 horas del día, los 7 días de la semana, listo para gestionar las solicitudes al instante, sin tener que arrancar un entorno nuevo cada vez.
Comparativa de opciones de alojamiento para Angular con SSR
Entonces, si el modelo «serverless» genera «cold starts», ¿dónde deberías alojar realmente tu aplicación Angular compilada? En realidad, hay tres niveles que debes tener en cuenta para las aplicaciones de Node.js.
| Tipo de alojamiento | Estado del entorno | Impacto en el TTFB | Se requiere DevOps | Ejemplos de proveedores |
| Sin servidor | Efémero | Alto, arranques en frío de 2-3 s | Bajo, automatizado | Vercel, AWS Lambda |
| VPS sin sistema operativo | Persistente | Instantáneo | Alto, Nginx/PM2 manual | DigitalOcean, EC2 |
| Node.js gestionado | Persistente | Instantáneo | Bajo, automatizado | Cloudways Velocity |
Lo primero es el modelo «serverless». Como ya he dicho, es fácil de implementar, pero lo pagas con los arranques en frío. Es genial para aplicaciones estáticas del lado del cliente, pero bastante malo para el SSR que requiere mucha potencia de cálculo.
Luego está la opción del VPS puro. Alquilas algo como un droplet de Ubuntu de 5 dólares en DigitalOcean, tu servidor Node está encendido las 24 horas del día, los 7 días de la semana, y no hay ningún arranque en frío.
¿El problema? Ahora eres administrador de sistemas. Tienes que conectarte tú mismo al servidor por SSH, instalar Node, configurar un proxy inverso de Nginx para redirigir el puerto 4000 al 80, configurar PM2 para que la app vuelva a funcionar si se cuelga y renovar a mano tus certificados SSL de Let’s Encrypt.
Y luego está la opción gestionada, que es la que yo uso para el SSR de Angular en producción. Plataformas como Cloudways Velocity te proporcionan un contenedor persistente, así que tu aplicación Node permanece activa las 24 horas del día, los 7 días de la semana, sin ningún arranque en frío; solo que ahora la plataforma se encarga por ti del proxy Nginx, el SSL y las implementaciones automáticas desde GitHub.
Así que disfrutas de la velocidad pura de un VPS sin tener que ocuparte de nada relacionado con la administración del sistema.
En la siguiente sección, voy a crear una nueva aplicación de Angular con SSR activado y voy a desarrollar un componente que recupere datos del servidor, para que podamos comprobar que esta configuración funciona en producción.
Consigue la velocidad de un VPS sin tener que ocuparte de la administración del sistema
Cloudways Velocity te ofrece para tu aplicación Angular con SSR un contenedor Node.js persistente con implementaciones automáticas desde GitHub.
Creación de una aplicación Angular SSR basada en datos
Una aplicación estática de «Hola, mundo» no nos sirve aquí. Necesito algo que realmente realice tareas del lado del servidor, para poder comprobar que la configuración del alojamiento funciona bien.
Así que voy a crear una aplicación sencilla e independiente de Angular que llame a una API externa (JSONPlaceholder) y recupere una lista de entradas. El objetivo es sencillo: ver cómo Angular recupera esos datos durante la renderización e incorporarlos al HTML antes de que lleguen al navegador.
Paso 1: Configuración del entorno Angular
Antes de empezar a configurar nada, necesito Node en mi terminal. Como estoy usando mi portátil del trabajo con restricciones informáticas, no puedo ejecutar un instalador normal, así que estoy usando el binario independiente de Node.js. Esto significa que CMD no sabe dónde están mis herramientas de Node hasta que se lo indique. Para solucionarlo, abriré CMD y le indicaré la carpeta donde descomprimí Node:
PATH=%PATH%;C:\Users\abdulrehman\Downloads\node-v24.18.0-win-x64\node-v24.18.0-win-x64
Una vez configurado esto, usaré Angular CLI para crear un nuevo espacio de trabajo.
npx @angular/cli new angular-ssr-demo --ssr
Durante las indicaciones de configuración, solo pulso Intro para quedarme con el CSS estándar. También me he saltado la integración de herramientas de IA para mantener mi espacio de trabajo ordenado.

Una vez que termine la instalación, voy a mi directorio:
cd angular-ssr-demo
Al usar el parámetro –ssr, le indicas a Angular que genere un archivo server.ts, que es tu backend de Express, y que configure automáticamente los objetivos de compilación del servidor dentro de angular.json.
Lo más importante que hace es configurar la hidratación moderna. Si abro src/app/app.config.ts, quiero mantener los valores predeterminados que ha generado la CLI (como provideBrowserGlobalErrorListeners), pero tengo que asegurarme de que tanto el enrutamiento como la hidratación estén habilitados, además de HttpClient para que el propio servidor pueda realizar llamadas a la API.

También voy a añadir withEventReplay(), que se encarga de que cualquier clic que hagas antes de que termine de cargarse el JavaScript se registre y no se pierda.
// src/app/app.config.ts
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(routes),
// Enables non-destructive hydration and buffers early user clicks
provideClientHydration(withEventReplay()),
// withFetch() is required for SSR to make HTTP calls natively in Node
provideHttpClient(withFetch())
]
};

Paso 2: Crear el componente de recuperación de datos
Ahora voy a crear el componente que realmente recupera las entradas y las muestra. Esta es la parte que demuestra lo que quiero decir: el servidor ejecuta esta recuperación y el resultado acaba en el código HTML antes incluso de que el navegador lo vea.
Voy a abrir src/app/app.ts y sustituir lo que hay ahí por mi propio componente. Este define la estructura de una publicación, llama a la API de JSONPlaceholder dentro de ngOnInit y, a continuación, recorre los resultados en la plantilla para mostrar cada uno de ellos.
// src/app/app.ts
import { Component, inject, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { CommonModule } from '@angular/common';
interface Post {
id: number;
title: string;
}
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule],
template: `
<main style="font-family: sans-serif; padding: 2rem;">
<h1>Angular SSR Data Fetching</h1>
<p>If you View Page Source, this list is baked into the raw HTML:</p>
<ul>
<li *ngFor="let post of posts">
<strong>{{ post.id }}</strong> - {{ post.title }}
</li>
</ul>
</main>
`
})
export class App implements OnInit {
private http = inject(HttpClient);
posts: Post[] = [];
ngOnInit() {
// The server waits for this request to finish before rendering the HTML
this.http.get<Post[]>('https://jsonplaceholder.typicode.com/posts?_limit=5')
.subscribe(data => {
this.posts = data;
});
}
}

Como configuré HttpClient con withFetch() en el paso 1, el servidor puede ejecutar esta llamada HTTP de forma nativa durante la generación de la página. Así que, para cuando la página llega al navegador, la lista de entradas ya está incluida en el HTML.
Paso 3: Probar la app localmente
Para confirmar que el servidor se encarga realmente de la renderización, voy a poner en marcha el servidor de desarrollo local.
npm run start
Una vez que el servidor esté en marcha, en la ventana del símbolo del sistema debería aparecer«Application bundle generation complete».

A continuación, abriré http://localhost:4200 en mi navegador.

El aspecto de la página no es lo que realmente me interesa. Lo que quiero comprobar es qué es lo que el servidor ha enviado en primer lugar.
Así que voy a copiar el primer elemento de la lista de la página y luego voy a ver el código fuente de la página.

Si busco con CTRL+F lo que he copiado, en lugar de ver un espacio en blanco a la espera de JavaScript, puedo ver los datos de mi envío ya incrustados en el código HTML sin formato que ha enviado el servidor. Angular ha recuperado los datos mientras generaba la página y los ha serializado directamente en la respuesta.

Esa es precisamente la ventaja de renderizar en el lado del servidor: el contenido ya está ahí en la primera carga, antes de que se ejecute ningún JavaScript del lado del cliente.
Ahora que la app funciona, el siguiente paso es compilarla para producción y ponerla en marcha.
Paso 4: Compilar la aplicación para producción
Como la recuperación de datos funciona de forma local, el siguiente paso es compilar la aplicación para producción.
npm run build

Una compilación normal de Angular del lado del cliente simplemente guarda un montón de archivos estáticos en una sola carpeta, pero la compilación SSR funciona de otra manera y genera una estructura específica dentro de dist/angular-ssr-demo/.
Cuando abro ese directorio «dist», hay dos carpetas principales. La carpeta «browser/» contiene los recursos estándar del lado del cliente: los CSS, las imágenes y los paquetes de JavaScript que toman el relevo en cuanto se activa la hidratación. La carpeta «server/» contiene el servidor Node.js Express compilado que se encarga de la representación real.
Lo que más me importa a la hora de hacer el despliegue es la carpeta «browser/ ». Durante la compilación, Angular prerenderiza mi ruta e inserta los datos recuperados directamente en el código HTML de esa carpeta. Así que, cuando haga el despliegue, esa es la carpeta a la que le indicaré a Cloudways que apunte, ya que ya contiene páginas totalmente renderizadas y listas para servir.
Paso 5: Implementación de Angular SSR en Cloudways Velocity
Ahora voy a poner esto en marcha en Cloudways Velocity, un entorno gestionado de Node.js que se encarga del proxy Nginx, el SSL y las implementaciones automáticas desde GitHub por mí.
Primero, voy a hacer un commit de todo el proyecto y subirlo a un nuevo repositorio de GitHub. Git ya ignora «node_modules» y la carpeta «dist» por defecto, así que esas no se incluyen.



En cuanto lo haya subido, me conectaré a Cloudways y configuraré la implementación.
Empezaré creando una aplicación Velocity desde el panel de control de Cloudways y elegiré el tamaño del servidor.


A continuación, conectaré mi cuenta de GitHub y seleccionaré el repositorio «angular-server-side-rendering» que acabo de subir.


Ahora Cloudways detecta automáticamente la configuración de mi app. Establece el framework en Angular, la rama en «main» y la versión de Node en v24. No tengo que configurar nada de esto a mano.

Hay un ajuste que sí tengo que indicar en el lugar correcto, y es el «Directorio de salida». La compilación de Angular genera sus archivos prerenderizados y listos para el navegador dentro de una subcarpeta llamada «browser», así que voy a configurar el «Directorio de salida» en:
dist/angular-ssr-demo/browser

Esa es la carpeta que contiene el HTML prerenderizado con mis datos ya incluidos, junto con los paquetes del lado del cliente que alimentan la página una vez que se carga.
En cuanto pulso «Desplegar», Cloudways recupera el repositorio, ejecuta «npm install», realiza la compilación y sirve ese resultado prerenderizado.


Cuando termina, la app ya está en línea en la URL temporal de Cloudways. Abro «Ver código fuente de la página» y ahí están los datos de mi entrada, directamente en el HTML sin formato, tal y como estaban en mi equipo.


Conclusión
El SSR de Angular no es solo una casilla que hay que marcar para conseguir una mejor puntuación en Lighthouse. Cambia la forma en que funciona realmente tu aplicación, pasando de una compilación estática a un proceso activo del lado del servidor, y vale la pena tenerlo en cuenta a la hora de decidir dónde alojarla.
La ventaja es sencilla: quienquiera que cargue la página, o cualquier rastreador que la indexe, obtiene contenido real al instante en lugar de una página vacía a la espera de que se ejecute JavaScript.
Y si despliegas ese resultado prerenderizado en un servidor gestionado como Cloudways Velocity, lo consigues todo sin tener que tocar Nginx ni lidiar tú mismo con los servidores.
Implementa tu aplicación Angular SSR en Cloudways
. Conecta tu repositorio, configura el directorio de salida y disfruta de un alojamiento Node.js persistente sin arranques en frío.
P. ¿El SSR de Angular genera solicitudes HTTP duplicadas en el cliente?
No, y de hecho esa es una de las ventajas de cómo funciona. Angular serializa los datos que ha obtenido del servidor directamente en el contenido HTML, así que cuando entra en acción el HttpClient del lado del cliente, simplemente reutiliza esos datos ya existentes en lugar de lanzar la misma solicitud de red por segunda vez.
P. ¿Cuál es la principal diferencia entre el SSR y el SSG (prerenderizado) de Angular?
La diferencia radica en cuándo se genera el HTML. El SSR genera HTML nuevo con cada solicitud al servidor, que es justo lo que necesitas cuando los datos son en tiempo real y cambian constantemente. El SSG genera todo su HTML estático de una sola vez con antelación durante el comando «npm run build», lo que lo hace más adecuado para contenidos que no cambian mucho entre una implementación y otra.
P. ¿Resolverá el SSR todos los problemas de rendimiento de mi app?
No del todo, y conviene ser sincero sobre cuáles son sus límites. El SSR mejora la carga inicial de la página, por lo que tu TTFB y tu FCP mejoran, y también ayuda con la indexación SEO. Pero una vez que haya terminado la hidratación, no servirá de nada ante un código del lado del cliente mal escrito o recursos pesados y sin optimizar, y eso es algo que tendrás que gestionar tú mismo.
Start Growing with Cloudways Today.
Our Clients Love us because we never compromise on these
[email protected]
Abdul es un experto en tecnología, aficionado al café y al marketing creativo al que le encanta estar al día de las últimas actualizaciones de software y aparatos tecnológicos. También es un hábil escritor técnico capaz de explicar conceptos complejos de forma sencilla para un público amplio. Abdul disfruta compartiendo sus conocimientos sobre el sector de la Nube a través de manuales de usuario, documentación y entradas de blog.