Why Persist State to Browser Storage
Blazor components lose all in-memory state on a full page reload in WebAssembly, and Blazor Server loses circuit state entirely if the SignalR connection drops and can't reconnect, so persisting anything that should survive those events, like a user's theme preference or a partially filled form, requires writing it into the browser's storage APIs rather than keeping it only in a C# field. Blazor has no native storage API, so this always goes through JavaScript interop, either hand-written or via a NuGet package like Blazored.LocalStorage.
Cricket analogy: A scorer who only keeps the tally on a chalkboard loses everything if rain washes it away mid-match, just as a Blazor component's in-memory state vanishes on reload unless it's written somewhere durable like localStorage.
localStorage vs. sessionStorage
localStorage persists key-value string data with no expiration until explicitly cleared, and it's shared across every tab and window for the same origin, making it right for durable preferences like theme or language. sessionStorage is scoped to a single browser tab and is cleared the moment that tab closes, even if the same page is reopened in a new tab, which makes it appropriate for data that should not survive beyond the current visit, like a multi-step form's in-progress draft.
Cricket analogy: A player's career batting average is recorded permanently in the official records regardless of which match you're watching, like localStorage persisting across every tab; a single innings' live scorecard resets each new match, like sessionStorage resetting per tab.
Accessing Browser Storage from Blazor
Because browser storage APIs are JavaScript, not .NET, Blazor reaches them through IJSRuntime.InvokeAsync, calling a small JS function like localStorage.setItem(key, value) that you either hand-write in a wwwroot .js file or install via the Blazored.LocalStorage package, which wraps that interop in an injectable ILocalStorageService with typed GetItemAsync<T> and SetItemAsync<T> methods. Blazor Server code must additionally guard against calling storage during prerendering, since there's no browser JS runtime available until the SignalR circuit actually connects.
Cricket analogy: A team's video analyst can't request footage from the broadcaster's system until the satellite uplink is actually connected, similar to how Blazor Server can't call JS interop for localStorage until the SignalR circuit connects after prerendering.
// wwwroot/storageInterop.js
window.storageInterop = {
setItem: (key, value) => localStorage.setItem(key, value),
getItem: (key) => localStorage.getItem(key)
};@inject IJSRuntime JS
@inject Blazored.LocalStorage.ILocalStorageService LocalStorage
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var theme = await JS.InvokeAsync<string>("storageInterop.getItem", "theme");
currentTheme = theme ?? "light";
draftForm = await LocalStorage.GetItemAsync<CheckoutDraft>("checkout-draft")
?? new CheckoutDraft();
StateHasChanged();
}
}
private async Task SaveDraftAsync()
=> await LocalStorage.SetItemAsync("checkout-draft", draftForm);
}Storage calls should only happen after the component has actually rendered, since IJSRuntime.InvokeAsync requires a live browser JS environment: use OnAfterRenderAsync(firstRender) in Blazor Server, or note that Blazor WASM has no prerendering step by default so storage calls in OnInitializedAsync generally work fine there.
Both localStorage and sessionStorage store plain, unencrypted strings that are fully readable by any script running on the page or through the browser's dev tools, so sensitive data like auth tokens or personal information should never be stored there without additional encryption, and ideally should use an httpOnly cookie instead.
- localStorage persists indefinitely across tabs and browser restarts until explicitly cleared
- sessionStorage is scoped to one tab and clears when that tab closes
- Blazor has no native storage API; access always goes through JS interop via IJSRuntime
- Blazored.LocalStorage wraps interop in a typed, injectable ILocalStorageService
- Blazor Server must wait for the SignalR circuit to connect before storage calls succeed (no interop during prerendering)
- Storage data is plain-text and readable via browser dev tools — never store secrets there unencrypted
- OnAfterRenderAsync(firstRender) is the safe place for storage calls in Blazor Server components
Practice what you learned
1. What's the key difference between localStorage and sessionStorage?
2. Why can't a Blazor component call localStorage directly in C#?
3. Why might calling JS interop for storage fail early in a Blazor Server component's lifecycle?
4. What is a security risk of storing an auth token directly in localStorage?
5. What does Blazored.LocalStorage's ILocalStorageService.GetItemAsync<T> do that raw JS interop doesn't handle automatically?
Was this page helpful?
You May Also Like
State Management Patterns
Explore techniques for sharing and persisting state across Blazor components, from cascading parameters to dedicated state containers.
JavaScript Interop
Call JavaScript from C# and C# from JavaScript in Blazor to access browser APIs that .NET can't reach directly.
HttpClient and Calling APIs
Learn how to configure and use HttpClient in Blazor Server and WebAssembly to call REST APIs safely and efficiently.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics