다음을 통해 공유


ASP.NET Core Razor 구성 요소 미리 렌더링

참고

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 9 버전을 참조하세요.

중요한

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 9 버전을 참조하세요.

이 문서에서는 Razor의 서버 렌더링 구성 요소에 대한 Blazor Web App 구성 요소 미리 렌더링 시나리오를 설명합니다.

미리 렌더링 은 렌더링된 컨트롤에 대한 이벤트 처리기를 사용하도록 설정하지 않고 서버에서 페이지 콘텐츠를 처음 렌더링하는 프로세스입니다. 서버는 초기 요청에 대한 응답으로 가능한 한 빨리 페이지의 HTML UI를 출력하므로 앱이 사용자에게 더 반응할 수 있습니다. 사전 렌더링은 검색 엔진이 페이지 순위를 계산하는 데 사용하는 초기 HTTP 응답에 대한 콘텐츠를 렌더링하여 SEO(검색 엔진 최적화) 를 향상시킬 수도 있습니다.

미리 렌더링된 상태 유지

미리 렌더링된 상태를 유지하지 않으면 미리 렌더링하는 동안 사용된 상태를 잃게 되며 앱이 완전히 로드되면 다시 만들어야 합니다. 비동기적으로 상태가 만들어지면 구성 요소가 다시 렌더링될 때 미리 렌더링된 UI가 대체되므로 UI가 깜박일 수 있습니다.

다음 PrerenderedCounter1 카운터 구성 요소를 고려합니다. 구성 요소는 수명 주기 메서드에서OnInitialized 미리 렌더링하는 동안 초기 임의 카운터 값을 설정합니다. 클라이언트에 대한 SignalR 연결이 설정되면 구성 요소가 다시 렌더링되고, OnInitialized이 두 번째로 실행될 때 초기 개수 값이 바뀝니다.

PrerenderedCounter1.razor:

@page "/prerendered-counter-1"
@rendermode @(new InteractiveServerRenderMode(prerender: true))
@inject ILogger<PrerenderedCounter1> Logger

<PageTitle>Prerendered Counter 1</PageTitle>

<h1>Prerendered Counter 1</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount;

    protected override void OnInitialized()
    {
        currentCount = Random.Shared.Next(100);
        Logger.LogInformation("currentCount set to {Count}", currentCount);
    }

    private void IncrementCount() => currentCount++;
}

앱을 실행하고 구성 요소에서 로깅을 검사합니다. 예제 출력은 다음과 같습니다.

참고

앱이 대화형 라우팅을 채택하고 내부 향상된 탐색을 통해 페이지에 도달하면 미리 렌더링이 발생하지 않습니다. 따라서 다음 출력을 보려면 구성 요소에 PrerenderedCounter1 대한 전체 페이지 다시 로드를 수행해야 합니다. 자세한 내용은 대화형 라우팅 및 미리 렌더링 섹션을 참조하세요.

info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 41
info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 92

첫 번째 로깅된 개수는 미리 렌더링하는 동안 발생합니다. 구성 요소를 다시 렌더링할 때 미리 렌더링한 후 개수가 다시 설정됩니다. 개수가 41에서 92로 업데이트되면 UI에도 깜박임이 있습니다.

초기 카운터 값을 미리 렌더링 동안 유지하려면 Blazor 서비스는 미리 렌더링된 페이지에서 상태 지속을 지원합니다. 또한 MVC 앱의 Pages나 뷰에 포함된 구성 요소의 경우, PersistentComponentState를 사용할 수 있습니다.

미리 렌더링된 상태를 유지하려면 [SupplyParameterFromPersistentComponentState] 속성을 사용하여 상태를 지속시킵니다. 이 특성이 있는 속성은 미리 렌더링하는 동안 서비스를 사용하여 PersistentComponentState 자동으로 유지됩니다. 구성 요소가 대화형으로 렌더링되거나 서비스가 인스턴스화될 때 상태가 검색됩니다.

