.NET 9 Blazor: Pełnostackowy rozwój aplikacji z Blazor United w 2026

.NET 9 Blazor United łączy statyczny SSR, Server i WebAssembly w jednym frameworku full-stack. Praktyczny poradnik obejmujący tryby renderowania, streaming rendering, wstrzykiwanie zależności i wzorce produkcyjne.

Pełnostackowy rozwój z .NET 9 Blazor United i wieloma trybami renderowania

.NET 9 wprowadza Blazor United — architekturę, która scala trzy dotychczas rozdzielne strategie renderowania w jeden spójny model programowania. Deweloperzy zyskują możliwość łączenia statycznego Server-Side Rendering, interaktywności przez SignalR oraz WebAssembly w obrębie tego samego komponentu, a nawet tej samej strony. Eliminuje to kompromisy między wydajnością pierwszego załadowania, SEO a możliwościami aplikacji działających całkowicie po stronie klienta. Blazor United to odpowiedź na rosnące wymagania współczesnych aplikacji webowych, które muszą być jednocześnie szybkie, interaktywne i łatwe w utrzymaniu.

Czym jest Blazor United?

Blazor United to zunifikowany model renderowania w .NET 9, który pozwala mieszać tryby statyczne, Server (SignalR) i WebAssembly w jednej aplikacji bez konieczności tworzenia oddzielnych projektów. Komponent może renderować się statycznie dla SEO, a po załadowaniu przełączyć się na interaktywność przez WebAssembly — wszystko automatycznie.

Konfiguracja projektu .NET 9 Blazor Web App

Rozpoczęcie pracy z Blazor United wymaga .NET 9 SDK. Nowy szablon blazorweb umożliwia wybór trybu interaktywności już podczas tworzenia projektu. Opcja --interactivity Auto włącza hybrydowe renderowanie — serwer obsługuje pierwszą interakcję, a następnie aplikacja przełącza się na WebAssembly po pobraniu runtime'u w tle.

bash
# Create a new Blazor Web App with all render modes enabled
dotnet new blazorweb -n FullStackApp --interactivity Auto --all-interactive false
cd FullStackApp
dotnet run

Parametr --all-interactive false wymusza selektywną interaktywność — domyślnie komponenty renderują się statycznie, chyba że explicite zaznaczono inaczej przez atrybut @rendermode. To podejście optymalizuje transfer danych i eliminuje niepotrzebne połączenia SignalR dla treści, która nie wymaga dynamiki.

Tryby renderowania i ich zastosowania

Blazor United opiera się na czterech trybach renderowania, które można przypisywać zarówno całym stronom, jak i pojedynczym komponentom. Każdy tryb ma specyficzne zastosowania zależne od wymagań dotyczących wydajności, SEO i interaktywności.

| Render Mode | Hosting | Interactivity | Best For | |---|---|---|---| | Static SSR | Server | None | Marketing pages, docs, SEO content | | Interactive Server | Server via SignalR | Full | Dashboards, CRUD, forms | | Interactive WebAssembly | Browser via WASM | Full | Offline, heavy client-side logic | | Interactive Auto | Server then WASM | Full | Best of both — fast load + independence |

Statyczny SSR generuje czysty HTML bez JavaScript — idealny dla landing pages, dokumentacji i wszystkiego, co musi być indeksowane przez wyszukiwarki. Interactive Server utrzymuje połączenie WebSocket i wykonuje kod C# na serwerze, co eliminuje opóźnienia sieciowe dla aplikacji wymagających częstych aktualizacji UI. WebAssembly przenosi całą logikę do przeglądarki, umożliwiając offline-first experiences i złożone operacje bez obciążania serwera. Auto mode łączy oba — pierwsze kliknięcie obsługuje serwer, a w międzyczasie pobiera runtime WASM dla przyszłych interakcji.

Components/Pages/Dashboard.razorcsharp
@page "/dashboard"
@rendermode InteractiveServer

<PageTitle>Dashboard</PageTitle>

<h1>Real-Time Metrics</h1>

<!-- This component renders interactively via SignalR -->
<MetricsChart />

<!-- Static content below renders as plain HTML -->
<footer>Updated every 5 seconds</footer>

