Let’s examine client, server and mixed render modes across three leading web frameworks: Blazor, Angular and React.
The evolution of web development has transformed how applications render content to users. As powerful as they are diverse, render modes determine where your code executes, how quickly users see content and how interactive that content becomes. With the right rendering strategy, developers can dramatically improve performance, user experience and SEO without compromising on functionality.
Let’s examine render modes across three leading web frameworks: Blazor, Angular and React. By understanding how each framework approaches rendering, we can identify the similarities between their approaches and select the optimal rendering strategy for our applications while finding common ground among competing technologies.
Throughout web development history, rendering has shifted from static HTML files to server rendering, then to client-first approaches and now to hybrid solutions. With the exception of Blazor, frameworks like Angular and React initially embraced client-side rendering to simplify building highly interactive applications. However, this approach introduced challenges that server rendering capabilities could address.
The strategic selection of render modes impacts applications in several critical ways including:
Because of these trade-offs, modern web development stacks now include multiple render modes to minimize the negative effects of each mode.
Next we’ll examine what render modes exist in modern frameworks and how they’re implemented. The goal is to understand common ground between implementations and not to pit one versus another. Understanding these features as a whole makes choosing frameworks or switching between them much easier.
Blazor targets developer productivity by enabling C# developers to build interactive web UIs without JavaScript. Blazor’s rendering capabilities offer exceptional flexibility with several render modes that determine where components execute and how they interact with users.
In a Blazor application, developers can set render modes on a per-component basis, application-wide or using a mix-and-match approach. Render modes are set using the @rendermode
directive, which child components inherit through the component hierarchy.
Static server-side rendering (Static SSR) renders components on the server as static HTML without interactivity. This approach delivers extremely fast content that’s easily cached, making it ideal for content-focused pages like landing pages or marketing materials.
@page "/static-example"
@rendermode StaticServer
<h1>Static Server Rendered Component</h1>
<p>This content is rendered on the server as static HTML.</p>
Interactive server-side rendering (Interactive SSR) renders components on the server but maintains interactivity through a SignalR connection. User interactions are sent to the server, which updates the UI accordingly. This thin-client approach renders the application quickly, but is dependent on the client’s latency. In addition, it requires a persistent connection to the server to remain interactive.
@page "/interactive-server"
@rendermode InteractiveServer
<button @onclick="UpdateMessage">Click me</button> @message
@code {
private string message = "Not updated yet.";
private void UpdateMessage()
{
// This executes on the server
message = "Updated on the server!";
}
}
Client-side rendering in Blazor leverages WebAssembly to run .NET code directly in the browser. The component is downloaded and executed on the client, with all interactivity processed using the .NET runtime instead of JavaScript.
@page "/interactive-wasm"
@rendermode InteractiveWebAssembly
<button @onclick="UpdateMessage">Click me</button> @message
@code {
private string message = "Not updated yet.";
private void UpdateMessage()
{
// This executes in the browser
message = "Updated on the client!";
}
}
Automatic rendering is a hybrid approach that initially renders with Interactive Server and then switches to WebAssembly for subsequent visits after the .NET runtime and app bundle are downloaded and cached. This mode eliminates most if not all user perceived trade-offs while some additional engineering may be required to seamlessly maintain application state.
@page "/auto-render"
@rendermode InteractiveAuto
<button @onclick="UpdateMessage">Click me</button> @message
@code {
private string message = "Not updated yet.";
private void UpdateMessage()
{
// Executes on server initially,
// then client on subsequent visits
message = "Updated with Auto mode!";
}
}
While Blazor doesn’t commonly use the term “Hydration,” it shares similar concepts through its rendering and interactivity modes. Blazor’s render modes include optional server pre-rendering as a speed optimization. When a page is fetched, the server statically renders the HTML and delivers it to the client. During interactivity, the component will perform additional renders depending on the interactivity mode. In this process, the application state needs to be maintained between requests so that the data represented in the pre-rendering persists through interactivity.
PersistentComponentState in Blazor is a state hydration feature that allows you to persist the server state of components during pre-rendering.
@inject PersistentComponentState ApplicationState
@code {
private string? data;
protected override void OnInitialized()
{
if (ApplicationState.TryTake("MyDataKey", out string? persistedData))
{
data = persistedData; // Restore persisted state
}
else
{
data = "Hello, world!"; // Initialize state
ApplicationState.Persist("MyDataKey", data); // Persist state
}
}
}
In this example, when the component is initialized it retrieves the persisted state if available using TryTake
. If no data is available, new data is initialized and saved for future use by the Persist
method. Following this logic, when the component is statically rendered for the first time, it will initialize data and store it in the persisted state. When the component becomes interactive, it will call OnInitialized
again thus rehydrating the state. This approach helps preserve the component’s state across pre-rendering and interactive modes.
Angular, Google’s comprehensive web application framework, offers several rendering strategies to optimize performance and user experience.
In traditional Angular applications, rendering happens entirely on the client. The browser downloads the JavaScript bundle, Angular initializes and then renders the application.
// Standard Angular component with client-side rendering
@Component({
selector: 'app-client-example',
template: `
<h1>Client-Side Rendered Component</h1>
<button (click)="updateMessage()">Click me</button>
<p>{{ message }}</p>
`
})
export class ClientExampleComponent {
message = 'Not updated yet';
updateMessage() {
this.message = 'Updated on the client!';
}
}
The client-side rendering example demonstrates a typical Angular component that renders entirely in the browser. When a user interacts with the button, the updateMessage()
method updates the component’s state client-side, changing the displayed message without any server interaction.
Angular’s server-side rendering generates the initial HTML on the server, which is then sent to the client. This improves initial load time and SEO. SSR with Angular is more comparable to Blazor’s server pre-rendering with client interactivity. However, Angular uses the JavaScript runtime directly and does not require the additional runtime resources like Blazor. In addition, Angular does not offer server interactivity in the same way that Blazor’s Interactive Server mode does.
To enable SSR in an Angular project:
# For new projects
ng new --ssr
# For existing projects
ng add @angular/ssr
Once SSR is enabled, Angular components work the same way, but they render first on the server. The same component code works in both client and server environments:
// This component will first render on the server, then hydrate on the client
@Component({
selector: 'app-ssr-example',
template: `
<h1>Server-Side Rendered Content</h1>
<p>This renders on the server first, then becomes interactive</p>
<button (click)="updateMessage()">Click me</button>
<p>{{ message }}</p>
`
})
export class SsrExampleComponent {
message = 'Not updated yet';
updateMessage() {
this.message = 'Updated after hydration!';
}
}
The above server-side rendering example shows how the same component structure works with SSR enabled. The key difference is that with SSR:
Let’s further examine how Angular implements hydration to bridge the gap between static server-rendered HTML and fully interactive client-side applications.
Hydration is the process that bridges server-side rendering and client-side interactivity. After the server-rendered HTML is delivered, Angular “hydrates” it by attaching event listeners and making it interactive.
// In app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [
provideClientHydration()
]
};
Angular 19 introduced incremental hydration, allowing developers to prioritize which parts of the application should become interactive first. Incremental hydration is a sophisticated enhancement to Angular’s rendering capabilities controlled by a @defer
block and a variety of hydration triggers.
import {
bootstrapApplication,
provideClientHydration,
withIncrementalHydration,
} from '@angular/platform-browser';
...
bootstrapApplication(AppComponent, {
providers: [provideClientHydration(withIncrementalHydration())]
});
This allows developers to optimize application performance. These triggers include hydrate on: idle (during browser idle time), viewport (when content becomes visible), interaction and hover (respond to user engagement), immediate (right after initial content renders), timer (after a specified delay), hydrate when (based on custom conditions) and hydrate never (permanently static). By strategically applying these triggers, developers can prioritize critical UI elements while deferring less important components, resulting in faster initial load times and improved user experience.
@defer (hydrate on viewport) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
In Angular’s incremental hydration, nested deferred blocks follow a hierarchical hydration pattern where parent components must be hydrated before their children, creating a sequential code loading process. To maximize performance, developers should strategically position hydration boundaries around computationally expensive components, implement Angular Signals for efficient cross-boundary state management, design effective loading states with @placeholder content, consider zone-less change detection to reduce overhead, and fully utilize Angular Universal’s server-side rendering capabilities for optimal initial content delivery.
React, Facebook’s popular UI library, has evolved its rendering capabilities significantly, especially with the introduction of React Server Components.
Traditional React components run on the client. They’re downloaded as JavaScript, executed in the browser, and can maintain state and handle user interactions.
'use client';
import { useState } from 'react';
export default function ClientComponent() {
const [message, setMessage] = useState('Not updated yet');
return (
<div>
<h1>Client Component</h1>
<button onClick={() => setMessage('Updated on client!')}>
Click me
</button>
<p>{message}</p>
</div>
);
}
The above code shows a React client component that runs entirely in the browser. The 'use client'
directive at the top explicitly marks it as client-side code, a convention introduced with React Server Components to distinguish between server and client rendering contexts. The component maintains state with useState and updates the message when the button is clicked, all on the client.
React Server Components, introduced in React 19, allow components to render on the server. They can access server resources directly and reduce the JavaScript sent to the client.
React Server Components and Blazor’s Interactive Server mode represent two different approaches to server-side rendering and interactivity, with fundamental architectural differences. Blazor Interactive Server offers a more traditional “thin client” approach with server-driven UI, while React Server Components provide a more hybrid approach that combines server rendering with client interactivity in a more decoupled way. Blazor’s Automatic render mode and React Server Components aim to solve the same problems and share similarities.
// No 'use client' directive means this is a Server Component
import { getServerData } from '../lib/data';
import ClientComponent from './ClientComponent';
export default async function ServerComponent() {
// This runs on the server only
const data = await getServerData();
return (
<div>
<h1>Server Component</h1>
<p>Data from server: {data}</p>
{/* Server Components can render Client Components */}
<ClientComponent initialData={data} />
</div>
);
}
The code above demonstrates a React Server Component. Without the 'use client'
directive, this component runs exclusively on the server. It can directly access server resources and perform async operations like data fetching during the rendering process. The server renders the component with data already included and sends the resulting HTML to the client. As shown above, Server Components can seamlessly render Client Components, creating a hybrid rendering model where server-rendered content can include interactive client-side elements.
When Server Components render Client Components or when using traditional server-side rendering in React, the framework needs a way to make static HTML interactive on the client. Like we’ve seen with Angular, this is where hydration comes in.
React’s hydration process attaches event listeners to server-rendered HTML, making it interactive. This is handled through functions like hydrateRoot
:
import { hydrateRoot } from 'react-dom/client';
import App from './App';
// Assumes the HTML was server-rendered and contains the App structure
hydrateRoot(document.getElementById('root'), <App />);
Each framework has an ecosystem of tools that enhance or simplify server rendering capabilities.
Next.js: The most popular framework for React server rendering, offering three server rendering strategies:
// Next.js page with static rendering
export function getStaticProps() {
return {
props: { data: 'This was rendered at build time' }
};
}
export default function Page({ data }) {
return <div>{data}</div>;
}
Astro: Focuses on content-driven websites with a unique “islands architecture” approach to hydration.
// Astro component with React island
---
// Server-only code (runs at build time)
const title = "Welcome to Astro";
---
<html>
<body>
<h1>{title}</h1>
<!-- React component that hydrates on the client -->
<React.InteractiveComponent client:load />
</body>
</html>
While React has several frameworks for server rendering, Progress KendoReact provides a robust UI component library that works seamlessly with these server rendering solutions:
KendoReact: A professional UI component library with 100+ high-performance React components that are fully compatible with server-side rendering frameworks like Next.js. KendoReact components maintain their functionality and appearance regardless of the rendering approach. Try the free version, with 50+ components available at no cost, no time limit.
Built-in SSR: Server-side rendering is integrated directly into the Angular framework and can be easily set up with the Angular CLI.
// server.ts (created by Angular Universal)
import 'zone.js/node';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { AppServerModule } from './src/main.server';
const app = express();
app.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
app.get('*', (req, res) => {
res.render('index', { req });
});
app.listen(4000);
Angular’s server rendering capabilities through Angular Universal are complemented by Kendo UI for Angular:
Kendo UI for Angular: A complete UI component library with 100+ native Angular components that fully support server-side rendering. Kendo UI for Angular is designed to work seamlessly with Angular Universal, providing consistent behavior across server and client rendering. Check out the 30-day free trial.
Azure SignalR Service: Blazor’s server rendering capabilities are built into the framework and third-party libraries aren’t required. Hosting applications with server-interactive mode can benefit from additional resources like Azure SignalR Service, which allows server-interactivity to scale with minimal effort.
Telerik UI for Blazor: A comprehensive suite of 100+ truly native Blazor UI components that work seamlessly with all Blazor render modes. Telerik UI for Blazor supports both Blazor Server and WebAssembly projects, offering high-performance components like Grid, Charts and Scheduler that maintain their functionality across different render modes. This one also comes with a 30-day free trial—get started.
Comparing the render modes across frameworks:
Framework | Implementation | Pros | Cons |
---|---|---|---|
Blazor | Interactive WebAssembly | Works offline, reduces server load | Larger initial download, slower startup |
Angular | Traditional SPA | Rich interactivity, simpler development | Poorer SEO, slower initial render |
React | Client Components | Full interactivity, familiar model | Larger JS bundles, SEO challenges |
Framework | Implementation | Pros | Cons |
---|---|---|---|
Blazor | Interactive WebAssembly | Works offline, reduces server load | Larger initial download, slower startup |
Angular | Traditional SPA | Rich interactivity, simpler development | Poorer SEO, slower initial render |
React | Client Components | Full interactivity, familiar model | Larger JS bundles, SEO challenges |
Framework | Implementation | Pros | Cons |
---|---|---|---|
Blazor | InteractiveAuto | Best of both worlds, optimized for returning visitors | More complex, requires both server and client setup |
Angular | SSR with Hydration | Good SEO with full interactivity | Potential hydration mismatches |
React | Next.js with mixed components | Flexible, optimized per-page rendering | More complex mental model |
Modern web frameworks have converged on similar rendering strategies, each with their own implementation details. The key similarities include:
The choice of render mode should be driven by your application’s specific requirements around performance, SEO, interactivity and target audience. By understanding the trade-offs between different render modes, you can make informed decisions that result in better user experiences.
As powerful as they are convenient, modern rendering approaches make a great choice for new applications, enabling developers to build fast, interactive and SEO-friendly web experiences without compromising on functionality.
As web frameworks continue to evolve, we can expect even more sophisticated rendering strategies that further optimize the balance between server and client responsibilities. The future of web rendering lies in intelligent, context-aware approaches that deliver the right experience for each user and use case. Understanding these concepts at an architectural level helps developers foster technology independence.
💜 Special thanks to Hassan Djirdeh, Alyssa Nicoll and Kathryn Grayson Nanz for their contributions to this article.
One of the best ways to understand how these three frameworks compare is to run them head-to-head and see what will work best for your needs. Progress offers all three of its corresponding component libraries in the Telerik DevCraft bundle—plus other UI component libraries and an assortment of tools like reporting and mocking. The 30-day free trial includes award-winning support to help you get started.
Ed