기본적으로 속성은 기본 설정이 있는 serializer를 System.Text.Json 사용하여 직렬화됩니다. Serialization은 트리밍 안전성을 보장하지 않으며, 사용된 형식을 반드시 보존해야 합니다. 자세한 내용은 ASP.NET Core용 트리머 구성을 참조하세요 Blazor.

다음 카운터 구성 요소는 미리 렌더링하는 동안 카운터 상태를 유지하며 구성 요소를 초기화하는 상태를 검색합니다.

  • [SupplyParameterFromPersistentComponentState] 속성은 CounterState 형식(State)에 적용됩니다.
  • 카운터의 상태는 nullOnInitialized에서 할당되며, 구성 요소가 대화형으로 렌더링될 때 자동으로 복원됩니다.

PrerenderedCounter2.razor:

@page "/prerendered-counter-2"
@inject ILogger<PrerenderedCounter2> Logger

<PageTitle>Prerendered Counter 2</PageTitle>

<h1>Prerendered Counter 2</h1>

<p role="status">Current count: @State?.CurrentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    [SupplyParameterFromPersistentComponentState]
    public CounterState? State { get; set; }

    protected override void OnInitialized()
    {
        if (State is null)
        {
            State = new() { CurrentCount = Random.Shared.Next(100) };
            Logger.LogInformation("CurrentCount set to {Count}", 
                State.CurrentCount);
        }
        else
        {
            Logger.LogInformation("CurrentCount restored to {Count}", 
                State.CurrentCount);
        }
    }

    private void IncrementCount()
    {
        if (State is not null)
        {
            State.CurrentCount++;
        }
    }

    public class CounterState
    {
        public int CurrentCount { get; set; }
    }
}

구성 요소가 실행되면 CurrentCount 미리 렌더링하는 동안 한 번만 설정됩니다. 구성 요소를 다시 렌더링하면 값이 복원됩니다. 예제 출력은 다음과 같습니다.

참고

앱이 대화형 라우팅을 채택하고 내부 향상된 탐색을 통해 페이지에 도달하면 미리 렌더링이 발생하지 않습니다. 따라서 다음 출력을 보려면 구성 요소에 대한 전체 페이지 다시 로드를 수행해야 합니다. 자세한 내용은 대화형 라우팅 및 미리 렌더링 섹션을 참조하세요.

info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
CurrentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
CurrentCount restored to 96

다음 예제에서는 동일한 형식의 여러 구성 요소에 대한 상태를 serialize합니다.

  • [SupplyParameterFromPersistentComponentState] 특성으로 주석이 추가된 속성은 미리 렌더링하는 동안 직렬화되고 역직렬화됩니다.
  • @key 지시문 특성은 상태가 구성 요소 인스턴스와 올바르게 연결되어 있는지 확인하는 데 사용됩니다.
  • Element 속성은 쿼리 매개 변수 및 양식 데이터에 대해 null 참조를 방지하는 방법과 유사하게 null 참조 예외를 방지하기 위해 수명 주기 메서드에서 초기화OnInitialized됩니다.

PersistentChild.razor:

<div>
    <p>Current count: @Element.CurrentCount</p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</div>

@code {
    [SupplyParameterFromPersistentComponentState]
    public State Element { get; set; }

    protected override void OnInitialized()
    {
        Element ??= new State();
    }

    private void IncrementCount()
    {
        Element.CurrentCount++;
    }

    private class State
    {
        public int CurrentCount { get; set; }
    }
}

Parent.razor:

@page "/parent"

@foreach (var element in elements)
{
    <PersistentChild @key="element.Name" />
}