W powyższym przykładzie cała strona /dashboard renderuje się przez SignalR. Oznacza to, że każde zdarzenie (kliknięcie, input) wysyła wiadomość do serwera, który przetwarza logikę i zwraca zaktualizowany fragment DOM. Ten tryb sprawdza się w dashboardach czasu rzeczywistego, gdzie dane zmieniają się co kilka sekund i wymagają synchronizacji z bazą danych.

Adaptacyjne komponenty mogą wykrywać kontekst renderowania i dostosowywać zachowanie:

Components/Shared/AdaptiveComponent.razorcsharp
@if (RendererInfo.IsInteractive)
{
    <button @onclick="HandleClick">Interactive Action</button>
}
else
{
    <a href="/fallback">Static Fallback Link</a>
}

@code {
    private void HandleClick()
    {
        // Runs only in interactive modes (Server or WASM)
    }
}

RendererInfo.IsInteractive zwraca true tylko w trybach Server, WebAssembly i Auto. Dzięki temu ten sam komponent może być używany zarówno w statycznych dokumentach, jak i interaktywnych modułach aplikacji — w pierwszym przypadku wyświetli link, w drugim przycisk z logiką C#.

Streaming rendering dla wydajności postrzeganej

Atrybut [StreamRendering] włącza progresywne renderowanie HTML. Zamiast czekać na zakończenie wszystkich asynchronicznych operacji, Blazor wysyła fragmenty strony w miarę ich gotowości. Użytkownik widzi szkielet UI niemal natychmiast, a treść pojawia się stopniowo w tle.

Components/Pages/Products.razorcsharp
@page "/products"
@attribute [StreamRendering]

<PageTitle>Product Catalog</PageTitle>

@if (products is null)
{
    <p>Loading products...</p>
}
else
{
    <div class="product-grid">
        @foreach (var product in products)
        {
            <ProductCard Item="@product" />
        }
    </div>
}

@code {
    private List<Product>? products;

    // Data loads asynchronously; streaming pushes updates to the browser
    protected override async Task OnInitializedAsync()
    {
        products = await ProductService.GetAllAsync();
    }
}

Podczas pierwszego renderu przeglądarka otrzymuje HTML z komunikatem "Loading products...". Gdy ProductService.GetAllAsync() zakończy pobieranie danych, Blazor wysyła aktualizację przez ten sam strumień HTTP, zastępując placeholder listą produktów. Z perspektywy użytkownika strona ładuje się dwuetapowo — najpierw nagłówek i layout, potem zawartość. Redukuje to postrzegane opóźnienie, szczególnie przy wolnych zapytaniach do bazy danych.

Streaming rendering działa tylko w trybie statycznego SSR. Jeśli komponent ma @rendermode InteractiveServer, atrybut [StreamRendering] zostanie zignorowany — SignalR zarządza aktualizacjami przez własny protokół.

Wstrzykiwanie zależności przez konstruktory

.NET 9 wprowadza primary constructors dla komponentów Razor. Zamiast deklarować właściwości z [Inject], można przekazać zależności bezpośrednio w konstruktorze, co poprawia czytelność i eliminuje boilerplate.

Components/Pages/OrderHistory.razorcsharp
@page "/orders"
@rendermode InteractiveServer

<h2>Order History</h2>

