Telerik blogs

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.

Why Render Modes Matter

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:

  1. Performance: Different render modes offer varying performance characteristics, affecting metrics like First Contentful Paint (FCP) and Time to Interactive (TTI).
  2. SEO: Search engines require HTML content to properly index pages. Server-side rendering provides this content immediately, while client-side rendering requires additional optimization.
  3. User experience: The rendering approach directly impacts how quickly users see and interact with your content.
  4. Resource usage: Server rendering can reduce client-side processing requirements but may increase server load.
  5. Development experience: Different render modes affect code structure and state management approaches.

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 Render Modes

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 Rendering

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 Rendering

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!";
    }
}

Interactive WebAssembly (Client) Rendering

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 (Auto) Rendering

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!";
    }
}

State Persistence During Rendering

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 Render Modes

Angular, Google’s comprehensive web application framework, offers several rendering strategies to optimize performance and user experience.

Client-Side Rendering (CSR)

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.

Server-Side Rendering (SSR)

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:

  1. The component initially renders on the server, generating HTML that’s immediately visible to users and search engines.
  2. This HTML is sent to the browser, allowing for a faster First Contentful Paint.
  3. Angular then “hydrates” the component on the client, attaching event handlers to make it interactive.
  4. After hydration, the component behaves identically to a client-rendered component.

Let’s further examine how Angular implements hydration to bridge the gap between static server-rendered HTML and fully interactive client-side applications.

Hydration

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()
  ]
};

Incremental Hydration

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 Render Modes

React, Facebook’s popular UI library, has evolved its rendering capabilities significantly, especially with the introduction of React Server Components.

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

Server Components

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.

Hydration

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 />);

Third-Party Tools

Each framework has an ecosystem of tools that enhance or simplify server rendering capabilities.

For React

Next.js: The most popular framework for React server rendering, offering three server rendering strategies:

  • Static rendering: Pre-renders pages at build time
  • Dynamic rendering: Renders pages at request time
  • Streaming: Progressively renders UI from the server
// 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.

For Angular

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.

For Blazor

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.

Comparison of Approaches and Trade-offs

Comparing the render modes across frameworks:

Server Rendering

FrameworkImplementationProsCons
BlazorInteractive WebAssemblyWorks offline, reduces server loadLarger initial download, slower startup
AngularTraditional SPARich interactivity, simpler developmentPoorer SEO, slower initial render
ReactClient ComponentsFull interactivity, familiar modelLarger JS bundles, SEO challenges

Client Rendering

FrameworkImplementationProsCons
BlazorInteractive WebAssemblyWorks offline, reduces server loadLarger initial download, slower startup
AngularTraditional SPARich interactivity, simpler developmentPoorer SEO, slower initial render
ReactClient ComponentsFull interactivity, familiar modelLarger JS bundles, SEO challenges

Hybrid Approaches

FrameworkImplementationProsCons
BlazorInteractiveAutoBest of both worlds, optimized for returning visitorsMore complex, requires both server and client setup
AngularSSR with HydrationGood SEO with full interactivityPotential hydration mismatches
ReactNext.js with mixed componentsFlexible, optimized per-page renderingMore complex mental model

Best Practices for Choosing the Right Render Mode

When to Use Server Rendering

  1. Content-focused sites: Blogs, news sites and documentation benefit from server rendering for SEO and fast initial load.
  2. Low-powered client devices: Server rendering offloads processing to the server, benefiting users on mobile or low-end devices.
  3. Dynamic content that changes frequently: Server rendering enables users to see the latest content.

When to Use Client Rendering

  1. Highly interactive applications: Apps with complex user interactions benefit from client-side rendering.
  2. Offline capabilities: Applications that need to work without a network connection should use client rendering.
  3. Reduced server load: For applications with many concurrent users, client rendering can reduce server resource usage.

When to Use Hybrid Approaches

  1. Ecommerce sites: Product listings can be server-rendered for SEO, while interactive elements like shopping carts can be client-rendered.
  2. Dashboards: Static content can be server-rendered for fast initial load, while interactive charts and filters can be client-rendered.
  3. Progressive enhancement: Start with server rendering for core content and enhance with client interactivity as resources load.

Framework-Specific Recommendations

Blazor

  • Use static server for content-heavy pages with minimal interactivity.
  • Use interactive server for applications with frequent small updates.
  • Use interactive WebAssembly for offline-capable applications.
  • Use auto for applications with returning users who benefit from caching.

Angular

  • Use client-side rendering for internal applications where SEO isn’t a concern.
  • Use server-side rendering for public-facing sites that need SEO.
  • Use incremental hydration for large applications to prioritize critical UI elements.

React

  • Use Server Components for data-fetching and content rendering.
  • Use Client Components for interactive elements.
  • Consider Next.js to leverage its flexible rendering options.

Conclusion

Modern web frameworks have converged on similar rendering strategies, each with their own implementation details. The key similarities include:

  1. All three frameworks support both server and client rendering.
  2. All three use some form of hydration to bridge server rendering with client interactivity.
  3. All three are moving toward hybrid approaches that combine the benefits of server and client rendering.

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.

Credits

💜 Special thanks to Hassan Djirdeh, Alyssa Nicoll and Kathryn Grayson Nanz for their contributions to this article.


Check Out Telerik DevCraft

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.

Telerik DevCraft - the most complete software development tooling - try now

Try Now


About the Author

Ed Charbeneau

Ed Charbeneau is a web enthusiast, speaker, writer, design admirer, and Developer Advocate for Telerik. He has designed and developed web based applications for business, manufacturing, systems integration as well as customer facing websites. Ed enjoys geeking out to cool new tech, brainstorming about future technology, and admiring great design. Ed's latest projects can be found on GitHub.

Related Posts

Comments

Comments are disabled in preview mode.