다음 예제에서는 종속성 주입 서비스에 대한 상태를 직렬화합니다.

  • 속성으로 [SupplyParameterFromPersistentComponentState] 주석이 추가된 속성은 미리 렌더링하는 동안 직렬화되고, 앱이 상호작용 가능한 상태가 되면 역직렬화됩니다.
  • AddPersistentService 메서드는 지속성을 위해 서비스를 등록하는 데 사용됩니다. 렌더링 모드는 서비스 유형에서 유추할 수 없기 때문에 필요합니다. 다음 값 중에서 사용합니다.
    • RenderMode.Server: 이 서비스는 대화형 서버 렌더링 모드에 사용할 수 있습니다.
    • RenderMode.Webassembly: 이 서비스는 대화형 Webassembly 렌더링 모드에 사용할 수 있습니다.
    • RenderMode.InteractiveAuto: 구성 요소가 해당 모드 중 하나에서 렌더링되는 경우 대화형 서버 및 대화형 Webassembly 렌더링 모드 모두에 서비스를 사용할 수 있습니다.
  • 서비스가 대화형 렌더링 모드를 초기화하는 동안 해제되고, [SupplyParameterFromPersistentComponentState] 특성으로 주석이 추가된 속성은 역직렬화됩니다.

참고

범위가 지정된 서비스만 유지됩니다.

CounterService.cs:

public class CounterService
{
    [SupplyParameterFromPersistentComponentState]
    public int CurrentCount { get; set; }

    public void IncrementCount()
    {
        CurrentCount++;
    }
}

Program.cs의 경우

builder.Services.AddPersistentService<CounterService>(RenderMode.InteractiveAuto);

직렬화된 속성은 실제 서비스 인스턴스에서 식별됩니다.

  • 이 접근 방식은 추상화를 지속적인 서비스로 표시할 수 있게 합니다.
  • 실제 구현을 내부 또는 다른 형식으로 설정할 수 있습니다.
  • 다른 어셈블리에서 공유 코드를 지원합니다.
  • 각 인스턴스에서 동일한 속성을 드러냅니다.

선언적 모델로 [SupplyParameterFromPersistentComponentState] 속성을 사용하여 상태를 유지하는 대신, PersistentComponentState 서비스를 직접 사용할 수 있습니다. 이 서비스는 복잡한 상태 지속성 시나리오에 대해 더 큰 유연성을 제공합니다. 미리 렌더링하는 동안 구성 요소 상태를 유지하기 위해 콜백을 등록하기 위한 호출 PersistentComponentState.RegisterOnPersisting 입니다. 구성 요소가 대화형으로 렌더링될 때 상태가 검색됩니다. 앱 종료 중에 잠재적인 경합 상태를 방지하기 위해 초기화 코드가 끝날 때 호출합니다.

다음 카운터 구성 요소 예제는 미리 렌더링하는 동안 카운터 상태를 유지하며 구성 요소를 초기화하는 상태를 검색합니다.

PrerenderedCounter3.razor:

@page "/prerendered-counter-3"
@implements IDisposable
@inject ILogger<PrerenderedCounter3> Logger
@inject PersistentComponentState ApplicationState

<PageTitle>Prerendered Counter 3</PageTitle>

<h1>Prerendered Counter 3</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override void OnInitialized()
    {
        if (!ApplicationState.TryTakeFromJson<int>(
            nameof(currentCount), out var restoredCount))
        {
            currentCount = Random.Shared.Next(100);
            Logger.LogInformation("currentCount set to {Count}", currentCount);
        }
        else
        {
            currentCount = restoredCount!;
            Logger.LogInformation("currentCount restored to {Count}", currentCount);
        }

        // Call at the end to avoid a potential race condition at app shutdown
        persistingSubscription = ApplicationState.RegisterOnPersisting(PersistCount);
    }

    private Task PersistCount()
    {
        ApplicationState.PersistAsJson(nameof(currentCount), currentCount);

        return Task.CompletedTask;
    }

    private void IncrementCount() => currentCount++;

    void IDisposable.Dispose() => persistingSubscription.Dispose();
}

구성 요소가 실행되면 currentCount 미리 렌더링하는 동안 한 번만 설정됩니다. 구성 요소를 다시 렌더링하면 값이 복원됩니다. 예제 출력은 다음과 같습니다.

