최신 클라이언트 측 라우팅: Navigation API

싱글 페이지 애플리케이션 빌드를 완전히 개편하는 새로운 API를 통해 클라이언트 측 라우팅을 표준화합니다.

Browser Support

  • Chrome: 102.
  • Edge: 102.
  • Firefox: 147.
  • Safari: 26.2.

Source

단일 페이지 애플리케이션(SPA)은 사용자가 사이트와 상호작용할 때 서버에서 완전히 새로운 페이지를 로드하는 기본 방법 대신 콘텐츠를 동적으로 다시 작성하는 핵심 기능으로 정의됩니다.

SPA는 History API를 통해 (또는 제한된 경우 사이트의 #hash 부분을 조정하여) 이 기능을 제공할 수 있었지만, 이는 SPA가 일반화되기 훨씬 전에 개발된 투박한 API이며 웹에서는 완전히 새로운 접근 방식이 필요합니다. Navigation API는 History API의 거친 부분을 단순히 패치하는 대신 이 공간을 완전히 개편하는 제안된 API입니다. (예를 들어 스크롤 복원은 History API를 재창조하려고 하지 않고 패치를 적용했습니다.)

이 게시물에서는 Navigation API를 개략적으로 설명합니다. 기술 제안서를 읽으려면 WICG 저장소의 초안 보고서를 참고하세요.

사용 예

Navigation API를 사용하려면 먼저 전역 navigation 객체에 "navigate" 리스너를 추가합니다. 이 이벤트는 기본적으로 중앙 집중식입니다. 사용자가 링크를 클릭하거나, 양식을 제출하거나, 뒤로/앞으로 이동하는 등의 작업을 수행한 경우 또는 탐색이 프로그래매틱 방식으로(즉, 사이트의 코드를 통해) 트리거된 경우 등 모든 유형의 탐색에 대해 발생합니다. 대부분의 경우 코드가 해당 작업에 대한 브라우저의 기본 동작을 재정의할 수 있습니다. SPA의 경우 사용자를 동일한 페이지에 유지하고 사이트 콘텐츠를 로드하거나 변경하는 것이 좋습니다.

NavigateEvent는 대상 URL과 같은 탐색에 관한 정보를 포함하는 "navigate" 리스너에 전달되며, 이를 통해 한 중앙 위치에서 탐색에 응답할 수 있습니다. 기본 "navigate" 리스너는 다음과 같습니다.

navigation.addEventListener('navigate', navigateEvent => {
  // Exit early if this navigation shouldn't be intercepted.
  // The properties to look at are discussed later in the article.
  if (shouldNotIntercept(navigateEvent)) return;

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

  if (url.pathname === '/') {
    navigateEvent.intercept({handler: loadIndexPage});
  } else if (url.pathname === '/cats/') {
    navigateEvent.intercept({handler: loadCatsPage});
  }
});

다음 두 가지 방법 중 하나로 탐색을 처리할 수 있습니다.

  • 탐색을 처리하기 위해 intercept({ handler })를 호출합니다 (위 설명 참고).
  • preventDefault()를 호출하여 탐색을 완전히 취소할 수 있습니다.

이 예시에서는 이벤트에서 intercept()를 호출합니다. 브라우저가 handler 콜백을 호출하며, 이 콜백은 사이트의 다음 상태를 구성해야 합니다. 이렇게 하면 다른 코드에서 탐색 진행 상황을 추적하는 데 사용할 수 있는 전환 객체 navigation.transition가 생성됩니다.

intercept()preventDefault()는 일반적으로 허용되지만 호출할 수 없는 경우도 있습니다. 탐색이 교차 출처 탐색인 경우 intercept()를 통해 탐색을 처리할 수 없습니다. 또한 사용자가 브라우저에서 뒤로 또는 앞으로 버튼을 누르는 경우 preventDefault()를 통해 탐색을 취소할 수 없습니다. 사용자가 사이트에 갇히지 않도록 해야 합니다. (이 문제는 GitHub에서 논의 중입니다.)

탐색 자체를 중지하거나 가로챌 수 없더라도 "navigate" 이벤트는 계속 발생합니다. 정보를 제공하므로 코드가 예를 들어 사용자가 사이트를 떠나는 것을 나타내는 애널리틱스 이벤트를 로깅할 수 있습니다.

플랫폼에 다른 이벤트를 추가하는 이유는 무엇인가요?

"navigate" 이벤트 리스너는 SPA 내에서 URL 변경사항 처리를 중앙 집중화합니다. 이전 API를 사용하면 이 제안이 어렵습니다. History API를 사용하여 자체 SPA의 라우팅을 작성한 적이 있다면 다음과 같은 코드를 추가했을 수 있습니다.

function updatePage(event) {
  event.preventDefault(); // we're handling this link
  window.history.pushState(null, '', event.target.href);
  // TODO: set up page based on new URL
}
const links = [...document.querySelectorAll('a[href]')];
links.forEach(link => link.addEventListener('click', updatePage));

이 방법은 괜찮지만 완전하지는 않습니다. 링크는 페이지에 표시되기도 하고 표시되지 않기도 하며, 사용자가 페이지를 탐색할 수 있는 유일한 방법은 아닙니다. 예를 들어 양식을 제출하거나 이미지 맵을 사용할 수 있습니다. 페이지에서 이러한 문제를 처리할 수도 있지만, 새로운 탐색 API를 통해 간소화할 수 있는 가능성이 많습니다.

또한 위에서는 뒤로/앞으로 탐색을 처리하지 않습니다. 이와 관련된 다른 이벤트가 있습니다("popstate").

개인적으로는 History API가 이러한 가능성을 지원하는 데 도움이 될 수 있다고 생각합니다. 하지만 브라우저에서 사용자가 뒤로 또는 앞으로를 누르는 경우에 응답하고 URL을 푸시하고 바꾸는 두 가지 표면 영역만 있습니다. 위에서 설명한 것처럼 클릭 이벤트 리스너를 수동으로 설정하는 경우를 제외하고 "navigate"에 대한 비유가 없습니다.

탐색 처리 방법 결정

navigateEvent에는 특정 탐색을 처리하는 방법을 결정하는 데 사용할 수 있는 탐색에 관한 많은 정보가 포함되어 있습니다.

주요 속성은 다음과 같습니다.

canIntercept
이 값이 false이면 탐색을 가로챌 수 없습니다. 크로스 오리진 탐색 및 크로스 문서 트래버설은 가로챌 수 없습니다.
destination.url
탐색을 처리할 때 고려해야 할 가장 중요한 정보일 것입니다.
hashChange
탐색이 동일한 문서이고 해시가 현재 URL과 다른 URL의 유일한 부분인 경우 true입니다. 최신 SPA에서 해시는 현재 문서의 여러 부분에 연결하는 데 사용해야 합니다. 따라서 hashChange가 true이면 이 탐색을 가로채지 않아도 됩니다.
downloadRequest
이 값이 true이면 탐색이 download 속성이 있는 링크에 의해 시작된 것입니다. 대부분의 경우 이를 가로챌 필요가 없습니다.
formData
이 값이 null이 아니면 이 탐색이 POST 양식 제출의 일부입니다. 내비게이션을 처리할 때 이 점을 고려해야 합니다. GET 탐색만 처리하려면 formData이 null이 아닌 탐색을 가로채지 마세요. 이 도움말의 뒷부분에 있는 양식 제출 처리 예시를 참고하세요.
navigationType
"reload", "push", "replace", "traverse" 중 하나입니다. "traverse"인 경우 preventDefault()을 통해 이 탐색을 취소할 수 없습니다.

예를 들어 첫 번째 예에 사용된 shouldNotIntercept 함수는 다음과 같을 수 있습니다.

function shouldNotIntercept(navigationEvent) {
  return (
    !navigationEvent.canIntercept ||
    // If this is just a hashChange,
    // just let the browser handle scrolling to the content.
    navigationEvent.hashChange ||
    // If this is a download,
    // let the browser perform the download.
    navigationEvent.downloadRequest ||
    // If this is a form submission,
    // let that go to the server.
    navigationEvent.formData
  );
}

가로채기

코드가 "navigate" 리스너 내에서 intercept({ handler })를 호출하면 브라우저에 이제 새 업데이트된 상태를 위해 페이지를 준비하고 있으며 탐색에 시간이 걸릴 수 있다고 알립니다.

브라우저는 현재 상태의 스크롤 위치를 캡처하여 나중에 선택적으로 복원할 수 있도록 한 후 handler 콜백을 호출합니다. handler가 프라미스를 반환하면 (비동기 함수를 사용하면 자동으로 발생) 해당 프라미스는 브라우저에 탐색이 얼마나 걸리는지, 성공 여부를 알려줍니다.

navigation.addEventListener('navigate', navigateEvent => {
  if (shouldNotIntercept(navigateEvent)) return;
  const url = new URL(navigateEvent.destination.url);

  if (url.pathname.startsWith('/articles/')) {
    navigateEvent.intercept({
      async handler() {
        const articleContent = await getArticleContent(url.pathname);
        renderArticlePage(articleContent);
      },
    });
  }
});

따라서 이 API는 브라우저가 이해하는 시맨틱 개념을 도입합니다. SPA 탐색이 현재 진행 중이며 시간이 지남에 따라 이전 URL과 상태에서 새 URL과 상태로 문서가 변경됩니다. 이를 통해 접근성을 비롯한 여러 가지 이점을 얻을 수 있습니다. 브라우저에서 탐색의 시작, 끝 또는 잠재적 실패를 표시할 수 있습니다. 예를 들어 Chrome은 기본 로드 표시기를 활성화하고 사용자가 중지 버튼과 상호작용할 수 있도록 허용합니다. (현재 사용자가 뒤로/앞으로 버튼을 통해 탐색하는 경우에는 발생하지 않지만 곧 수정될 예정입니다.)

탐색을 가로챌 때 새 URL은 handler 콜백이 호출되기 직전에 적용됩니다. DOM을 즉시 업데이트하지 않으면 이전 콘텐츠가 새 URL과 함께 표시되는 기간이 발생합니다. 이는 데이터를 가져오거나 새 하위 리소스를 로드할 때 상대 URL 확인과 같은 항목에 영향을 미칩니다.

URL 변경을 지연하는 방법이 GitHub에서 논의되고 있지만, 일반적으로 들어오는 콘텐츠의 자리표시자로 페이지를 즉시 업데이트하는 것이 좋습니다.

navigation.addEventListener('navigate', navigateEvent => {
  if (shouldNotIntercept(navigateEvent)) return;
  const url = new URL(navigateEvent.destination.url);

  if (url.pathname.startsWith('/articles/')) {
    navigateEvent.intercept({
      async handler() {
        // The URL has already changed, so quickly show a placeholder.
        renderArticlePagePlaceholder();
        // Then fetch the real data.
        const articleContent = await getArticleContent(url.pathname);
        renderArticlePage(articleContent);
      },
    });
  }
});

이렇게 하면 URL 해결 문제가 방지될 뿐만 아니라 사용자에게 즉시 응답하므로 속도도 빠릅니다.

중단 신호

intercept() 핸들러에서 비동기 작업을 할 수 있으므로 탐색이 중복될 수 있습니다. 다음과 같은 경우에 발생합니다.

  • 사용자가 다른 링크를 클릭하거나 일부 코드가 다른 탐색을 실행합니다. 이 경우 새 탐색이 우선시되어 이전 탐색은 사용되지 않습니다.
  • 사용자가 브라우저에서 '중지' 버튼을 클릭합니다.

이러한 가능성을 처리하기 위해 "navigate" 리스너에 전달된 이벤트에는 AbortSignalsignal 속성이 포함됩니다. 자세한 내용은 중단 가능한 가져오기를 참고하세요.

간단히 말해 작업을 중지해야 할 때 이벤트를 실행하는 객체를 제공합니다. 특히 탐색이 선점되면 진행 중인 네트워크 요청을 취소하는 AbortSignalfetch()에 대한 호출에 전달할 수 있습니다. 이렇게 하면 사용자의 대역폭이 절약되고 fetch()에서 반환된 Promise가 거부되어 이제 유효하지 않은 페이지 탐색을 표시하도록 DOM을 업데이트하는 등의 후속 코드가 실행되지 않습니다.

다음은 이전 예시에서 getArticleContent가 인라인으로 표시되어 AbortSignalfetch()와 함께 사용하는 방법을 보여줍니다.

navigation.addEventListener('navigate', navigateEvent => {
  if (shouldNotIntercept(navigateEvent)) return;
  const url = new URL(navigateEvent.destination.url);

  if (url.pathname.startsWith('/articles/')) {
    navigateEvent.intercept({
      async handler() {
        // The URL has already changed, so quickly show a placeholder.
        renderArticlePagePlaceholder();
        // Then fetch the real data.
        const articleContentURL = new URL(
          '/get-article-content',
          location.href
        );
        articleContentURL.searchParams.set('path', url.pathname);
        const response = await fetch(articleContentURL, {
          signal: navigateEvent.signal,
        });
        const articleContent = await response.json();
        renderArticlePage(articleContent);
      },
    });
  }
});

스크롤 처리

탐색을 intercept()하면 브라우저에서 자동으로 스크롤을 처리하려고 시도합니다.

새 기록 항목으로 이동하는 경우 (navigationEvent.navigationType"push" 또는 "replace"인 경우) URL 프래그먼트 (# 뒤에 오는 부분)로 표시된 부분으로 스크롤하거나 스크롤을 페이지 상단으로 재설정하려고 시도합니다.

새로고침 및 탐색의 경우 이는 이 기록 항목이 마지막으로 표시되었을 때의 스크롤 위치를 복원하는 것을 의미합니다.

기본적으로 이는 handler에서 반환된 프로미스가 해결된 후에 발생하지만 더 일찍 스크롤하는 것이 적절한 경우 navigateEvent.scroll()을 호출할 수 있습니다.

navigation.addEventListener('navigate', navigateEvent => {
  if (shouldNotIntercept(navigateEvent)) return;
  const url = new URL(navigateEvent.destination.url);

  if (url.pathname.startsWith('/articles/')) {
    navigateEvent.intercept({
      async handler() {
        const articleContent = await getArticleContent(url.pathname);
        renderArticlePage(articleContent);
        navigateEvent.scroll();

        const secondaryContent = await getSecondaryContent(url.pathname);
        addSecondaryContent(secondaryContent);
      },
    });
  }
});

또는 intercept()scroll 옵션을 "manual"로 설정하여 자동 스크롤 처리를 완전히 선택 해제할 수 있습니다.

navigateEvent.intercept({
  scroll: 'manual',
  async handler() {
    // …
  },
});

포커스 처리

handler에서 반환된 Promise가 확인되면 브라우저가 autofocus 속성이 설정된 첫 번째 요소 또는 해당 속성이 있는 요소가 없는 경우 <body> 요소에 포커스를 둡니다.

intercept()focusReset 옵션을 "manual"로 설정하여 이 동작을 선택 해제할 수 있습니다.

navigateEvent.intercept({
  focusReset: 'manual',
  async handler() {
    // …
  },
});

성공 및 실패 이벤트

intercept() 핸들러가 호출되면 다음 중 한 가지가 발생합니다.

  • 반환된 Promise가 완료되면 (또는 intercept()를 호출하지 않은 경우) Navigation API는 Event와 함께 "navigatesuccess"를 실행합니다.
  • 반환된 Promise가 거부되면 API는 ErrorEvent와 함께 "navigateerror"을 실행합니다.

이러한 이벤트를 사용하면 코드가 중앙 집중식으로 성공 또는 실패를 처리할 수 있습니다. 예를 들어 다음과 같이 이전에 표시된 진행 상태 표시기를 숨겨 성공을 처리할 수 있습니다.

navigation.addEventListener('navigatesuccess', event => {
  loadingIndicator.hidden = true;
});

또는 실패 시 오류 메시지를 표시할 수 있습니다.

navigation.addEventListener('navigateerror', event => {
  loadingIndicator.hidden = true; // also hide indicator
  showMessage(`Failed to load page: ${event.message}`);
});

ErrorEvent를 수신하는 "navigateerror" 이벤트 리스너는 새 페이지를 설정하는 코드에서 오류를 수신하도록 보장되므로 특히 유용합니다. 네트워크를 사용할 수 없는 경우 오류가 결국 "navigateerror"로 라우팅된다는 점을 알고 await fetch()하면 됩니다.

navigation.currentEntry는 현재 항목에 대한 액세스를 제공합니다. 사용자의 현재 위치를 설명하는 객체입니다. 이 항목에는 현재 URL, 시간이 지남에 따라 이 항목을 식별하는 데 사용할 수 있는 메타데이터, 개발자가 제공한 상태가 포함됩니다.

메타데이터에는 현재 항목과 슬롯을 나타내는 각 항목의 고유한 문자열 속성인 key가 포함됩니다. 이 키는 현재 항목의 URL이나 상태가 변경되더라도 동일하게 유지됩니다. 여전히 동일한 슬롯에 있습니다. 반대로 사용자가 뒤로 버튼을 누른 다음 동일한 페이지를 다시 열면 이 새 항목이 새 슬롯을 생성하므로 key가 변경됩니다.

개발자에게 key는 유용합니다. Navigation API를 사용하면 사용자를 키가 일치하는 항목으로 직접 이동할 수 있기 때문입니다. 다른 항목의 상태에서도 페이지 간에 쉽게 이동할 수 있도록 유지할 수 있습니다.

// On JS startup, get the key of the first loaded page
// so the user can always go back there.
const {key} = navigation.currentEntry;
backToHomeButton.onclick = () => navigation.traverseTo(key);

// Navigate away, but the button will always work.
await navigation.navigate('/another_url').finished;

Navigation API는 '상태'라는 개념을 표시합니다. 이는 개발자가 제공한 정보로, 현재 기록 항목에 영구적으로 저장되지만 사용자에게 직접 표시되지는 않습니다. 이는 History API의 history.state와 매우 유사하지만 개선되었습니다.

Navigation API에서는 현재 항목 (또는 항목)의 .getState() 메서드를 호출하여 상태의 사본을 반환할 수 있습니다.

console.log(navigation.currentEntry.getState());

기본적으로 undefined입니다.

설정 상태

상태 객체는 변경될 수 있지만 이러한 변경사항은 기록 항목과 함께 다시 저장되지 않으므로 다음 사항에 유의해야 합니다.

const state = navigation.currentEntry.getState();
console.log(state.count); // 1
state.count++;
console.log(state.count); // 2
// But:
console.info(navigation.currentEntry.getState().count); // will still be 1

상태를 설정하는 올바른 방법은 스크립트 탐색 중에 설정하는 것입니다.

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

여기서 newState복제 가능한 객체일 수 있습니다.

현재 항목의 상태를 업데이트하려면 현재 항목을 대체하는 탐색을 실행하는 것이 좋습니다.

navigation.navigate(location.href, {state: newState, history: 'replace'});

그러면 "navigate" 이벤트 리스너가 navigateEvent.destination를 통해 이 변경사항을 선택할 수 있습니다.

navigation.addEventListener('navigate', navigateEvent => {
  console.log(navigateEvent.destination.getState());
});

동기적으로 상태 업데이트

일반적으로 navigation.reload({state: newState})를 통해 비동기적으로 상태를 업데이트하는 것이 좋습니다. 그러면 "navigate" 리스너가 해당 상태를 적용할 수 있습니다. 하지만 사용자가 <details> 요소를 전환하거나 사용자가 양식 입력의 상태를 변경하는 경우와 같이 코드가 상태 변경을 감지할 때 이미 상태 변경이 완전히 적용된 경우도 있습니다. 이 경우 다시 로드 및 탐색을 통해 이러한 변경사항이 유지되도록 상태를 업데이트하는 것이 좋습니다. updateCurrentEntry()를 사용하여 이 작업을 수행할 수 있습니다.

navigation.updateCurrentEntry({state: newState});

이 변경사항에 대해 알아볼 수 있는 이벤트도 있습니다.

navigation.addEventListener('currententrychange', () => {
  console.log(navigation.currentEntry.getState());
});

하지만 "currententrychange"에서 상태 변경에 반응하는 경우 "navigate" 이벤트와 "currententrychange" 이벤트 간에 상태 처리 코드를 분할하거나 중복할 수 있습니다. 반면 navigation.reload({state: newState})를 사용하면 한곳에서 처리할 수 있습니다.

상태와 URL 매개변수

상태는 구조화된 객체일 수 있으므로 모든 애플리케이션 상태에 사용하는 것이 좋습니다. 하지만 대부분의 경우 URL에 상태를 저장하는 것이 좋습니다.

사용자가 다른 사용자와 URL을 공유할 때 상태가 유지되기를 바란다면 URL에 저장하세요. 그렇지 않으면 상태 객체가 더 나은 옵션입니다.

모든 항목에 액세스

하지만 '현재 항목'이 전부는 아닙니다. 또한 API는 사용자가 사이트를 사용하는 동안 탐색한 전체 항목 목록에 액세스하는 방법을 제공합니다. navigation.entries() 호출을 통해 항목의 스냅샷 배열을 반환합니다. 예를 들어 사용자가 특정 페이지로 이동한 방식에 따라 다른 UI를 표시하거나 이전 URL 또는 상태를 다시 살펴보는 데 사용할 수 있습니다. 현재 기록 API로는 불가능합니다.

브라우저 기록에 더 이상 포함되지 않는 항목이 있을 때 발생하는 개별 NavigationHistoryEntry에서 "dispose" 이벤트를 수신할 수도 있습니다. 일반 정리의 일부로 발생할 수도 있고 탐색 중에 발생할 수도 있습니다. 예를 들어 10개의 장소를 뒤로 이동한 다음 앞으로 이동하면 해당 10개의 기록 항목이 삭제됩니다.

위에서 언급한 바와 같이 "navigate" 이벤트는 모든 유형의 탐색에 대해 발생합니다. (실제로 가능한 모든 유형의 긴 부록이 사양에 있습니다.)

많은 사이트에서 가장 일반적인 경우는 사용자가 <a href="...">을 클릭하는 경우이지만, 다룰 만한 두 가지 주목할 만한 복잡한 탐색 유형이 있습니다.

프로그래매틱 탐색

첫 번째는 프로그래매틱 탐색으로, 클라이언트 측 코드 내의 메서드 호출로 인해 탐색이 발생합니다.

코드의 어느 곳에서나 navigation.navigate('/another_page')을 호출하여 탐색을 유발할 수 있습니다. 이는 "navigate" 리스너에 등록된 중앙 집중식 이벤트 리스너에 의해 처리되며 중앙 집중식 리스너가 동기식으로 호출됩니다.

이는 location.assign()와 같은 이전 메서드와 History API의 메서드 pushState()replaceState()의 개선된 집계로 의도되었습니다.

navigation.navigate() 메서드는 { committed, finished }에 두 개의 Promise 인스턴스가 포함된 객체를 반환합니다. 이를 통해 호출자는 전환이 '커밋' (표시되는 URL이 변경되고 새 NavigationHistoryEntry를 사용할 수 있음)되거나 '완료' (intercept({ handler })에서 반환된 모든 약속이 완료됨 또는 실패하거나 다른 탐색에 의해 선점되어 거부됨)될 때까지 기다릴 수 있습니다.

navigate 메서드에는 다음을 설정할 수 있는 옵션 객체도 있습니다.

  • state: 새 기록 항목의 상태입니다. NavigationHistoryEntry.getState() 메서드를 통해 사용할 수 있습니다.
  • history: 현재 기록 항목을 대체하도록 "replace"로 설정할 수 있습니다.
  • info: navigateEvent.info를 통해 navigate 이벤트에 전달할 객체입니다.

특히 info는 다음 페이지가 표시되도록 하는 특정 애니메이션을 나타내는 데 유용할 수 있습니다. (대안은 전역 변수를 설정하거나 #hash의 일부로 포함하는 것일 수 있습니다. 두 옵션 모두 약간 어색합니다.) 특히 이 info는 사용자가 나중에 뒤로 및 앞으로 버튼을 통해 탐색을 유발하는 경우 재생되지 않습니다. 이 경우 항상 undefined입니다.

왼쪽 또는 오른쪽에서 열기 데모

navigation에는 { committed, finished }가 포함된 객체를 반환하는 여러 탐색 메서드도 있습니다. traverseTo() (사용자 기록의 특정 항목을 나타내는 key를 허용함) 및 navigate()는 이미 언급했습니다. back(), forward(), reload()도 포함됩니다. 이러한 메서드는 모두 중앙 집중식 "navigate" 이벤트 리스너에 의해 navigate()와 마찬가지로 처리됩니다.

양식 제출

둘째, POST를 통한 HTML <form> 제출은 특별한 유형의 탐색이며 Navigation API가 이를 가로챌 수 있습니다. 추가 페이로드가 포함되지만 탐색은 여전히 "navigate" 리스너에 의해 중앙에서 처리됩니다.

NavigateEvent에서 formData 속성을 찾아 양식 제출을 감지할 수 있습니다. 다음은 fetch()를 통해 모든 양식 제출을 현재 페이지에 유지되는 양식 제출로 변환하는 예입니다.

navigation.addEventListener('navigate', navigateEvent => {
  if (navigateEvent.formData && navigateEvent.canIntercept) {
    // User submitted a POST form to a same-domain URL
    // (If canIntercept is false, the event is just informative:
    // you can't intercept this request, although you could
    // likely still call .preventDefault() to stop it completely).

    navigateEvent.intercept({
      // Since we don't update the DOM in this navigation,
      // don't allow focus or scrolling to reset:
      focusReset: 'manual',
      scroll: 'manual',
      handler() {
        await fetch(navigateEvent.destination.url, {
          method: 'POST',
          body: navigateEvent.formData,
        });
        // You could navigate again with {history: 'replace'} to change the URL here,
        // which might indicate "done"
      },
    });
  }
});

누락된 정보

"navigate" 이벤트 리스너의 중앙 집중식 특성에도 불구하고 현재 Navigation API 사양은 페이지의 첫 번째 로드에서 "navigate"를 트리거하지 않습니다. 모든 상태에 서버 측 렌더링 (SSR)을 사용하는 사이트의 경우 서버에서 올바른 초기 상태를 반환할 수 있으므로 콘텐츠를 사용자에게 제공하는 가장 빠른 방법이 될 수 있습니다. 하지만 클라이언트 측 코드를 활용하여 페이지를 만드는 사이트는 페이지를 초기화하는 추가 함수를 만들어야 할 수 있습니다.

Navigation API의 또 다른 의도적인 설계 선택은 단일 프레임(즉, 최상위 페이지 또는 단일 특정 <iframe>) 내에서만 작동한다는 것입니다. 여기에는 사양에 자세히 설명된 몇 가지 흥미로운 의미가 있지만 실제로는 개발자의 혼란을 줄여줍니다. 이전 History API에는 프레임 지원과 같은 혼동스러운 특이 사례가 많이 있었지만, 새롭게 설계된 Navigation API는 처음부터 이러한 특이 사례를 처리합니다.

마지막으로 사용자가 탐색한 항목 목록을 프로그래매틱 방식으로 수정하거나 재정렬하는 데는 아직 합의가 이루어지지 않았습니다. 이 문제는 현재 논의 중이지만, 한 가지 옵션은 기록 항목 또는 '모든 향후 항목'과 같은 삭제만 허용하는 것입니다. 후자를 사용하면 임시 상태가 허용됩니다. 예를 들어 개발자는 다음 작업을 할 수 있습니다.

  • 새 URL 또는 상태로 이동하여 사용자에게 질문하기
  • 사용자가 작업을 완료하거나 뒤로 이동할 수 있도록 허용
  • 작업 완료 시 기록 항목 삭제

이는 임시 모달이나 인터스티셜에 적합합니다. 새 URL은 사용자가 뒤로 동작을 사용하여 종료할 수 있지만 항목이 삭제되었으므로 실수로 앞으로 이동하여 다시 열 수는 없습니다. 현재 기록 API로는 불가능합니다.

Navigation API 사용해 보기

Navigation API는 Chrome 102에서 플래그 없이 사용할 수 있습니다. Domenic Denicola데모를 사용해 볼 수도 있습니다.

클래식 History API는 간단해 보이지만 잘 정의되어 있지 않으며, 브라우저마다 다르게 구현된 코너 사례와 관련해 많은 문제가 있습니다. 새 탐색 API에 관한 의견을 보내주시면 감사하겠습니다.

참조

감사의 말씀

이 게시물을 검토해 주신 토마스 스타이너, 도메닉 데니콜라, 네이트 채핀에게 감사드립니다.