@if (orders is not null)
{
    <table>
        <thead>
            <tr><th>Order ID</th><th>Date</th><th>Total</th></tr>
        </thead>
        <tbody>
            @foreach (var order in orders)
            {
                <tr>
                    <td>@order.Id</td>
                    <td>@order.PlacedAt.ToShortDateString()</td>
                    <td>@order.Total.ToString("C")</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    // Constructor injection via primary constructor
    [Inject] public required IOrderService OrderService { get; set; }
    [Inject] public required NavigationManager Nav { get; set; }

    private List<Order>? orders;

    protected override async Task OnInitializedAsync()
    {
        orders = await OrderService.GetRecentOrdersAsync();
    }
}

W tym przykładzie IOrderService i NavigationManager są wstrzykiwane przez kontenera DI w momencie tworzenia instancji komponentu. Modifier required wymusza inicjalizację przed pierwszym użyciem, co chroni przed błędami NullReferenceException w runtime. Taka składnia jest spójna z resztą ekosystemu C# 12 i ułatwia testowanie — wystarczy stworzyć mock serwisu i przekazać go do komponentu w unit teście.

Gotowy na rozmowy o .NET?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Interactive Auto — najlepsze z dwóch światów

Tryb Auto łączy Server i WebAssembly w jednym komponencie. Pierwsza interakcja (kliknięcie, input) jest obsługiwana przez SignalR, jednocześnie w tle pobiera się runtime .NET WASM. Po zakończeniu pobierania kolejne interakcje wykonują się lokalnie w przeglądarce bez opóźnień sieciowych.

Components/Pages/Chat.razorcsharp
@page "/chat"
@rendermode InteractiveAuto

<h2>Live Chat</h2>

<div class="chat-messages">
    @foreach (var msg in messages)
    {
        <div class="message">@msg.Author: @msg.Text</div>
    }
</div>

<input @bind="newMessage" @onkeydown="HandleKey" placeholder="Type a message..." />

@code {
    [Inject] public required IChatService ChatService { get; set; }

    private List<ChatMessage> messages = new();
    private string newMessage = string.Empty;

    protected override async Task OnInitializedAsync()
    {
        messages = await ChatService.GetRecentMessagesAsync();
    }

    private async Task HandleKey(KeyboardEventArgs e)
    {
        if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(newMessage))
        {
            await ChatService.SendAsync(newMessage);
            newMessage = string.Empty;
        }
    }
}

Podczas pierwszego wejścia na /chat przeglądarka otrzymuje HTML z listą wiadomości. Jeśli użytkownik od razu zacznie pisać, zdarzenie @onkeydown trafia do serwera przez SignalR — odpowiedź przychodzi w ciągu 20–50 ms zależnie od latencji. Równolegle w tle ładuje się 1.5–2 MB runtime .NET (skompresowany gzip). Po zakończeniu pobierania Blazor automatycznie przełącza renderowanie na WebAssembly — kolejne naciśnięcia klawiszy wykonują się lokalnie bez żadnego ruchu sieciowego. Użytkownik nie zauważa momentu przełączenia, a UX pozostaje płynny.

Interactive Auto eliminuje kompromis między szybkością pierwszego załadowania a responsywnością aplikacji. Sprawdza się w formularzach z walidacją po stronie klienta, edytorach WYSIWYG i wszystkich interfejsach wymagających natychmiastowej reakcji na input użytkownika. Jest to domyślna rekomendacja dla większości nowych projektów Blazor w 2026 roku, co często pojawia się w blazor interview questions 2026 jako best practice.

Resilient connections — obsługa utraconych połączeń

Tryb Interactive Server opiera się na WebSocket. Utrata połączenia (zmiana sieci, uśpienie laptopa, restart serwera) przerywa działanie aplikacji. .NET 9 wprowadza konfigurowalny mechanizm ponownego łączenia z wykładniczym backoff.

wwwroot/app.js — Custom reconnection configurationjavascript
Blazor.start({
    circuit: {
        reconnectionOptions: {
            retryIntervalMilliseconds: (retryNumber) => {
                // Exponential backoff: 200ms, 400ms, 800ms, max 30s
                return Math.min(200 * Math.pow(2, retryNumber), 30000);
            },
            maxRetries: 15
        }
    }
});

Domyślna konfiguracja próbuje połączyć się 8 razy z interwałami 0, 2s, 10s, 30s. W środowiskach z niestabilnym internetem (mobile, hotspot) warto zwiększyć maxRetries do 15 i skrócić początkowe opóźnienie do 200 ms. Wykładniczy backoff zapobiega przeciążeniu serwera w przypadku masowej utraty połączeń (np. restart klastra Kubernetes).

Blazor wyświetla domyślny UI podczas próby ponownego połączenia — niebieski pasek na dole ekranu z tekstem "Attempting to reconnect...". Można go zastąpić własnym komponentem przez nadpisanie elementu #components-reconnect-modal w CSS lub całkowitą podmianę logiki w JavaScript.

W trybie Auto po utracie połączenia SignalR aplikacja automatycznie przełącza się na WebAssembly (jeśli runtime został już pobrany). Oznacza to, że użytkownik może kontynuować pracę offline — formularz będzie działał lokalnie, a wysyłka danych nastąpi po przywróceniu łączności. To kluczowa przewaga nad czystym Interactive Server w aplikacjach mission-critical.

Selektywna interaktywność dla optimum wydajności

