top of page

Prerendering in ASP.NET Core Razor Components

When users land on your Blazor WebAssembly app, a speedy initial load time can make all the difference in keeping them engaged. This is where prerendering comes in – a powerful technique that can significantly improve the perceived responsiveness of your app.


This article is all about prerendering in ASP.NET Core Razor components. We'll explore how it works, its benefits, and how to implement it in your Blazor WebAssembly application.


Prerendering in ASP.NET Core Razor Components

Prerendering in ASP.NET Core Razor Components involves rendering components on the server before sending the initial HTTP response to the client. Here are some benefits of prerendering in ASP.NET Core:

  1. Improved Perceived Load Time: Prerendering makes the app feel more responsive by delivering the HTML UI of the page as soon as possible in response to the initial request.

  2. SEO Enhancement: By rendering content for the initial response, search engines can use it to calculate page rank, improving Search Engine Optimization (SEO).

  3. Interactivity: You can add interactivity to existing views or pages using the Component Tag Helper.


When rendering a page or view in an ASP.NET Core app, blazor components can be prerendered simultaneously. This means the server-side rendering (prerendering) and client-side rendering (Blazor WebAssembly) can occur together. The server renders the initial HTML content, which is then hydrated by Blazor on the client side. This approach provides a seamless user experience by delivering content quickly while allowing for interactivity once the client-side components load.



Server-Side Prerendering

Server-side prerendering involves rendering a web page or component on the server before it reaches the client’s browser.

Prerendering in ASP.NET Core Razor Components
  • When a user requests a page, the server generates the HTML content page.

  • This initial rendering occurs before any client-side JavaScript or interactivity is applied.

  • The server sends this prerendered HTML to the client, making the page appear faster to the user.

  • Later, when the client-side Blazor runtime initializes, it takes over and enables interactivity.


Advantages of Server-Side Prerendering:

  • Users see content faster since the initial rendering happens on the server.

  • The page appears more responsive, reducing perceived load time.

  • Search engines can index the prerendered content, improving page rank.

  • Prerendered pages are more likely to be discoverable by search engines.


Client-Side Blazor Runtime

When a client-side Blazor WebAssembly app is loaded in the browser, the Blazor runtime initializes. This initialization sets up the necessary infrastructure for running Blazor components.


The Blazor runtime is used for managing component lifecycles, rendering, and handling user interactions.


Once the Blazor runtime is initialized, components become interactive. Components are the building blocks of Blazor apps, representing UI elements or functionality.


These components can respond to user input, events, and data changes. They can be customized, composed, and reused throughout the app.


JavaScript Bridge:

Blazor WebAssembly allows you to write C# code that runs directly in the browser. However, some browser APIs and features are only accessible through JavaScript.


To bridge this gap, Blazor uses JavaScript interop. You can call JavaScript functions from C# code and vice versa. This enables seamless integration with existing JavaScript libraries and browser APIs.


Handling User Interactions:

As users interact with the app (e.g., clicking buttons, submitting forms), Blazor handles these interactions without triggering full page reloads.


When a user clicks a button or interacts with a form, Blazor components respond dynamically. Only the affected components are updated, resulting in a smoother user experience than traditional page-based navigation.


Dynamic Component Updates:

Blazor components update dynamically based on changes in data or user interactions. When data within a component changes (e.g., due to user input or server responses), Blazor re-renders the affected parts of the component. This process is efficient and avoids unnecessary full-page reloads.



Types of Prerendering

There are three different prerendering modes:

  • No Prerendering: Pure client-side rendering.

  • Prerendering with JavaScript: Initial rendering on the server, followed by client-side interactivity.

  • Prerendering without JavaScript: Full prerendering without client-side JavaScript.


Let’s compare the different types of prerendering in ASP.NET Core (specifically Blazor):

Prerendering Mode

Description

Use Cases

Working

When to Use

Static

Statically render the component with the specified parameters.

-Simple pages with minimal interactivity. -Content that doesn’t change frequently.

- Renders the component as static HTML during initial server-side rendering. - No client-side interactivity until the Blazor runtime initializes.

-When your application relies heavily on dynamic content and real-time interactivity.

-Use cases include single-page applications (SPAs) with complex user interfaces (UIs) that require frequent updates.

Server

Renders a marker where the component should be rendered interactively by the Blazor Server app.

- Balances performance and interactivity. - Suitable for most scenarios.

- Initial rendering on the server includes a marker. - Later, the client-side Blazor runtime takes over for interactivity.

-When you want to improve perceived load time by delivering initial content quickly.

-It is useful for SEO purposes since search engines can index the prerendered content.

-It is commonly used in Blazor applications to strike a balance between performance and interactivity.

ServerPrerendered

Statically prerenders the component along with a marker for later interactive rendering.

- Optimized for SEO (search engine optimization). - Quick initial content delivery. - Time-based positioning in search results.

