Blazor 라우팅과 내비게이션 — @page와 NavigationManager 완전 가이드

라우팅과 내비게이션

📚 라우팅과 내비게이션

핵심 요약

@page 지시어와 NavigationManager로 페이지 이동과 URL 매개변수를 처리합니다.

Blazor 라우팅과 내비게이션
@page 지시어와 NavigationManager로 페이지 이동과 URL 매개변수를 처리합니다

📌 학습 목표

  • @page 지시어를 사용하여 컴포넌트에 URL 경로를 지정하는 방법을 이해합니다.
  • 라우트 매개변수와 라우트 제약 조건으로 동적 URL을 처리합니다.
  • NavigationManager를 주입하여 프로그래밍 방식으로 페이지를 이동합니다.
  • [SupplyParameterFromQuery]로 쿼리 스트링 값을 컴포넌트 속성에 바인딩합니다.
  • 내비게이션 이벤트를 감지하고 메모리 누수 없이 안전하게 처리합니다.

📝 개념 설명

1. Blazor 라우팅의 기본 구조

Blazor에서 라우팅은 URL과 컴포넌트를 연결하는 메커니즘입니다. 사용자가 특정 URL로 접근하면 Blazor 라우터가 해당 URL과 매칭되는 컴포넌트를 찾아 렌더링합니다. 이 과정은 App.razor에 정의된 <Router> 컴포넌트가 담당합니다.

<!-- App.razor -->
<Router AppAssembly='@typeof(App).Assembly'>
    <Found Context='routeData'>
        <RouteView RouteData='@routeData' DefaultLayout='@typeof(MainLayout)' />
        <FocusOnNavigate RouteData='@routeData' Selector='h1' />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout='@typeof(MainLayout)'>
            <p role='alert'>해당 페이지를 찾을 수 없습니다.</p>
        </LayoutView>
    </NotFound>
</Router>

<Found>는 매칭된 라우트가 있을 때, <NotFound>는 매칭되는 라우트가 없을 때 렌더링됩니다.

2. @page 지시어

@page 지시어는 Razor 컴포넌트(.razor 파일) 최상단에 선언하여 해당 컴포넌트가 응답할 URL 경로를 지정합니다.

@page "/products"

<h1>상품 목록</h1>
<p>이 페이지는 /products URL로 접근 가능합니다.</p>

하나의 컴포넌트에 복수의 @page 지시어를 선언하여 여러 URL을 동시에 처리할 수도 있습니다.

@page "/"
@page "/home"
@page "/index"

<h1>홈 페이지</h1>
<p>세 가지 URL 모두 이 컴포넌트로 라우팅됩니다.</p>

3. 라우트 매개변수 (Route Parameters)

URL 경로에 중괄호 {}를 사용하여 동적 값을 캡처할 수 있습니다. 캡처된 값은 동일한 이름의 C# 속성(Property)에 자동으로 바인딩됩니다.

@page "/product/{Id}"

<h2>상품 상세: @Id</h2>

@code {
    [Parameter]
    public string? Id { get; set; }
}

URL /product/42로 접근하면 Id 속성에 "42"가 바인딩됩니다. 매개변수 속성에는 반드시 [Parameter] 어트리뷰트를 붙여야 합니다.

4. 라우트 제약 조건 (Route Constraints)

매개변수 뒤에 콜론(:)과 타입명을 붙이면 특정 타입만 허용하도록 제약할 수 있습니다. 제약 조건과 일치하지 않는 URL은 해당 라우트와 매칭되지 않습니다.

@page "/product/{Id:int}"

@code {
    [Parameter]
    public int Id { get; set; }  // 정수만 허용: /product/42 OK, /product/abc 불일치
}

주요 라우트 제약 조건은 다음과 같습니다.

  • :int — 정수 (예: 1, 42, -5)
  • :long — 64비트 정수
  • :float, :double, :decimal — 실수
  • :bool — true 또는 false
  • :datetime — 날짜/시간 형식
  • :guid — GUID 형식
  • :alpha — 알파벳(a-z, A-Z)만
  • :minlength(n), :maxlength(n) — 문자열 길이 제한
  • :min(n), :max(n), :range(min,max) — 숫자 범위