Parametr --all-interactive false wymusza świadome decyzje o interaktywności. Domyślnie każdy komponent renderuje się statycznie — deweloper musi jawnie dodać @rendermode, aby włączyć SignalR lub WASM. To podejście drastycznie redukuje payload JavaScript i liczbę otwartych WebSocket.

Components/Pages/Privacy.razorcsharp
@page "/privacy"
@attribute [ExcludeFromInteractiveRouting]

<PageTitle>Privacy Policy</PageTitle>

<!-- This page always renders as static HTML -->
<!-- Even if global interactivity is enabled -->
<article>
    <h1>Privacy Policy</h1>
    <p>Last updated: April 2026</p>
    <!-- Content -->
</article>

Atrybut [ExcludeFromInteractiveRouting] gwarantuje, że strona /privacy zawsze renderuje się jako czysty HTML, nawet jeśli globalna konfiguracja wymusza Interactive Server. Jest to szczególnie istotne dla stron wymaganych przez regulacje (privacy policy, terms of service, cookie consent) — muszą być dostępne bez JavaScript i ładować się w mniej niż sekundę.

W praktyce większość aplikacji Blazor United składa się z mieszanki stron statycznych (landing, docs, legal), komponentów Interactive Server (dashboardy, formularze CRUD), komponentów Interactive Auto (edytory, chat, real-time collaboration) oraz komponentów WASM (offline forms, heavy client-side calculations). Taka kombinacja minimalizuje koszty infrastruktury, poprawia SEO i zapewnia doskonały UX. Zgodnie z najnowszym asp.net blazor tutorial Microsoft, takie rozłożenie proporcji jest optymalne dla większości aplikacji enterprise.

Ahead-of-Time compilation dla maksymalnej wydajności

WebAssembly w Blazor domyślnie używa interpretowanego .NET runtime. AOT (Ahead-of-Time) kompiluje assemblies do natywnego WebAssembly podczas budowania, eliminując JIT overhead. Aplikacje zyskują znacząco lepszą wydajność w zamian za dłuższy czas budowania i większy rozmiar bundle.

bash
# Publish with AOT for production
dotnet publish -c Release -p:RunAOTCompilation=true

AOT jest szczególnie opłacalne dla aplikacji z intensywnymi obliczeniami po stronie klienta — edytory graficzne, silniki gier 2D, parsery dużych JSON, kalkulatory finansowe. Dla typowych CRUD-ów różnica będzie niezauważalna, a zwiększony rozmiar bundle może pogorszyć Time to Interactive na wolnych połączeniach.

W .NET 9 AOT wspiera pełne odbicie i większość bibliotek BCL (Base Class Library). Wcześniejsze wersje miały ograniczenia w dynamicznym tworzeniu typów i serializacji JSON — obecnie działa to transparentnie z System.Text.Json i większością ORM.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Kluczowe wnioski

Blazor United w .NET 9 stanowi przełom w architekturze full-stack aplikacji webowych. Eliminuje konieczność wyboru między wydajnością a interaktywnością, umożliwiając płynne łączenie trybów renderowania w obrębie jednej aplikacji.

  • Zunifikowany model programowania — jeden język (C#), jeden runtime, trzy tryby renderowania bez kompromisów
  • Optymalna wydajność — statyczny SSR dla SEO, SignalR dla dashboardów, WASM dla offline i heavy computations
  • Interactive Auto mode — automatyczne przełączanie z serwera na klient po pobraniu runtime WASM w tle
  • Selektywna interaktywność — każdy komponent może mieć własny tryb renderowania, co minimalizuje payload i koszty infrastruktury
  • Streaming rendering — progresywne ładowanie HTML redukuje postrzegane opóźnienie
  • Resilient connections — konfigurowalne reconnection z wykładniczym backoff i automatyczne fallback do WASM
  • AOT compilation — znacząco lepsza wydajność WASM dzięki kompilacji do natywnego kodu

Blazor United to obecnie najbardziej zaawansowany framework full-stack dla deweloperów .NET. Aplikacje budowane w tym modelu osiągają wydajność porównywalną z najlepszymi frameworkami JavaScript, zachowując zalety silnego typowania C# i integracji z ekosystemem .NET. W 2026 roku znajomość blazor united full stack jest standardowym wymaganiem dla senior .NET developers.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Tagi

#dotnet
#blazor
#aspnet-core
#webassembly
#full-stack

Udostępnij

Powiązane artykuły