- Pre-renders the page as static HTML with a marker. - Later transitions to an interactive Blazor server app.

-When SEO is critical, and you want search engines to fully index your content.

-Ideal for static websites or pages with minimal interactivity.

-Consider this mode for landing pages, blogs, or documentation sites.


Prerendering Strategies

Handling dynamic data during prerendering in Blazor WebAssembly involves some considerations and strategies.


1. Static Data Components:

These components have fixed content that doesn’t change based on user interactions or external data.


Strategies:

  • Purely Static Components: For content like landing pages, about us sections, or static marketing material, prerendering the entire component is ideal. The content remains unchanged until the next full page load.

  • Use Static Parameters: If a component has some dynamic parts but mostly static content, you can pass parameters to it during prerendering. These parameters remain constant until they become interactive.


2. Dynamic Data Components:

These components fetch data from APIs, databases, or other external sources. They need to update their content based on user actions or real-time data.


Challenges:

  • Initial Load Time: Fetching data during prerendering can increase the initial load time. Users might see a loading spinner until the data arrives.

  • SEO and Real-Time Updates: Balancing SEO benefits (static content) with real-time updates (dynamic data) can be tricky.

  • Data Consistency: Ensuring the prerendered content matches the data fetched later can be challenging.


Strategies to Address Challenges:

  • Hybrid Prerendering:

  • Use a combination of static and dynamic prerendering. Prerender the static parts and leave placeholders for dynamic content.

  • When the app becomes interactive, replace the placeholders with actual data fetched dynamically.

  • Lazy Loading:

  • Fetch dynamic data after the initial load. Show placeholders or loading indicators until the data arrives.

  • Use JavaScript interop to fetch data asynchronously.

  • SEO Considerations:

  • If SEO is crucial, prioritize static prerendering for critical content.

  • For dynamic parts, consider client-side rendering after the initial load.

  • Data Validation and Consistency:

  • Validate prerendered data against actual data during interactive rendering.

  • Handle discrepancies gracefully (e.g., show an error message if data doesn’t match).


Example Scenario: Persisting Prerendered State

Persisting Prerendered State retains or preserves the initial state of a component during server-side prerendering in ASP.NET Core Razor applications.


When building Blazor apps, maintaining user state (data) is crucial for a seamless experience. The requested page doesn’t yet exist in the browser, and storage options are limited during prerendering. However, we can take steps to persist important states across circuits (user sessions) and ensure a consistent experience.


PersistentComponentState Service

The PersistentComponentState service allows us to register callbacks for state persistence. Here’s how it works:

  1. Registering a Callback:

  • Use PersistentComponentState.RegisterOnPersisting to register a callback that runs before the app is paused (e.g., during prerendering).

  • In this callback, you can save the relevant component state you want to retain.

  1. Retrieving State on Resume:

  • The saved state is retrieved when the app resumes (after prerendering).

  • This ensures that your component can restore its initial state even if the user navigates away and returns later.


Here are the steps on how to use the PersistentComponentState service in your Blazor app to persist state during server-side prerendering:


Before using the PerisitentComponentState service, you have to configure the solution to enable prerendering. For that, follow the below steps:


STEP 1: Hosted Blazor WebAssembly App:

Host your Blazor WebAssembly app within an ASP.NET Core app. You have two options:

  • Standalone Blazor WebAssembly App: Add a standalone Blazor WebAssembly app to an existing solution.

  • Hosted Blazor WebAssembly App: Create a hosted Blazor WebAssembly app using the project template.


In Visual Studio, select the “ASP.NET Core Hosted” checkbox when creating the Blazor WebAssembly app. Alternatively, use the .NET CLI with the -ho (or --hosted) option.


STEP 2: Remove Default Index.html:

Delete the wwwroot/index.html file from the Blazor WebAssembly client project.


By default, Blazor WebAssembly apps use index.html as the entry point for the client-side runtime. However, when we want to enable prerendering (server-side rendering), we need a custom entry point that allows us to control the initial rendering process.


The custom _Host.cshtml file (instead of index.html) serves as this entry point during prerendering. It allows us to include both static content and placeholders for dynamic components.


STEP 3: Add the Tag Helper:

  • In your _Host.cshtml (for Blazor apps) or Layout.cshtml (for embedded components), add the <persist-component-state /> tag inside the closing </body> tag.

  • This ensures that the state is saved during prerendering.


STEP 4: Decide What State to Persist:

  • Determine which component state you want to retain. It could be anything from user preferences to form input values.

  • Register a callback using PersistentComponentState.RegisterOnPersisting to save the state before the app pauses (e.g., during prerendering). This callback runs just before the rendered HTML is sent to the client.

  • The saved state will be retrieved when the app resumes.


Let’s say we have a simple counter component. We want to persist the counter value during prerendering:

