Navigation API

김동현·2026년 3월 22일

Navigation API

Baseline: 2026

🆕 Newly available (새롭게 사용 가능)

✅ Chrome ✅ Edge ✅ Firefox ✅ Safari

2026년 1월부터, 이 기능은 최신 기기와 브라우저 버전에서 작동해요. 이 기능은 이전 기기나 브라우저에서는 작동하지 않을 수 있어요.


Navigation API는 브라우저 네비게이션 액션을 시작하고, 가로채고, 관리하는 기능을 제공해요. 또한 애플리케이션의 히스토리 항목을 조사할 수도 있어요. 이것은 History APIwindow.location 같은 이전 웹 플랫폼 기능들의 후속으로, 그것들의 단점을 해결하고 특히 싱글 페이지 애플리케이션(SPA)의 요구에 맞춰져 있어요.

안녕하세요! 프론트엔드 개발자로서 정말 흥미로워할 만한 최신 API 문서를 가져오셨군요. 바로 Navigation API입니다.

실무에서 리액트(React)나 넥스트제이에스(Next.js) 같은 프레임워크로 싱글 페이지 애플리케이션(SPA)을 개발하다 보면, 브라우저의 기본 뒤로 가기/앞으로 가기 동작과 자바스크립트로 렌더링된 화면 상태를 맞추는 게 얼마나 까다로운 일인지 금방 느끼게 됩니다. 기존에는 History API를 억지로 끼워 맞춰서 라우터를 구현해야 했지만, 이제는 SPA의 라우팅을 위해 작정하고 만들어진 이 Navigation API가 그 자리를 대체해 나가고 있습니다.

이해하기 쉽게 팍팍 풀어서 설명해 드릴게요!


개념 및 사용법 (Concepts and usage)

SPA(싱글 페이지 애플리케이션) 환경에서는 사용자가 서비스를 이용하는 동안 페이지의 기본 틀(template)은 그대로 유지되고, 사용자가 다른 페이지나 기능을 방문할 때마다 콘텐츠만 동적으로 다시 쓰입니다(rewritten). 결과적으로 브라우저에는 단 하나의 고유한 페이지만 로드되기 때문에, 사용자가 브라우저의 뒤로 가기나 앞으로 가기 버튼을 눌렀을 때 기대하는 '이전/다음 위치로의 이동'이라는 자연스러운 사용자 경험(UX)이 깨지게 됩니다.

이 문제는 기존의 History API를 통해 어느 정도 해결할 수 있었지만, History API는 애초에 SPA의 요구사항을 염두에 두고 설계된 것이 아니었습니다. Navigation API는 바로 이 격차를 메우기 위해 등장했습니다.

이 API는 전역 Navigation 객체를 반환하는 Window.navigation 속성을 통해 접근할 수 있습니다. 각각의 window 객체는 자신만의 고유한 navigation 인스턴스를 가집니다.


네비게이션 처리하기 (Handling navigations)

navigation 인터페이스에는 여러 관련 이벤트가 있는데, 그중 가장 눈여겨봐야 할 것은 바로 Maps 이벤트입니다. 이 이벤트는 어떤 종류의 네비게이션이든 (링크 클릭, 뒤로 가기, 자바스크립트 이동 등) 시작될 때마다 발생합니다. 즉, SPA 프레임워크의 라우팅 기능에 아주 이상적이게도 모든 페이지 이동을 중앙의 한 곳에서 통제할 수 있다는 뜻입니다! (모든 이동을 감지하고 대응하기 까다로웠던 History API와는 확연히 다른 점이죠.)

Maps 이벤트 핸들러는 MapsEvent 객체를 전달받는데, 이 안에는 이동할 목적지, 이동 유형, POST 폼 데이터나 다운로드 요청이 포함되어 있는지 등 아주 상세한 정보가 들어 있습니다.

