Blazor WebAssembly Just Got Smarter: Meet Persistent State in .NET 10 ⚡
All your pre-rendering problems are finally solved in Blazor WebAssembly in .NET 10
If you’ve ever built a Blazor WebAssembly app and hit refresh, you probably noticed something strange.
The page flashes. Your data disappears for a moment, then pops back in.
It feels like your app forgets what it was doing for a split second.
That’s been normal in Blazor for a long time. But not anymore.
With .NET 10, we get something called Persistent State, and it’s a real game-changer.
If you read my previous article about Persistent State in Blazor Server or watched my YouTube tutorial, you already know what this means for server apps.
But today, we’re talking about Blazor WebAssembly, and trust me, this one is even bigger.
💡 Persistent State makes Blazor WebAssembly pages load smoother, faster, and without that annoying flash when pre-rendering is enabled.
Let’s Talk About the Problem
Imagine you’re running a space station dashboard built with Blazor WebAssembly.
Your app shows incoming cargo ships, their fuel levels, and which planets they came from.
When you first open the page, everything looks perfect.
But then you hit refresh.
Suddenly, the list of ships disappears, a “Loading data…” message flashes for a second, and then everything comes back.
If your data changes often or uses random test values, the numbers might even look different after reload.
That quick flash might not seem like much, but it breaks the user experience and makes your app feel less solid.
Why It Happens
Here’s what’s going on behind the scenes:
Blazor WebAssembly starts with pre-rendering on the server.
That’s great for SEO and quick first loads.The browser receives the rendered HTML, so it looks like the page is ready.
But when the client (your browser) starts the interactive part, it doesn’t realize the server already gave it data.
The client goes, “Wait, I better fetch that data myself!” and calls the API again.
This second API call is what causes that short flicker.
Server: "Here’s the cargo list."
Client: "Thanks! I'll ask for it again anyway."And that’s what we’re fixing today.
Building a Simple Example
Let’s make a tiny Blazor WebAssembly app that lists incoming space cargo.
Our model is simple:
public class CargoShip
{
public string Name { get; set; } = string.Empty;
public string Planet { get; set; } = string.Empty;
public int Fuel { get; set; }
}
Then we have a CargoService that returns random ships:
public interface ICargoService
{
IEnumerable<CargoShip> GetRandomShips();
}
public class CargoService : ICargoService
{
private static readonly string[] Planets = { “Mars”, “Venus”, “Jupiter”, “Saturn” };
private static readonly string[] Names = { “Aurora”, “Eclipse”, “Nebula”, “Voyager” };
private readonly Random _random = new();
public IEnumerable<CargoShip> GetRandomShips()
{
return Enumerable.Range(1, 5).Select(_ => new CargoShip
{
Name = Names[_random.Next(Names.Length)],
Planet = Planets[_random.Next(Planets.Length)],
Fuel = _random.Next(10, 100)
});
}
}And an API endpoint:
[Route(”api/[controller]”)]
[ApiController]
public class CargoController(ICargoService cargoService) : ControllerBase
{
[HttpGet]
public IEnumerable<CargoShip> Get() => cargoService.GetRandomShips();
}Finally, our Blazor page:
@page “/cargo”
@using PersistentStateBlazorWebAssembly.Client.Models
@inject HttpClient Http
@rendermode InteractiveWebAssembly
<h3>Incoming Cargo Ships</h3>
@if (ships == null)
{
<p>Loading cargo data...</p>
}
else
{
<ul>
@foreach (var ship in ships)
{
<li>@ship.Name from @ship.Planet (Fuel: @ship.Fuel%)</li>
}
</ul>
}
@code {
private List<CargoShip>? ships;
protected override async Task OnInitializedAsync()
{
ships = await Http.GetFromJsonAsync<List<CargoShip>>(”api/cargo”);
}
}When you run this, it works fine.
But if you refresh, you’ll notice:
The list flashes before it reappears.
The data might change (because it’s random).
The Network tab in the developer tools shows a new API call every time.
The Fix: Add [PersistentState]
Here’s the magic part.
Simply add the new attribute to your list and convert it into a property.
[PersistentState]
public List<CargoShip>? Ships { get; set; }Now Blazor remembers the state from the server and reuses it when the client hydrates.
Your updated page looks like this:
@page “/cargo”
@using PersistentStateBlazorWebAssembly.Client.Models
@inject HttpClient Http
@rendermode InteractiveWebAssembly
<h3>Incoming Cargo Ships</h3>
@if (Ships == null)
{
<p>Loading cargo data...</p>
}
else
{
<ul>
@foreach (var ship in Ships)
{
<li>@ship.Name from @ship.Planet (Fuel: @ship.Fuel%)</li>
}
</ul>
}
@code {
[PersistentState]
public List<CargoShip>? Ships { get; set; }
protected override async Task OnInitializedAsync()
{
if (Ships == null)
{
Ships = await Http.GetFromJsonAsync<List<CargoShip>>(”api/cargo”);
}
}
}Now refresh again.
You’ll notice:
No flicker.
No extra API call.
The data stays consistent.
That’s Persistent State at work.
The server pre-renders your HTML, then hands the client the same data for hydration.
Verify in the Browser
Open the Network tab again.
Reload the page.
You’ll see there’s no new request to api/cargo.
The data already exists inside the HTML the client received.
Blazor simply reuses it.
If you remove [PersistentState] and reload, you’ll see the API call return.
Persistent State bridges the gap between pre-rendered HTML and client hydration.
How It Works
During pre-rendering, Blazor serializes your component’s state.
That includes fields and properties like our ships list.
When the app hydrates on the client, Blazor replays that state, so everything looks exactly the same.
No re-fetching.
No data loss.
No flashing.
Watch Out for This ⚠️
Make sure you have this if-check before loading data:
if (Ships == null)
{
Ships = await Http.GetFromJsonAsync<List<CargoShip>>(”api/cargo”);
}Without it, Blazor will load the data again and overwrite the restored state.
Also, [PersistentState] only works when pre-rendering is enabled.
If you turn that off, you lose the SEO and the feature itself.
When Should You Use It?
Use [PersistentState] on pages that:
Load data from an API during
OnInitializedAsync.Use the Interactive WebAssembly render mode.
Have pre-rendering enabled.
Don’t need live updates after the first load.
Perfect examples include:
Dashboards
Product lists
Profiles
Reports
Article or documentation pages
It’s not meant for things that constantly change, like live chat or stock tickers.
For static or semi-static data, it’s gold.
Why It Matters
Before Persistent State, you had to choose between:
SEO and fast load (pre-rendering on)
or smooth UX (pre-rendering off)
Now, you can have both.
The page loads instantly, looks stable, and stays consistent.
Users won’t see a flicker, and search engines still get full HTML.
Persistent State makes Blazor feel like a polished, professional web framework.
My Thoughts
When I first tested this in .NET 10 RC1, it blew me away.
Blazor WebAssembly finally feels smooth, like the modern apps we expect today.
This tiny attribute solves a problem every Blazor developer has seen.
No hacks. No extra projects. Just clean, reliable state transfer.
If you’ve built Blazor apps before, try it out.
You’ll instantly feel the difference.
Persistent State lets your Blazor WebAssembly pages stay calm and stable, even during reloads.
No flashes, no reloads, no lost data.
P.S. Watch this tutorial on YouTube right here. 👇
P.P.S. Get the complete project source code here. Enjoy!






Will this work even in enhanced navigation too like it does in Server Interactivity enhanced navigation. Because when i tried it in enhanced navigation doing [PersistentState(AllowUpdates = true)] it does not work . I am doing this on Blazor Web App WebAssembly Interactivity.
Please help me on this
Thanks! I typically have my state variables factored into a container object that gets injected via DI. How would I use PersistentState in that case/on that injected instance?