<!-- In your _Host.cshtml or Layout.cshtml -->
<body>
    <!-- Other content -->
    <persist-component-state />
</body>

<!-- Counter.razor -->
@page "/counter"

<h3>Counter</h3>
<p>Current count: @count</p>

<button @onclick="Increment">Increment</button>

@code {
    private int count = 0;

    private void Increment()
    {
        count++;
    }

    protected override void OnInitialized()
    {
        // Register the callback for state persistence
        PersistentComponentState.RegisterOnPersisting(PersistState);
    }

    private void PersistState()
    {
        // Save the counter value to local storage or another storage mechanism
        // (Note: Local storage is not available during prerendering)
        // For demonstration purposes, let's assume we're using a hypothetical storage service
        MyStorageService.Save("counterValue", count);
    }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            // Retrieve the counter value when the app resumes
            count = MyStorageService.Load<int>("counterValue");
            StateHasChanged(); // Trigger a re-render
        }
    }
}

In this example:

  • We register the PersistState method to save the counter value during prerendering.

  • When the app resumes, we retrieve the value from our hypothetical storage service (MyStorageService).


Remember:-

The PersistentComponentState service works only during the initial page load and not across enhanced page navigation events. When the app performs a full navigation, such as reloading the entire page or navigating to a different route, the persisted state becomes available again. The saved state is retrieved, allowing your components to restore their initial state.


Limitations of using PersistentComponentState for large datasets

When using PersistentComponentState for large datasets, there are some important limitations to consider:


Memory Usage:

  • Storing large datasets in memory during prerendering can consume significant memory resources.

  • If your dataset is too large, it might lead to performance issues or even cause the application to crash.


Network Latency:

  • Network latency can impact the overall page load time if your large dataset is fetched from an external API during prerendering.

  • Users might experience delays while waiting for the data to be retrieved.


Serialization and Deserialization Overhead:

  • Persisting large objects involves serialization (converting objects to a format suitable for storage) and deserialization (retrieving objects from storage).

  • For large datasets, these operations can be time-consuming and affect performance.


Security Considerations:

  • Be cautious when persisting sensitive data in memory during prerendering.

  • Ensure that the sensitive information (such as user credentials or tokens) is handled securely.


Browser Compatibility:

  • Some older browsers or devices might struggle with large datasets during prerendering.

  • Test thoroughly across different browsers and devices to ensure compatibility.


Garbage Collection:

  • Large datasets may increase the garbage collection cycles, affecting overall app performance.

  • Monitor memory usage and optimize as needed.


Use cases for persisting state

User Authentication and Authorization:

  • Retain user authentication state during prerendering to ensure a seamless experience when transitioning to the client-side Blazor runtime.

  • For example, if a user is already authenticated, you can persist their authentication token or session information.


Initial Configuration or Settings:

  • Store initial configuration settings or user preferences during prerendering.

  • These settings can be used to customize the user experience once the client-side Blazor runtime takes over.


Cached Data or API Responses:

  • Fetch data from APIs during prerendering and cache it.

  • When the client-side runtime initializes, use the cached data to populate components without additional API calls.


Form State and Input Values:

  • Persist form input values during prerendering.

  • This ensures that user-entered data (e.g., form fields) is not lost when transitioning to the interactive mode.


Localization and Language Preferences:

  • Store the user’s preferred language or localization settings during prerendering.

  • Apply these settings when rendering localized content on the client side.


Custom Themes or Styling:

  • Persist theme or styling choices made by the user during prerendering.

  • Apply the chosen theme once the client-side Blazor runtime initializes.


Code Example

Let’s create a simplified version of the PrerenderedCounter1 component in Blazor WebAssembly.


The PrerenderedCounter1 component sets an initial random counter value during prerendering and then updates it when the component is re-rendered. We’ll use the OnInitialized lifecycle method to achieve this.


This is the Razor component file where we define our counter component.

@page "/prerendered-counter-1"

<PageTitle>Prerendered Counter 1</PageTitle>

<h1>Prerendered Counter 1</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount;

    protected override void OnInitialized()
    {
        // Set an initial random counter value during prerendering
        currentCount = Random.Shared.Next(100);
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

In the above code:

  1. @page "/prerendered-counter-1": Specifies the route for this component.

  2. <button>: Clicking this button increments the counter.

  3. @code: Contains the C# code for the component.

  4. OnInitialized(): Overrides the initialization method. We set the initial count here.

  5. IncrementCount(): Method to increase the counter value.


Behavior:

  1. During prerendering, the initial count is set (e.g., 41).

  2. After the SignalR connection to the client is established, the component rerenders, and the count updates (e.g., 92).

  3. There might be a slight UI flicker during this transition.


Conclusion

In this article, we explored the powerful technique of prerendering in ASP.NET Core Razor components. By rendering content on the server before the client-side Blazor runtime fully initializes, we can achieve better performance and improved SEO.

bottom of page