참고

앱이 대화형 라우팅을 채택하고 내부 향상된 탐색을 통해 페이지에 도달하면 미리 렌더링이 발생하지 않습니다. 따라서 다음 출력을 보려면 구성 요소에 대한 전체 페이지 다시 로드를 수행해야 합니다. 자세한 내용은 대화형 라우팅 및 미리 렌더링 섹션을 참조하세요.

info: BlazorSample.Components.Pages.PrerenderedCounter3[0]
currentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter3[0]
currentCount restored to 96

미리 렌더링된 상태를 보존하려면 PersistentComponentState 서비스를 사용하여 어떤 상태를 유지할지 결정하세요. PersistentComponentState.RegisterOnPersisting 는 미리 렌더링하는 동안 구성 요소 상태를 유지하기 위해 콜백을 등록합니다. 구성 요소가 대화형으로 렌더링될 때 상태가 검색됩니다. 앱 종료 중에 잠재적인 경합 상태를 방지하기 위해 초기화 코드가 끝날 때 호출합니다.

다음 카운터 구성 요소 예제는 미리 렌더링하는 동안 카운터 상태를 유지하며 구성 요소를 초기화하는 상태를 검색합니다.

PrerenderedCounter2.razor:

@page "/prerendered-counter-2"
@implements IDisposable
@inject ILogger<PrerenderedCounter2> Logger
@inject PersistentComponentState ApplicationState

<PageTitle>Prerendered Counter 2</PageTitle>

<h1>Prerendered Counter 2</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override void OnInitialized()
    {
        if (!ApplicationState.TryTakeFromJson<int>(
            nameof(currentCount), out var restoredCount))
        {
            currentCount = Random.Shared.Next(100);
            Logger.LogInformation("currentCount set to {Count}", currentCount);
        }
        else
        {
            currentCount = restoredCount!;
            Logger.LogInformation("currentCount restored to {Count}", currentCount);
        }

        // Call at the end to avoid a potential race condition at app shutdown
        persistingSubscription = ApplicationState.RegisterOnPersisting(PersistCount);
    }

    private Task PersistCount()
    {
        ApplicationState.PersistAsJson(nameof(currentCount), currentCount);

        return Task.CompletedTask;
    }

    void IDisposable.Dispose() => persistingSubscription.Dispose();

    private void IncrementCount() => currentCount++;
}

구성 요소가 실행되면 currentCount 미리 렌더링하는 동안 한 번만 설정됩니다. 구성 요소를 다시 렌더링하면 값이 복원됩니다. 예제 출력은 다음과 같습니다.

참고

앱이 대화형 라우팅을 채택하고 내부 향상된 탐색을 통해 페이지에 도달하면 미리 렌더링이 발생하지 않습니다. 따라서 다음 출력을 보려면 구성 요소에 대한 전체 페이지 다시 로드를 수행해야 합니다. 자세한 내용은 대화형 라우팅 및 미리 렌더링 섹션을 참조하세요.

info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount restored to 96

미리 렌더링하는 동안 사용된 것과 동일한 상태로 구성 요소를 초기화하면 비용이 많이 드는 초기화 단계가 한 번만 실행됩니다. 렌더링된 UI도 미리 렌더링된 UI와 일치하므로 브라우저에서 깜박임이 발생하지 않습니다.

지속형 미리 렌더링된 상태는 구성 요소 상태를 복원하는 데 사용되는 클라이언트로 전송됩니다. 클라이언트 쪽 렌더링(CSR InteractiveWebAssembly) 중에 데이터는 브라우저에 노출되며 중요한 개인 정보를 포함해서는 안 됩니다. 대화형 서버 쪽 렌더링 중(대화형 SSR, InteractiveServer) ASP.NET Core Data Protection은 데이터가 안전하게 전송되도록 합니다. 렌더링 모드는 InteractiveAuto WebAssembly와 서버 대화형 작업을 결합하므로 CSR 사례와 같이 브라우저에 대한 데이터 노출을 고려해야 합니다.

