Data fetching, caching and synchronization for Blazor,
without the boilerplate

Declare a query, bind its loading and error state, and let a shared keyed cache dedupe the requests. Mutations invalidate keys so every component showing that data refetches itself, and navigating away cancels what's in flight — down to the server. Blazor WebAssembly and Server, dependency-free.

$ dotnet add package Deja
Todos.razor
@page "/todos"
@inherits DejaComponentBase
@inject TodoApi Api

@if (_todos.IsLoading) { <Spinner/> }
@if (_todos.IsError) { <Alert>@_todos.ErrorMessage</Alert> }

<ul>
    @foreach (var todo in _todos.Data ?? [])
    {
        <li>@todo.Title</li>
    }
</ul>

@code {
    private readonly Query<List<Todo>> _todos = new();

    protected override Task OnInitializedAsync()
        => _todos.Execute("todos", Api.GetTodosAsync);
}

⚡ Bindable async state

Declare a Query<T>, bind IsLoading, IsError and Data in markup. No flags, no try/catch, no StateHasChanged.

🗄 Shared query cache

Components sharing a key render instantly from cache, join one in-flight request, and revalidate in the background when stale.

✂️ Automatic cancellation

Navigating away aborts in-flight requests down to the server. A newer load supersedes and cancels an older one, so stale responses never win.

🔒 Component isolation

State notifies exactly one owner. Two side-by-side components can never re-render each other — provable in the isolation demo.

✍️ Mutations & invalidation

Mutation<T> tracks writes and invalidates query keys by prefix on success, so every affected list refetches itself.

📦 Dependency-free

No JSON serializers, no reflection-heavy magic — trimming- and AOT-safe, multi-targeting net8.0 through net10.0.

If you know React Query (TanStack Query), Deja is that model of server-state management rebuilt for Blazor in idiomatic C#. These docs run on Deja itself — every demo is the real library fetching from a live API.