Durante años, comenzar un proyecto con Blazor implicaba tomar una decisión importante desde el primer minuto: ¿Blazor Server o Blazor WebAssembly?
No era una elección menor. Cada modelo traía ventajas claras, pero también compromisos que afectaban toda la arquitectura de la aplicación. Si te equivocabas o si los requisitos cambiaban con el tiempo, el coste de rectificar era alto. Había que pensarlo muy bien antes de escribir la primera línea de código.
Con la llegada de Blazor Web Apps en .NET 8 y su consolidación en .NET 9, esa dicotomía prácticamente ha desaparecido. Ya no eliges un solo modelo para toda la aplicación. Ahora puedes elegir una estrategia de renderizado diferente para cada parte de ella.
La pregunta que surge ahora es más interesante:
¿Estamos ante una evolución natural del ecosistema… o ante un verdadero cambio de paradigma en cómo diseñamos aplicaciones con Blazor?
El pasado: una elección binaria y con consecuencias
Antes de .NET 8, trabajar con Blazor significaba comprometerse con un modelo de hosting para toda la aplicación desde el inicio:
Blazor Server
- Renderizado en el servidor, con interactividad en tiempo real a través de SignalR.
- Carga inicial muy rápida: el cliente recibía HTML listo para usar.
- Alta dependencia de la conexión: cualquier latencia o desconexión afectaba directamente la experiencia del usuario.
- Escalabilidad limitada, porque cada usuario mantenía un circuito activo.
Blazor WebAssembly
- Ejecución completa en el navegador gracias al runtime de .NET compilado a WebAssembly (WASM).
- Mayor independencia del servidor y soporte offline real.
- Payload inicial pesado: había que descargar el runtime, los ensamblados y la aplicación completa antes de que el usuario pudiera interactuar.
El problema no era tener dos opciones. El problema era que la decisión era global e irreversible a corto plazo. Si tu aplicación necesitaba páginas públicas rápidas con buen SEO y, al mismo tiempo, dashboards complejos e interactivos, ningún modelo te lo daba todo de forma natural. Tenías que elegir el que “menos te perjudicaba” o terminar manteniendo dos proyectos separados.
He trabajado con ambos y la fricción era muy real. En proyectos Blazor Server, la dependencia de la conexión era un dolor constante en redes inestables. En WebAssembly, el tiempo de carga inicial siempre salía a relucir en las primeras demos. Nunca había una respuesta completamente cómoda.
El presente: Blazor Web Apps (consolidado en .NET 9)
A partir de .NET 8, y especialmente con las mejoras de .NET 9, el enfoque cambió de raíz. Ya no defines tu aplicación como “Server” o “WebAssembly”. Ahora creas una Blazor Web App y decides cómo se renderiza cada componente, página o ruta.
Render Modes: el corazón del nuevo modelo
Los modos de renderizado te permiten elegir dónde y cómo se ejecuta cada parte de tu aplicación:
| Modo | Dónde se renderiza | Interactividad | Escenario típico |
|---|---|---|---|
| Static SSR | Servidor | No | Contenido público, SEO, páginas informativas |
| Interactive Server | Servidor | Sí | Aplicaciones internas, lógica sensible en backend |
| Interactive WebAssembly | Cliente | Sí | Interfaces complejas, escenarios offline |
| Interactive Auto | Servidor → Cliente | Sí | Balance entre carga inicial rápida y autonomía |
El modo Auto es especialmente interesante porque resuelve de forma muy pragmática una de las tensiones históricas de Blazor: empieza renderizando en el servidor (con prerendering) para dar una experiencia inmediata, mientras descarga en segundo plano el runtime de WebAssembly. En visitas posteriores o interacciones siguientes, la ejecución se mueve al cliente. No es la solución perfecta para todos los casos, pero cubre muy bien el escenario más habitual.
Novedades clave en .NET 9
Una de las adiciones más útiles es RendererInfo: una API que te permite detectar en tiempo de ejecución el modo de renderizado actual del componente. Sus dos propiedades principales son RendererInfo.Name (que devuelve «Static«, «Server» o «WebAssembly«) y RendererInfo.IsInteractive. Esto facilita mucho los comportamientos condicionales y, sobre todo, la depuración en aplicaciones híbridas.
Ejemplo práctico:
@page "/counter"
@rendermode InteractiveAuto
<h3>Modo actual: @RendererInfo.Name</h3>
@if (RendererInfo.IsInteractive)
{
<p>
Componente interactivo ejecutándose en
@(RendererInfo.Name == "WebAssembly" ? "el cliente" : "el servidor")
</p>
}
else
{
<p>Renderizado estático (prerendering en curso)...</p>
}
@code {
// Lógica del componente
}
Además, .NET 9 trajo mejoras concretas de rendimiento: assets estáticos con pre-compresión y fingerprinting, lanzamiento de WebAssembly hasta un 25% más rápido, compresión de mensajes WebSocket en modo Server y una mejor experiencia de reconexión automática. No son cambios revolucionarios, pero sí se notan cuando la aplicación está en producción.
De arquitectura rígida a composición flexible
El cambio no es solo incremental. Es estructural.
Antes:
- Una aplicación = un único modelo de hosting
- Trade-offs globales que afectaban a todo
- Decisión temprana y costosa de revertir
Ahora:
- Una aplicación = múltiples estrategias de renderizado coexistiendo
- Decisión por componente, página o ruta
- Trade-offs localizados donde realmente importan
Esto abre la puerta a escenarios que antes obligaban a compromisos incómodos o arquitecturas complejas:
- Páginas públicas con Static SSR para maximizar SEO y velocidad de carga
- Dashboards con Interactive Server para mantener la lógica cerca de los datos
- Componentes ricos con Interactive WebAssembly donde la autonomía del cliente suma valor real
- Interactive Auto como opción pragmática para la mayor parte de la aplicación
En código, esa flexibilidad se ve de forma muy directa:
<Counter @rendermode="InteractiveServer" />
<Weather @rendermode="InteractiveWebAssembly" />
Dos componentes en la misma página, cada uno con su propia estrategia. Sin magia, sin configuraciones ocultas.
Los desafíos reales (no solo los teóricos)
Este nuevo modelo no elimina la complejidad. La redistribuye. Y eso trae consecuencias concretas que es bueno tener en cuenta.
Más decisiones durante el desarrollo. Antes tomabas una decisión difícil al principio y luego seguías adelante. Ahora tomas decisiones más pequeñas pero de forma continua. Si el equipo no tiene criterios claros sobre cuándo usar cada modo, puedes terminar con una aplicación inconsistente.
El ciclo de vida se complica en escenarios híbridos. Uno de los errores más frecuentes es asumir que el estado se comporta igual durante el prerendering y en la fase interactiva. OnInitializedAsync se ejecuta dos veces en componentes con prerendering: una en el servidor (estático) y otra al hidratar en el cliente. Si no lo tienes en cuenta, puedes duplicar llamadas a APIs o generar comportamientos inesperados.
@code {
protected override async Task OnInitializedAsync()
{
// Se ejecuta dos veces si hay prerendering
await LoadDataAsync();
}
}
La solución habitual pasa por comprobar el estado de interactividad antes de ejecutar lógica costosa, o usar PersistentComponentState para transferir datos del prerendering al cliente sin repetir trabajo.
Restricción importante: un componente interactivo no puede contener hijos con un modo interactivo diferente. Esta limitación no es obvia al principio y suele generar errores en tiempo de ejecución. La jerarquía de modos importa.
Recomendación práctica: diseña tus componentes lo más agnósticos posible al render mode. Aplica el @rendermode en el punto donde usas el componente, no dentro de él. De esta forma mantienes la flexibilidad y evitas acoplar la lógica a una estrategia concreta.
¿Reemplazo o evolución?
Ni una cosa ni la otra exactamente. Es una convergencia.
Blazor Server y WebAssembly no desaparecen como conceptos ni como motores. Siguen siendo la base de todo. Los render modes simplemente los abstraen y los combinan de forma inteligente. Cuando usas Interactive Server sigue habiendo SignalR debajo, y cuando usas Interactive WebAssembly sigue estando el runtime WASM.
Lo que realmente cambia es el modelo mental con el que diseñamos:
Antes definías cómo funcionaba toda tu aplicación.
Ahora defines cómo debe comportarse cada una de sus partes.
Con las mejoras de .NET 9, especialmente RendererInfo, el mejor rendimiento de assets y la reconexión mejorada, este enfoque se siente mucho más maduro y preparado para proyectos reales que en su primera versión de .NET 8.
Mi lectura personal
Blazor Web Apps no hace que Blazor sea más simple. Lo hace más expresivo y más cercano a cómo realmente pensamos sobre los problemas que queremos resolver. Y, en consecuencia, más potente.
La decisión que antes tomabas una sola vez al inicio del proyecto ahora la distribuyes a lo largo del desarrollo. Eso puede sonar a más trabajo (y en cierta medida lo es), pero también significa que puedes tomar mejores decisiones, con más contexto y en el momento adecuado.
Después de haber trabajado con los tres modelos (Server, WebAssembly y Blazor Web Apps), lo que más me ha impactado no son las métricas de rendimiento, sino lo natural que se siente ahora el diseño de la aplicación. La arquitectura puede seguir las necesidades reales de cada sección, en lugar de forzarse a encajar en las limitaciones de un modelo global.
No es perfecto. Los desafíos que mencioné son reales y pueden complicarte la vida si no los anticipas. Pero la dirección es claramente la correcta.
Blazor Web Apps no elimina la complejidad; la hace más flexible, más observable y más distribuida.
La pregunta que importa ahora
Blazor Web Apps no es el final de Blazor Server ni de Blazor WebAssembly.
Es su convergencia en un modelo composable, optimizado y listo para producción.
La pregunta ya no es:
«¿Server o WebAssembly?»
Ahora es:
«¿Qué necesita esta parte concreta de mi aplicación?»
Ese cambio de pregunta, reforzado por las mejoras de .NET 9, está redefiniendo cómo diseñamos y cómo razonamos sobre las aplicaciones Blazor hoy en día.