페이지 및 뷰에 포함된 구성 요소(Razor Pages/MVC)

페이지 또는 MVC 앱의 페이지나 보기에 포함된 구성 요소의 경우, 앱 레이아웃에서 Razor 종료 태그 내부에 <persist-component-state /></body> HTML 태그를 추가해야 합니다. 이것은 Pages 및 MVC 앱에만 Razor 필요합니다. 자세한 내용은 ASP.NET Core에서 구성 요소 상태를 유지하는 태그 도우미를 참조하세요.

Pages/Shared/_Layout.cshtml:

<body>
    ...

    <persist-component-state />
</body>

대화형 라우팅 및 미리 렌더링

Routes 구성 요소가 렌더링 모드를 정의하지 않으면 앱은 페이지/구성 요소별 대화형 작업 및 탐색을 사용합니다. 페이지별/구성 요소 내비게이션을 사용함으로써, 앱이 대화형으로 변한 이후에는 내부 탐색†이 향상된 라우팅 에 의해 처리됩니다. †이 컨텍스트에서 "Internal"은 탐색 이벤트의 URL 목적지가 앱 내부의 Blazor 엔드포인트임을 의미합니다.

PersistentComponentState 서비스는 내부 향상된 페이지 탐색 이벤트가 아닌 첫 페이지 로드 시에만 작동합니다.

앱이 영구 구성 요소 상태를 활용하는 페이지에 대한 전체(비확장) 탐색을 수행하는 경우 앱이 대화형이 될 때 사용할 수 있는 지속형 상태가 제공됩니다.

대화형 회로가 이미 설정되었고 영구적 구성 요소 상태를 사용하는 페이지로 고급 탐색이 수행될 경우, 해당 구성 요소가을/를 사용하기 위해 기존 회로에서 상태 이/가 제공되지 않습니다. 내부 페이지 요청에 대한 미리 렌더링이 없으며 PersistentComponentState 서비스에서 향상된 탐색이 발생했음을 인식하지 못합니다. 기존 회로에서 이미 실행 중인 구성 요소에 상태 업데이트를 제공하는 메커니즘은 없습니다. 그 이유는 Blazor 런타임이 시작된 후가 아니라 런타임이 초기화될 때 서버에서 클라이언트로의 상태 전달만 지원하기 때문입니다.

이 시나리오를 해결하기 위한 Blazor 프레임워크에 대한 추가 작업은 .NET 10(2025년 11월)에 대해 고려 중입니다. 지원되지 않는 해결 방법에 대한 자세한 정보와 커뮤니티 토론은 향상된 페이지 탐색을 통한 영구 구성 요소 상태 유지 지원( #51584)을 참조하세요. •지원되지 않는 해결 방법은 Blazor 앱에서 사용하기 위해 Microsoft에서 승인하지 않습니다. 고유한 위험에 타사 패키지, 접근 방식 및 코드를 사용합니다.

향상된 탐색 기능을 비활성화하면 성능이 저하되지만, 내부 페이지 요청 시 발생하는 상태 PersistentComponentState 로드 문제를 방지할 수 있습니다. 이는 ASP.NET Core Blazor 라우팅 및 탐색에서 다룹니다.

미리 렌더링 지침

미리 렌더링 지침은 주제별로 설명서에 Blazor 정리되어 있습니다. 다음 링크는 주체가 설정한 설명서 전체의 모든 미리 렌더링 지침을 다룹니다.

.NET 7 이하의 경우 보안 추가 시나리오: 인증으로 미리 렌더링을 참조Blazor WebAssembly하세요. 이 섹션의 콘텐츠를 본 후 문서 문서 버전 선택기 드롭다운을 최신 .NET 릴리스 버전으로 다시 설정하여 이후 방문 시 문서 페이지가 최신 릴리스에 로드되도록 합니다.