Blazor's new CacheView in .NET 11 lets a static server-rendered page reuse a child component's HTML across GET requests.
You can cache a book list for everyone browsing the same category while Blazor renders each shopper's cart on every request.
On a cache hit, the list component never runs.
Which Part of the Page Should You Cache?
Caching the whole catalog page by category would also cache the first shopper's cart badge.
You could add a shopper ID to the key, but then each shopper would have a separate copy of the same list.
Put CacheView around the list and leave the cart badge outside it.
If several pages need the same underlying product data but render different HTML, caching the data in ASP.NET Core lets each page still generate its own markup.
Set Up a Static SSR Page
Create an app with the .NET 11 SDK.
The static SSR template registers AddRazorComponents() and maps MapRazorComponents<App>(), which is enough to try the default in-memory component cache.
dotnet new blazor -n CacheDemo --interactivity None --no-https
cd CacheDemo
Add Components/Pages/Catalog.razor.
The page has its own render number so you can tell when two HTTP responses came from different page instances.
The five-second lifetime makes expiration easy to observe.
@page "/catalog"
<p>Page render: @pageRender</p>
<CacheView VaryByQuery="category" ExpiresAfter="TimeSpan.FromSeconds(5)">
<CategoryList Category="@(Category ?? "all")" />
</CacheView>
@code {
[SupplyParameterFromQuery]
public string? Category { get; set; }
private static int pageRenders;
private readonly int pageRender = Interlocked.Increment(ref pageRenders);
}
Beside it, create Components/Pages/CategoryList.razor.
The child increments its counter in OnInitialized.
If two responses show the same list number and different page numbers, Blazor reused the list.
<p>List render: @listRender (@Category)</p>
@code {
[Parameter]
public string Category { get; set; } = "";
private static int listRenders;
private int listRender;
protected override void OnInitialized()
=> listRender = Interlocked.Increment(ref listRenders);
}
Put expensive work inside the cached child when you want a hit to skip it.
If the parent loads the books before passing them as a parameter, it still performs that lookup every time the parent renders.
Start the app with dotnet run --no-launch-profile --urls http://127.0.0.1:15298.
What Happens on a Cache Hit?
Open a second terminal. Make the first four requests in quick succession, then wait long enough for the first entry to expire. The commands below show the two paragraphs from each response.
for category in books books tools books; do
curl -fsS "http://127.0.0.1:15298/catalog?category=$category" |
grep -oE '<p>(Page|List) render:[^<]*</p>'
done
sleep 6
curl -fsS 'http://127.0.0.1:15298/catalog?category=books' |
grep -oE '<p>(Page|List) render:[^<]*</p>'
Starting with a fresh process, ASP.NET Core 11.0.0 returned these values.
| Request | Page render | List render |
|---|---|---|
| 1. books | 1 | 1 (books) |
| 2. books | 2 | 1 (books) |
| 3. tools | 3 | 2 (tools) |
| 4. books | 4 | 1 (books) |
| 5. books, after expiration | 5 | 3 (books) |
On the second request, Catalog rendered again while CategoryList.OnInitialized never ran.
The third request had a new category and needed its own entry.
The fourth request reused the first books entry.
After six seconds, the same URL created list instance 3.
Which Inputs Belong in the Cache Key?
The Category parameter tells the child what to render.
VaryByQuery="category" tells the cache which requests can share that rendering.
Omit VaryByQuery and the shopper who asks for tools can get a list of books.
For a localized catalog, also set VaryByCulture="true" if the child's output depends on the request culture.
If the category comes from a route such as /catalog/{category}, use VaryByRoute="category".
In a loop rendering multiple lists, give each CacheView a distinct CacheKey, such as the department ID, since multiple iterations share the same position in the component tree.
Keep the cart and permission-dependent actions outside the shared list.
The default cache can serve multiple users, and authorization logic inside a cached child doesn't run again on a hit.
A deleted book can stay in the list until ExpiresAfter elapses.
If stock status needs to update sooner, give it a shorter expiration or render it outside the cached list.
Summary
When you replace the counters with a database lookup, put that lookup in CategoryList.OnInitializedAsync.
Request books twice and check the query log.
You should see one lookup for books, a second when you switch to tools, and a third if you come back to books after the entry expires.
Frequently Asked Questions
What does CacheView cache in Blazor?
It stores the rendered markup of a component subtree during a static server-rendered GET. On a hit, Blazor reuses that markup and skips initializing the cached children. Other components on the page can still render for the request.
Does CacheView refresh when a component parameter changes?
Passing a new value to a child does not automatically create a different entry. Include the input in the cache key, for example with VaryByQuery for category or VaryByCulture for localized markup.
How long does Blazor CacheView keep an entry?
Set ExpiresAfter for an absolute lifetime measured from creation. With the default in-memory store, omitting expiration gives the entry a 30-second lifetime. Setting only ExpiresSliding still leaves that 30-second absolute limit in place.