5. 선택적 라우트 매개변수 (Optional Parameters)

매개변수 이름 뒤에 물음표(?)를 붙이면 선택적 매개변수가 됩니다. 값이 없을 경우 null이 할당되므로 nullable 타입을 사용해야 합니다.

@page "/blog/{Slug?}"

<h2>@(Slug ?? "블로그 목록")</h2>

@code {
    [Parameter]
    public string? Slug { get; set; }
    // /blog       → Slug = null
    // /blog/hello → Slug = "hello"
}

6. NavigationManager

NavigationManager는 Blazor에서 내비게이션을 프로그래밍 방식으로 제어하기 위한 내장 서비스입니다. @inject 지시어로 컴포넌트에 주입하여 사용합니다.

@page "/login"
@inject NavigationManager NavManager

<button @onclick='GoToHome'>홈으로 이동</button>

@code {
    private void GoToHome()
    {
        NavManager.NavigateTo("/");
    }
}

NavigationManager의 주요 멤버는 다음과 같습니다.

  • Uri — 현재 전체 URL (예: https://example.com/product/5)
  • BaseUri — 앱의 기본 URI (예: https://example.com/)
  • NavigateTo(uri) — 지정 URL로 이동
  • NavigateTo(uri, forceLoad: true) — 전체 페이지 새로고침 포함 이동
  • NavigateTo(uri, replace: true) — 브라우저 히스토리 스택 교체
  • LocationChanged — URL 변경 시 발생하는 이벤트
  • GetUriWithQueryParameters() — 쿼리 스트링을 안전하게 조합한 URI 반환
  • ToAbsoluteUri(relativeUri) — 상대 경로를 절대 URI로 변환

7. 쿼리 스트링 매개변수

URL의 ?key=value 형식 쿼리 스트링을 컴포넌트 속성에 바인딩하려면 [SupplyParameterFromQuery] 어트리뷰트를 사용합니다.

@page "/search"

<p>검색어: @Keyword</p>
<p>페이지: @Page</p>

@code {
    [SupplyParameterFromQuery(Name = "q")]
    [Parameter]
    public string? Keyword { get; set; }

    [SupplyParameterFromQuery(Name = "page")]
    [Parameter]
    public int Page { get; set; } = 1;
    // /search?q=Blazor&page=2 접근 시
    // Keyword = "Blazor", Page = 2
}

8. 내비게이션 이벤트 처리

URL이 변경될 때 특정 작업을 수행하려면 LocationChanged 이벤트를 구독합니다. 메모리 누수를 방지하기 위해 반드시 IDisposable을 구현하여 이벤트를 해제해야 합니다.

@page "/tracker"
@inject NavigationManager NavManager
@implements IDisposable

<p>현재 URL: @currentUrl</p>

@code {
    private string currentUrl = string.Empty;

    protected override void OnInitialized()
    {
        currentUrl = NavManager.Uri;
        NavManager.LocationChanged += OnLocationChanged;
    }

    private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
    {
        currentUrl = e.Location;
        StateHasChanged();  // UI 갱신 명시적 요청
    }

    public void Dispose()
    {
        NavManager.LocationChanged -= OnLocationChanged;  // 반드시 해제
    }
}

 

 

🔄 Blazor 라우팅 처리 흐름

 

사용자가 URL 입력
/product/42
Router가
@page 패턴과 매칭
라우트 매개변수
추출 (Id = 42)
[Parameter] 속성에
값 주입 후 렌더링

 

 

📌 NavigationManager 핵심 기능

 

📍
Uri / BaseUri
현재 전체 URL과 앱 기본 URI를 문자열로 제공하는 읽기 전용 속성
🚀
NavigateTo()
지정 URL로 이동. forceLoad=true는 전체 페이지 리로드, replace=true는 히스토리 스택 교체
🔔
LocationChanged
URL 변경 시 발생하는 이벤트. IDisposable로 반드시 구독 해제 필요
🔗
GetUriWithQueryParameters()
딕셔너리로 쿼리 스트링을 안전하게 조합한 URI 문자열 반환

💡 예제 & 실습

실습 1: 상품 상세 페이지 — 라우트 매개변수 활용

상품 ID를 URL로 받아 상세 정보를 표시하는 페이지를 구현합니다. :int 제약 조건을 적용하여 정수 ID만 허용합니다.

@* Pages/ProductDetail.razor *@
@page "/product/{Id:int}"

<PageTitle>상품 상세</PageTitle>
<h2>상품 번호: @Id</h2>

@if (product is not null)
{
    <div>
        <p>이름: @product.Name</p>
        <p>가격: @product.Price.ToString("N0")원</p>
    </div>
}
else
{
    <p>상품을 찾을 수 없습니다.</p>
}

@code {
    [Parameter]
    public int Id { get; set; }

    private Product? product;

    protected override void OnParametersSet()
    {
        // 매개변수가 바뀔 때마다 호출 → 데이터 새로 로드
        product = Id switch
        {
            1 => new Product("노트북", 1_200_000m),
            2 => new Product("마우스", 35_000m),
            _ => null
        };
    }

    private record Product(string Name, decimal Price);
}
💡 OnParametersSet vs OnInitialized
OnInitialized는 컴포넌트가 처음 생성될 때 단 한 번만 호출됩니다. 반면 OnParametersSet매개변수가 변경될 때마다 호출됩니다. 같은 컴포넌트 내에서 URL만 바뀌는 경우(예: /product/1 → /product/2)에도 데이터를 새로 로드하려면 반드시 OnParametersSet을 사용해야 합니다.

실습 2: NavigationManager로 로그인 후 리디렉션

로그인 성공 시 대시보드로 이동하고, 히스토리 스택을 교체하여 뒤로 가기 시 로그인 화면이 다시 나타나지 않도록 합니다.

@* Pages/Login.razor *@
@page "/login"
@inject NavigationManager NavManager

<h2>로그인</h2>
<input @bind='username' placeholder='사용자명' />
<input type='password' @bind='password' placeholder='비밀번호' />
<button @onclick='HandleLogin'>로그인</button>

@if (!string.IsNullOrEmpty(errorMessage))
{
    <p style='color:red'>@errorMessage</p>
}

@code {
    private string username = "";
    private string password = "";
    private string errorMessage = "";

    private void HandleLogin()
    {
        if (username == "admin" && password == "1234")
        {
            // replace: true → 로그인 페이지를 히스토리에서 교체
            NavManager.NavigateTo("/dashboard", replace: true);
        }
        else
        {
            errorMessage = "아이디 또는 비밀번호가 틀렸습니다.";
        }
    }
}

실습 3: 쿼리 스트링을 활용한 검색 페이지

GetUriWithQueryParameters()를 사용하여 검색어와 페이지 번호를 URL에 안전하게 반영합니다.

@* Pages/Search.razor *@
@page "/search"
@inject NavigationManager NavManager

<h2>검색</h2>
<input @bind='searchInput' placeholder='검색어 입력' />
<button @onclick='DoSearch'>검색</button>

@if (!string.IsNullOrEmpty(Keyword))
{
    <p>"@Keyword" 검색 결과 (페이지 @Page)</p>
}

@code {
    [SupplyParameterFromQuery(Name = "q")]
    [Parameter]
    public string? Keyword { get; set; }

    [SupplyParameterFromQuery(Name = "page")]
    [Parameter]
    public int Page { get; set; } = 1;

    private string searchInput = "";

    protected override void OnParametersSet()
    {
        searchInput = Keyword ?? "";
    }

    private void DoSearch()
    {
        var uri = NavManager.GetUriWithQueryParameters(
            "/search",
            new Dictionary<string, object?>
            {
                ["q"]    = searchInput,
                ["page"] = 1         // 새 검색 시 첫 페이지로 초기화
            }
        );
        NavManager.NavigateTo(uri);
    }
}

GetUriWithQueryParameters()는 null 값을 가진 키를 URL에서 자동으로 제거하므로, 조건부 쿼리 스트링 구성에 매우 유용합니다.

⚠️ 자주 틀리는 것 / 주의사항

⚠️ 1. [Parameter] 어트리뷰트 누락
라우트 매개변수로 지정한 속성에 [Parameter] 어트리뷰트를 붙이지 않으면 바인딩이 이루어지지 않아 값이 항상 null 또는 기본값으로 남습니다. 속성 이름은 라우트 템플릿의 매개변수 이름과 대소문자 구분 없이 일치해야 합니다.
⚠️ 2. OnInitialized에서 매개변수 의존 로직 처리
매개변수에 의존하는 초기화 로직을 OnInitialized에 넣으면, 같은 컴포넌트 내에서 URL 매개변수만 바뀔 경우 데이터가 갱신되지 않습니다. 매개변수 변경에 반응해야 한다면 반드시 OnParametersSet 또는 OnParametersSetAsync를 사용해야 합니다.
⚠️ 3. LocationChanged 이벤트 메모리 누수
NavManager.LocationChanged += ...로 이벤트를 구독한 컴포넌트는 반드시 @implements IDisposable을 선언하고, Dispose()에서 -=로 구독을 해제해야 합니다. 해제하지 않으면 컴포넌트가 DOM에서 제거된 후에도 이벤트 핸들러가 메모리에 남아 잠재적 버그를 유발합니다.
⚠️ 4. 라우트 경로 충돌
/product/{Id:int}/product/featured가 동시에 존재할 경우, 문자열 “featured”는 :int 제약에 맞지 않으므로 /product/featured가 우선 매칭됩니다. 라우트 제약 조건을 잘 활용하면 고정 경로와 동적 경로 간의 충돌을 효과적으로 분리할 수 있습니다.
⚠️ 5. 외부 URL 이동 시 forceLoad 필수 (Blazor WebAssembly)
Blazor WebAssembly에서 앱 외부 URL(예: https://external.com)로 NavigateTo()를 호출할 때는 forceLoad: true를 반드시 전달해야 합니다. 그렇지 않으면 Blazor 라우터가 내부 경로로 처리하려 시도하여 404가 발생합니다.

🎯 마무리

Blazor의 라우팅과 내비게이션 시스템은 @page 지시어NavigationManager 두 축을 중심으로 동작합니다. @page 지시어로 컴포넌트에 URL을 매핑하고, 라우트 매개변수와 제약 조건으로 동적 경로를 처리하며, NavigationManager로 코드에서 페이지 이동을 세밀하게 제어합니다.

 

실전에서는 OnParametersSet을 활용하여 URL 변경에 따른 데이터 갱신을 처리하고, GetUriWithQueryParameters()로 쿼리 스트링을 안전하게 조합하는 패턴을 익혀두면 복잡한 SPA 내비게이션도 깔끔하게 구현할 수 있습니다.

✅ 핵심 정리
  • @page “/경로”로 컴포넌트에 URL 매핑, 하나의 컴포넌트에 복수의 경로 선언 가능
  • {매개변수명}으로 동적 URL 값 캡처, 속성에 [Parameter] 어트리뷰트 필수
  • :int, :bool, :guid, :alpha 등 콜론 제약 조건으로 URL 타입 안전성 확보
  • {매개변수명?}는 선택적 매개변수 — 속성은 nullable 타입으로 선언
  • @inject NavigationManager NavManager로 서비스 주입 후 NavigateTo()로 페이지 이동
  • replace: true는 히스토리 교체, forceLoad: true는 전체 페이지 새로고침
  • [SupplyParameterFromQuery]로 쿼리 스트링 ?key=value를 속성에 자동 바인딩
  • 매개변수 변경 시 데이터 갱신은 OnParametersSet 사용 (OnInitialized는 최초 1회만)
  • LocationChanged 이벤트 구독 시 반드시 IDisposable 구현하여 Dispose()에서 해제

댓글 남기기

Wordpress Social Share Plugin powered by Ultimatelysocial
Copy link
URL has been copied successfully!
THREADS
RSS
error: 저작권 콘텐츠보호를 부탁드립니다.