MapsEvent 객체는 또한 두 가지 아주 중요한 메서드를 제공합니다:

  • intercept(): 네비게이션에 대한 커스텀 동작을 지정할 수 있게 해 주며, 다음과 같은 인자를 받을 수 있습니다:
    • 콜백 핸들러 함수: 네비게이션이 확정(commit)되었을 때확정되기 직전에 발생할 일들을 지정할 수 있습니다. 예를 들어, 이동하려는 URL 경로에 맞춰 SWR이나 Fetch로 새로운 콘텐츠를 불러와 UI에 렌더링하거나, 접근 권한이 없는 페이지인데 로그인하지 않은 상태라면 로그인 페이지로 강제 리다이렉트 시킬 수 있습니다.
    • 속성들: 네비게이션이 발생한 후 브라우저의 기본 포커스(focus) 이동이나 스크롤 동작을 활성화하거나 비활성화할 수 있습니다.
  • scroll(): 브라우저가 알아서 스크롤을 처리할 때까지 기다리지 않고, 코드 로직상 적절한 시점에 브라우저의 스크롤 동작(예: URL의 해시(#) 프래그먼트 위치로 이동)을 수동으로 즉시 실행시킵니다.

네비게이션이 시작되고 여러분의 intercept() 핸들러가 호출되면, 진행 중인 네비게이션의 과정을 추적할 수 있는 NavigationTransition 객체 인스턴스가 생성됩니다. (Navigation.transition을 통해 접근 가능합니다.)

참고:
여기서 말하는 "트랜지션(transition)"은 브라우저 히스토리의 한 항목에서 다른 항목으로 넘어가는 전환 과정을 뜻합니다. CSS의 트랜지션 애니메이션과는 전혀 관계가 없습니다.

참고:
대부분의 네비게이션 타입에 대해 preventDefault()를 호출하여 이동 자체를 완전히 멈출 수도 있습니다. (단, 앞/뒤로 이동하는 traverse 네비게이션의 취소 기능은 아직 구현되지 않았습니다.)

intercept() 핸들러 함수가 반환한 프로미스(Promises)가 성공적으로 완료(fulfill)되면, Navigation 객체의 Mapssuccess 이벤트가 발생하여 네비게이션 완료 후의 뒷정리(cleanup) 코드를 실행할 수 있습니다. 만약 프로미스가 실패(reject)하여 네비게이션이 실패했다면, 대신 Mapserror 이벤트가 발생하여 에러 상황을 우아하게(gracefully) 처리할 수 있도록 해줍니다.
또한 Navigation.navigate() 같은 네비게이션 메서드들의 반환 값 안에는 finished라는 프로미스 속성도 있어서, 위에서 언급한 이벤트들이 발생할 때 동일하게 성공/실패 여부를 알려주는 또 다른 에러 핸들링 경로를 제공합니다.

💡 강사의 핵심 팁:
이 API가 나오기 전에는 어땠을까요? 웹페이지에 있는 '모든' 링크(<a> 태그)의 클릭 이벤트를 일일이 감지해서, e.preventDefault()로 화면 깜빡임을 막고, History.pushState()로 주소창을 바꾼 다음, 새로운 URL에 맞춰 화면을 다시 그려야 했습니다. 심지어 이렇게 쌩고생을 해도 사용자가 직접 일으킨 클릭 이벤트만 잡을 수 있었지, 폼 제출이나 브라우저 자체의 이동 기능은 통제하기 어려웠어요. intercept()는 이 모든 복잡함을 한 방에 해결해 준 셈입니다!


프로그래밍 방식으로 탐색 기록 업데이트 및 이동 (Programmatically updating and traversing the navigation history)

사용자가 여러분의 애플리케이션을 돌아다님에 따라, 방문하는 새로운 위치마다 '네비게이션 히스토리 항목(navigation history entry)'이 생성됩니다.

각 히스토리 항목은 고유한 NavigationHistoryEntry 객체 인스턴스로 표현됩니다. 이 객체 안에는 항목의 고유 키(key), URL, 그리고 상태(state) 정보 같은 여러 속성들이 들어있습니다.
사용자가 현재 머물고 있는 항목은 Navigation.currentEntry로 얻을 수 있고, 기존에 쌓인 전체 히스토리 항목들의 배열은 Navigation.entries()를 통해 가져올 수 있습니다.

NavigationHistoryEntry 객체는 브라우저 히스토리에서 해당 항목이 완전히 지워질 때 발생하는 dispose 이벤트를 가지고 있습니다. 예를 들어, 사용자가 뒤로 가기를 세 번 누른 다음 완전히 새로운 다른 곳으로 이동(forward)해 버리면, 이전의 그 세 개의 히스토리 항목들은 영영 사라지면서(dispose) 이 이벤트가 발생하게 됩니다.

참고:
Navigation API는 현재 페이지와 출처(origin)가 같고 현재 브라우징 컨텍스트에서 생성된 히스토리 항목들만 노출합니다. (예를 들어, 페이지 안에 포함된 <iframe> 내부의 이동이나 완전히 다른 도메인으로의 이동 기록은 보여주지 않습니다.) 즉, 여러분의 앱에 딱 맞는 정확한 이전 기록 목록만 제공해 줍니다. 덕분에 구형 History API를 쓸 때보다 히스토리 탐색이 훨씬 안전하고 덜 깨지게 됩니다.

Navigation 객체에는 이 탐색 기록을 업데이트하고 이동하는 데 필요한 모든 메서드가 포함되어 있습니다.

navigate()

새로운 URL로 이동하며, 새로운 네비게이션 히스토리 항목을 생성합니다.

reload()

현재 네비게이션 히스토리 항목을 새로고침합니다.

back()

가능한 경우, 이전 네비게이션 히스토리 항목으로 이동합니다.

forward()

가능한 경우, 다음 네비게이션 히스토리 항목으로 이동합니다.

traverseTo()

항목의 NavigationHistoryEntry.key 속성을 통해 얻은 고유한 key 값을 사용하여, 특정 네비게이션 히스토리 항목으로 단번에 이동합니다.

위의 각 메서드들은 committedfinished라는 두 개의 프로미스를 담고 있는 객체를 반환합니다. 이를 통해 함수를 호출한 쪽에서는 다음 시점까지 기다렸다가 추가 작업을 수행할 수 있습니다.

  • committed가 성공(fulfill)하면: 화면에 보이는 URL이 이미 변경되었고 새로운 NavigationHistoryEntry가 성공적으로 생성되었음을 의미합니다.
  • finished가 성공(fulfill)하면: 여러분이 작성한 intercept() 핸들러가 반환한 모든 프로미스 작업이 끝났음을 의미합니다. 이는 앞서 언급했듯이 Mapssuccess 이벤트가 발생할 때 NavigationTransition.finished 프로미스가 성공하는 것과 동일합니다.
  • 두 프로미스 중 하나라도 실패(reject)하면: 어떤 이유로든 네비게이션이 실패했음을 의미합니다.

상태 (State)

Navigation API를 사용하면 각각의 히스토리 항목마다 고유한 상태(state)를 저장할 수 있습니다. 이건 개발자가 자유롭게 정의할 수 있는 정보로, 원하는 건 무엇이든 넣을 수 있죠. 예를 들어 해당 뷰(화면)를 몇 번 방문했는지 기록하는 visitCount 속성을 넣거나, 나중에 사용자가 다시 이 화면으로 뒤로 가기를 눌렀을 때 이전 상태를 완벽히 복원하기 위해 UI 상태와 관련된 여러 속성들을 담은 객체를 통째로 저장해 둘 수도 있습니다. (Zustand 같은 전역 상태의 일부 스냅샷을 넣어두는 식이죠!)

NavigationHistoryEntry의 상태를 가져오려면 getState() 메서드를 호출합니다. 처음에는 undefined를 반환하지만, 항목에 상태 정보가 설정되고 나면 그 저장된 값을 반환합니다.

하지만 상태를 설정(Setting)하는 것은 조금 뉘앙스가 다릅니다. getState()로 값을 가져와서 그 객체를 직접 수정한다고 해서 히스토리 항목에 저장된 복사본이 바뀌지는 않습니다.
대신, Maps()reload()를 수행할 때 업데이트해야 합니다. 이 메서드들은 선택적으로 options 객체를 매개변수로 받는데, 이 객체 안에 새로운 상태를 담은 state 속성을 넣어주면 됩니다. 이 네비게이션이 확정(commit)될 때 상태 변경 사항이 자동으로 적용됩니다.

그런데 어떤 경우에는 화면 이동이나 새로고침 없이도 상태만 살짝 업데이트하고 싶을 때가 있습니다. (예를 들어, 펼치기/접기가 가능한 <details> 요소의 현재 펼쳐짐 상태만 히스토리에 살짝 저장해 둬서, 나중에 뒤로 가기로 돌아오거나 브라우저를 재시작했을 때 똑같이 펼쳐져 있게 만들고 싶은 경우죠.) 이럴 때는 Navigation.updateCurrentEntry()를 사용합니다. 현재 항목의 변경이 완료되면 currententrychange 이벤트가 발생합니다.


한계점 (Limitations)

강력한 Navigation API에도 현재 명세상 몇 가지 한계점이 존재합니다.

  1. 현재 스펙에서는 페이지의 첫 번째 로드(First Load) 시에는 Maps 이벤트를 발생시키지 않습니다. 이는 서버 사이드 렌더링(SSR)을 사용하는 사이트에는 괜찮습니다. 서버가 올바른 초기 상태를 HTML로 내려주는 것이 사용자에게 콘텐츠를 제공하는 가장 빠른 방법이니까요. 하지만 클라이언트 측 자바스크립트 코드에 전적으로 의존해서 페이지를 그려내는(CSR) 사이트들은 초기 페이지 구성을 위해 추가적인 초기화 함수가 필요할 수 있습니다.
  2. Navigation API는 오직 단일 프레임(최상위 페이지 또는 하나의 특정 <iframe> 내부) 안에서만 작동합니다. 이는 명세서에 더 자세히 문서화되어 있는 흥미로운 사실이지만, 실무적으로는 개발자들의 혼란을 줄여줄 것입니다. (구형 History API는 프레임 지원과 같이 직관적이지 않은 까다로운 엣지 케이스들이 너무 많았는데, Navigation API는 이를 깔끔하게 정리했습니다.)
  3. 현재로서는 Navigation API를 사용해 히스토리 목록 자체를 프로그래밍 방식으로 수정하거나 순서를 재배치할 수는 없습니다. 예를 들어, 사용자에게 임시 모달창을 띄워 정보를 입력받은 뒤 이전 URL로 돌려보내고, 이 '임시 모달창' 히스토리를 깔끔하게 삭제해서 사용자가 '앞으로 가기'를 눌러도 다시 모달창이 뜨지 않게 하고 싶을 때가 있죠. 하지만 아직 이런 식의 조작은 불가능합니다.

인터페이스 (Interfaces)

NavigateEvent Experimental

어떤 종류의 네비게이션이든 시작될 때 발생하는 navigate 이벤트에 전달되는 이벤트 객체입니다. 네비게이션에 대한 자세한 정보에 접근할 수 있게 해 주며, 가장 중요한 intercept() 메서드를 제공하여 네비게이션 시작 시 일어날 일들을 통제할 수 있게 해 줍니다.

Navigation Experimental

현재 window에서 일어나는 모든 네비게이션 작업들을 중앙에서 통제합니다. 코드로 이동을 지시하거나, 히스토리 항목들을 살펴보거나, 진행 중인 네비게이션을 관리하는 역할을 합니다.

NavigationActivation Experimental

최근에 발생한 문서 간(cross-document) 이동을 나타냅니다. 네비게이션 유형과 현재/목적지 문서의 히스토리 항목 정보를 담고 있습니다.

NavigationCurrentEntryChangeEvent Experimental

Navigation.currentEntry가 변경될 때 발생하는 currententrychange 이벤트에 전달되는 객체입니다. 이동 유형과 바로 직전에 머물렀던 이전 히스토리 항목에 대한 정보를 제공합니다.

NavigationDestination Experimental

현재 진행 중인 네비게이션에서 이동하려는 최종 '목적지'를 나타냅니다.

NavigationHistoryEntry Experimental

네비게이션 히스토리(방문 기록)의 단일 항목 하나를 나타냅니다.

NavigationPrecommitController Experimental

NavigateEvent.intercept() 호출 시 precommitHandler 콜백으로 전달되어, 확정되기 전(precommit)에 리다이렉트 동작을 정의할 수 있게 해줍니다.

NavigationTransition Experimental

현재 활발하게 진행 중인 네비게이션 프로세스를 나타냅니다.


다른 인터페이스로의 확장 (Extensions to other interfaces)

Window.navigation 읽기 전용 Experimental

현재 window 객체와 연결된 Navigation 객체를 반환합니다. 이것이 바로 Navigation API를 사용하기 위한 진입점(entry point)입니다.


예제 (Examples)

참고:
Navigation API 라이브 데모를 직접 확인해 보세요. (데모 소스 코드 보기)

intercept()를 사용한 네비게이션 처리 (Handling a navigation using intercept())

navigation.addEventListener("navigate", (event) => {
  // 교차 출처(cross-origin) 이동 같은 특정 네비게이션은 가로챌 수 없습니다.
  // 이럴 때는 바로 return 해서 브라우저가 기본 동작을 하도록 놔둡니다.
  if (!event.canIntercept) {
    return;
  }

  // 해시(#) 이동이나 파일 다운로드 요청도 가로채지 않습니다.
  if (event.hashChange || event.downloadRequest !== null) {
    return;
  }

  const url = new URL(event.destination.url);

  // 사용자가 '/articles/' 경로로 이동하려고 할 때만 개입합니다!
  if (url.pathname.startsWith("/articles/")) {
    event.intercept({
      async handler() {
        // 이미 URL은 바뀌었기 때문에, 새로운 콘텐츠를 Fetch로 
        // 가져오는 동안 스피너(spinner)나 로딩 화면을 먼저 띄워줍니다.
        renderArticlePagePlaceholder();

        // 목적지 URL에 맞는 새로운 콘텐츠를 가져와서 다 받아지면 화면에 렌더링합니다!
        const articleContent = await getArticleContent(url.pathname);
        renderArticlePage(articleContent);
      },
    });
  }
});

scroll()을 사용한 수동 스크롤 처리 (Handling scrolling using scroll())

이 예제에서는 handler() 함수가 동작할 때 주요 기사(article) 콘텐츠를 먼저 가져와서 렌더링하고, 그다음에 부차적인 콘텐츠(secondary content, 예를 들어 댓글이나 추천 기사 등)를 마저 가져와 렌더링합니다.
이런 경우, 부차적 콘텐츠가 다 그려질 때까지 기다리는 것보다는 핵심인 기사 본문이 화면에 준비되자마자 즉시 그곳으로 스크롤을 이동시켜 사용자가 글을 읽을 수 있게 해주는 것이 훨씬 좋은 경험을 제공하겠죠? 이를 위해 두 렌더링 작업 사이에 scroll() 호출을 끼워 넣었습니다.

navigation.addEventListener("navigate", (event) => {
  // 가로챌 수 없는 상황이면 빠른 종료
  if (
    !event.canIntercept ||
    event.hashChange ||
    event.downloadRequest !== null
  ) {
    return;
  }

  const url = new URL(event.destination.url);

  if (url.pathname.startsWith("/articles/")) {
    event.intercept({
      async handler() {
        // 1. 메인 기사 본문을 가져와 렌더링합니다.
        const articleContent = await getArticleContent(url.pathname);
        renderArticlePage(articleContent);

        // 2. 본문이 다 그려졌으니, 브라우저 스크롤을 그곳으로 이동시킵니다!
        event.scroll();

        // 3. 사용자가 글을 읽기 시작한 동안 부가적인 콘텐츠를 마저 불러와 렌더링합니다.
        const secondaryContent = await getSecondaryContent(url.pathname);
        addSecondaryContent(secondaryContent);
      },
    });
  }
});

특정 히스토리 항목으로 이동하기 (Traversing to a specific history entry)

// 앱이 처음 시작될 때 첫 번째 로드된 페이지의 고유 'key'를 저장해 둡니다.
// 이렇게 하면 사용자가 언제든지 한 방에 첫 화면(Home)으로 돌아올 수 있게 만들 수 있습니다.
const { key } = navigation.currentEntry;
backToHomeButton.onclick = () => navigation.traverseTo(key);

// 사용자가 다른 여러 페이지로 돌아다니더라도, 
// 위에 만든 '첫 화면으로 가기' 버튼은 저장해둔 key 덕분에 항상 훌륭하게 작동할 것입니다.
await navigation.navigate("/another_url").finished;

상태 업데이트하기 (Updating state)

이동과 함께 상태를 전달할 때:

navigation.navigate(url, { state: newState });

또는 새로고침과 함께 상태를 전달할 때:

navigation.reload({ state: newState });

만약 네비게이션(이동)이나 새로고침 없이 조용히 상태 값만 갱신하고 싶을 때:

navigation.updateCurrentEntry({ state: newState });

이 Navigation API는 Next.js의 App Router 내부 동작 원리를 이해하는 데도 아주 큰 도움이 됩니다. 혹시 이 API를 활용해 간단한 라우터 클래스를 직접 만들어보는 예제를 더 살펴볼까요?

profile
프론트에_가까운_풀스택_개발자

0개의 댓글