웹 네비게이션 전략 알아보기

서버 사이드 네비게이션과 클라이언트 사이드 네비게이션에 대해 알아봅니다.

2025-02-02

서버 사이드 네비게이션

서버 사이드 네비게이션은 사용자가 링크를 클릭하거나 URL을 입력할 때 브라우저가 서버에 HTTP 요청(일반적으로 GET 방식)을 보내고, 서버는 해당 요청에 맞는 완전한 HTML 문서를 생성하여 응답합니다. 이 과정에서 브라우저는 새 페이지의 모든 리소스를 다시 불러오므로 전체 페이지가 새로고침됩니다. 또한 페이지 간 이동 시 전체 DOM 트리가 다시 렌더링 됩니다.

작동 원리

  • HTTP 요청 발생: 사용자가 링크(a 태그)나 버튼을 클릭하면 브라우저는 해당 URL로 HTTP 요청을 보냅니다.
  • 서버 응답: 서버는 요청에 따라 해당 페이지를 HTML, CSS, JavaScript 등의 리소스와 함께 응답합니다.
  • 전체 페이지 새로고침: 브라우저는 응답받은 완성된 HTML을 이용해 페이지 전체를 다시 렌더링 합니다. 이 과정에서 브라우저는 기존에 있던 DOM 트리를 파기하고 새로운 트리를 생성합니다.

구현 방식

  • <a href="..."> 태그를 이용한 페이지 이동

  • window.location 객체를 이용한 페이지 이동

    • href 속성을 이용한 페이지 이동 -> assign() 메서드와 동일하게 동작합니다.
    • assign(): 페이지를 히스토리 스택에 추가합니다. 뒤로 가기 버튼을 누르면 이전 페이지로 이동합니다.
    • replace(): 페이지를 히스토리 스택에서 완전히 교체합니다. 뒤로 가기 버튼을 눌러도 이전 페이지로 이동하지 않습니다.
  • <form> 태그를 이용한 페이지 이동

    • <form action="..." method="GET">
    • form 요소를 이용해 GET 방식으로 페이지 이동을 할 수 있습니다.
  • meta 태그를 이용한 페이지 이동

    • <meta http-equiv="refresh" content="0;url=...">
    • content 속성에 지정된 시간(초) 후에 지정된 URL로 이동합니다.

장점

  • SEO: 서버 사이드 렌더링은 검색 엔진 최적화(SEO)에 유리합니다. 검색 엔진은 페이지의 내용을 크롤링 할 때 HTML을 읽어야 하는데, 서버 사이드 네비게이션은 완성된 HTML을 제공하므로 검색 엔진이 쉽게 인덱싱할 수 있습니다.
  • 단순한 구현: 클라이언트 사이드의 복잡한 로직 없이, 링크 클릭 시 기본 동작으로 동작하므로 상대적으로 구현이 단순합니다.
  • 브라우저 호환성: 스크립트가 비활성 되어 있거나 오래된 브라우저에서도 동작하므로 기본 웹 환경에서 안정적입니다.

단점

  • 느린 전환 속도: 전체 페이지를 새로고침하므로 필요한 리소스와 레이아웃 등 모든 것을 다시 불러와야 하므로 전환 속도가 느립니다. 사용자의 경우 페이지가 깜빡거리는 현상을 경험할 수 있습니다.
  • 불필요한 리소스 다운로드: 전체 페이지를 새로 렌더링 하므로 이미 존재하는 리소스(공통 레이아웃, 공통 요소)를 다시 다운로드해야 합니다. 이는 불필요한 네트워크 트래픽을 발생시킵니다. -> 서버 부하 증가
  • 상태 관리 어려움: 페이지 전환 시 클라이언트에 남아있던 상태(스크롤 위치, 입력값 등)가 초기화되므로 상태를 유지하기 어렵습니다.

이와 같은 서버 사이드 네비게이션의 단점을 보완하기 위해 클라이언트 사이드 네비게이션을 사용할 수 있습니다.

클라이언트 사이드 네비게이션

클라이언트 사이드 네비게이션은 사용자가 애플리케이션 내에서 이동할 때 전체 페이지 새로고침 없이, 필요한 부분만 동적으로 변경하는 방식입니다. 위의 서버 사이드 네비게이션과 달리 초기 로드 후 클라이언트 사이드에서 스크립트가 라우팅 및 렌더링을 담당합니다.

작동 원리

  • 초기 페이지 로드: 브라우저는 처음에 최소한의 혹은 초기 상태의 HTML 문서를 서버에서 받아옵니다. 이때 서버는 클라이언트 사이드 라우팅을 위한 스크립트 번들을 함께 제공합니다.
  • 라우팅 관리: 클라이언트 내에 존재하는 라우터가 현재 URL을 파악하고, 해당 URL에 맞는 컴포넌트 혹은 뷰를 렌더링 합니다. URL 변경 시 전체 페이지를 새로 고치는 대신 DOM의 일부분만 업데이트합니다.
  • History API: 브라우저의 History 객체를 이용해 URL을 변경하고, 뒤로/앞으로 가기 기능을 처리합니다.

장점

  • 빠른 전환 속도: 전체 페이지를 새로고침하지 않고 필요한 부분만 업데이트하므로 전환 속도가 빠릅니다. 사용자는 페이지가 깜빡거리지 않고 부드럽게 전환되는 경험을 할 수 있습니다. -> 네이티브 앱과 유사한 사용자 경험 제공
  • 상태 관리 용이: 페이지 전환 시 상태를 유지할 수 있습니다. 브라우저 히스토리를 이용해 뒤로/앞으로 가기 기능을 제공하면서 상태를 유지할 수 있습니다.
  • 효율적인 리소스 관리: 전체 HTML을 매번 요청하는 대신, 변경되는 부분만 갱신하므로 불필요한 리소스 다운로드를 줄일 수 있습니다. -> 네트워크 트래픽 감소, 서버 부하 감소

단점

  • 초기 로딩 비용 증가: SPA 등 클라이언트 사이드 네비게이션을 구현하는 경우, 초기 로딩 시 필요한 리소스(JavaScript, CSS, 이미지 등)를 모두 다운로드해야 하므로 초기 로딩 속도가 느릴 수 있습니다.
  • SEO 이슈: 서버에서 생성된 HTML이 아닌 동적으로 구성된 컨텐츠를 제공하므로 검색 엔진 최적화(SEO)에 불리할 수 있습니다. -> SSR(Server-Side Rendering), SSG(Static Site Generation) 또는 Prerendering 등을 이용해 SEO 문제를 해결할 수 있습니다.
  • 클라이언트 의존성: 클라이언트 성능에 의존하므로, 저사양 기기나 느린 네트워크 환경에서는 성능 이슈가 발생할 수 있습니다.

클라이언트 사이드 네비게이션은 사용자 상호작용 측면에서 부드러운 전환을 제공하지만, 초기 로딩 속도 및 SEO 최적화에는 한계가 있을 수 있습니다. 이런 문제를 해결하기 위해 SSR과 SSG 등을 이용해 SEO 문제를 해결하거나 초기 로딩 속도를 개선할 수 있습니다. -> Next.js와 같은 프레임워크

클라이언트 사이드 네비게이션 구현 방식

History / Navigation API

History

브라우저의 세션 기록(History)을 제어하여 페이지 이동을 프로그래밍적으로 처리할 수 있도록 해줍니다. 주로 페이지 간 이동이나 SPA(Single Page Application) 구현 시 URL 변경을 처리할 때 사용됩니다. popstate 이벤트를 통해 브라우저의 뒤로/앞으로 가기 동작에 대응할 수 있습니다.

  • 주요 메서드
    • pushState(state, unused, url): 현재 페이지를 새로고침하지 않고 새로운 히스토리 항목을 추가합니다.
      • state: 해당 항목과 연관된 상태 객체
      • unused: 대부분의 브라우저에서는 현재 무시됨 -> 빈 문자열 전달하는 것이 안전
      • url: 주소 표시줄에 나타날 URL
    • replaceState(state, title, url): 현재 히스토리 항목을 새로운 데이터로 대체합니다. (새 항목 생성 x)
    • back(): 히스토리 스택을 한 단계 뒤로 이동하며, 브라우저의 뒤로 가기와 동일한 효과를 나타냅니다.
    • forward(): 히스토리 스택을 앞으로 이동합니다.
    • go(n): 히스토리 스택에서 상대적인 위치(음수는 뒤로, 양수는 앞으로)로 이동하거나, 매개변수가 0이면 현재 페이지를 새로고침합니다.
history.pushState({ page: "home" }, "home", "/home");
 
history.replaceState({ page: "about" }, "about", "/about");
 
const goBack = () => {
  window.history.back();
};
 
const refreshPage = () => {
  window.history.go(0);
};
 
window.addEventListener("popstate", (event) => {
  console.log("현재 상태:", event.state);
});

Navigation

보다 세밀한 네비게이션을 위해 등장한 API로, 프로그래밍적으로 코드 내에서 네비게이션을 트리거 하거나 (navigate()) navigate 이벤트를 가로채 추가 처리를 할 수 있도록 해줍니다. 기존 History API 기능을 보완하면서, 네비게이션의 시작, 커밋 및 완료 상태를 프로미스 객체로 관리합니다.

  • 주요 메서드
    • navigate(url, options): 지정된 URL로 프로그램적으로 이동합니다. 옵션 객체에 상태 등 추가 정보를 전달할 수 있습니다. 프로미스를 반환하여 네비게이션 상태(커밋/완료)를 추적할 수 있습니다.
    • reload(options): 현재 페이지를 새로고침하며, 새 상태를 지정할 수 있습니다.
    • traverseTo(key): 저장해둔 특정 히스토리 key를 사용해 과거 특정 기록으로 이동합니다.
// programmatic navigation (특정 버튼 클릭 시 페이지 이동)
document.querySelector(".about").addEventListener("click", async () => {
  try {
    showLoadingIndicator();
    const response = await fetch(url);
    const prefetchedData = await response.text();
 
    updatePageContent(prefetchedData);
 
    await navigation.navigate(url).finished;
  } catch (error) {
    console.error("Error:", error);
  } finally {
    hideLoadingIndicator();
  }
});
 
// intercept navigation (모든 네비게이션 이벤트를 가로채기)
navigation.addEventListener("navigate", (event) => {
  event.intercept({
    async handler() {
      try {
        showLoadingIndicator();
        const response = await fetch(event.destination.url);
        const prefetchedData = await response.text();
 
        updatePageContent(prefetchedData);
      } catch (error) {
        console.error("Error:", error);
      } finally {
        hideLoadingIndicator();
      }
    },
  });
});

위의 코드와 같이 네비게이션은 명시적으로 navigation.navigate() 메서드를 호출하여 프로그래밍적으로 페이지 이동을 처리할 수 있습니다. 네비게이션을 트리거 한 후 프로미스를 통해 네비게이션 상태를 추적할 수 있습니다. 또한 네비게이션 이벤트가 발생할 때 자동으로 가로채기 처리를 할 수 있습니다. event.intercept() 메서드를 통해 커스텀 로직(로딩 화면 표시, 데이터 프리페치 등)을 수행할 수 있습니다.

즉 History API를 통해 히스토리를 제어하고, Navigation API를 통해 네비게이션 과정을 좀 더 세밀하게 제어할 수 있습니다.

현재 Navigation API는 실험적인 기능으로, 브라우저 호환성 및 안정성에 주의해야 합니다.

Next.js

Next.js는 클라이언트 사이드 네비게이션을 보다 쉽게 구현할 수 있도록 Link 컴포넌트와 Router 객체를 제공합니다. 이를 통해 개발자는 저수준의 API를 직접 다루지 않고도 Link 컴포넌트를 사용하여 페이지 간 이동을 처리하거나, Router 객체로 프로그래밍 방식의 페이지 이동을 손쉽게 구현할 수 있습니다. 또한, 클라이언트 사이드 네비게이션의 한계점인 SEO 문제를 보완하기 위해 서버 사이드 렌더링(SSR)을 기본적으로 지원합니다.

Link

Next.js의 Link 컴포넌트는 기존 a 태그의 기능을 확장하여 클라이언트 사이드 네비게이션과 프리페칭 기능을 제공합니다.

  • 해당 라우트에 필요한 부분만을 반영하여, 페이지 전환 시 전체 페이지 새로 고침 없이 이동합니다.
  • Link 컴포넌트가 브라우저의 뷰포트에 노출될 경우 자동으로 프리페칭을 수행하여 사용자 경험을 향상시킵니다.
    • 기본적으로 prefetch={null} 상태이며, 라우트가 정적일 경우 자동으로 프리페칭을 수행합니다. 동적 라우트의 경우 loading.js 경계에 가장 가까운 라우트 세그먼트까지 부분적으로 프리페칭됩니다.
    • prefetch={true}로 설정하면 라우트에 관계없이 프리페칭됩니다.
  • 프로그래밍적 네비게이션과 달리 간단하게 페이지 간 이동을 처리할 수 있습니다.
import Link from "next/link";
 
export default function Page() {
  return (
    <Link href="/post" prefetch={true}>
      Dashboard
    </Link>
  );
}

Router

Next.js의 Router 객체는 프로그래밍적으로 페이지 이동을 처리할 수 있도록 해줍니다. 해당 방식을 사용하기 위해선 useRouter 훅을 사용하여 Router 인스턴스를 가져와야 합니다. 해당 인스턴스에서 제공되는 메서드를 사용하면 브라우저의 히스토리 스택을 조작하여, 클라이언트 사이드 네비게이션을 가능하게 해줍니다.

  • push(href, options): 새로운 URL로 이동하며, 브라우저 기록에 새 항목을 추가합니다. 전체 페이지 새로고침 없이 페이지 이동을 처리합니다.
  • replace(href, options): 현재 라우트를 새로운 URL로 교체합니다. 새 항목을 추가하지 않고 현재 항목을 대체합니다.
  • refresh(): 현재 라우트를 새로고침합니다. 서버에 새로운 요청을 보내고 서버 컴포넌트를 리렌더링합니다. 이때, 클라이언트 상태에는 영향을 주지 않습니다.
  • prefetch(href): 제공된 라우트를 프리페칭합니다.
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
 
export default function Page() {
  const router = useRouter();
 
  const navigateToDashboard = () => {
    router.push("/post");
  };
 
  const redirectToLogin = () => {
    router.replace("/login");
  };
 
  const refreshCurrentPage = () => {
    router.refresh();
  };
 
  useEffect(() => {
    router.prefetch("/dashboard");
  }, []);
 
  return (
    <div>
      <button onClick={navigateToDashboard}>Go to Dashboard</button>
      <button onClick={redirectToLogin}>Replace with Login</button>
      <button onClick={refreshCurrentPage}>Refresh</button>
    </div>
  );
}

결론

서버 사이드 네비게이션과 클라이언트 사이드 네비게이션을 정리하면 다음과 같습니다:

특징서버 사이드 네비게이션클라이언트 사이드 네비게이션
페이지 전환 속도느림 (전체 새로고침 필요)빠름 (필요한 부분만 업데이트)
SEO우수 (완전한 HTML 제공)제한적 (SSR 또는 SSG 필요)
초기 로딩 속도빠름느림 (모든 리소스 초기 로딩 필요)
상태 관리어렵다 (페이지 전환 시 상태 초기화)용이하다 (상태 유지 가능)
네트워크 부하높음 (전체 페이지 재요청)낮음 (부분 업데이트로 효율적)
사용자 경험 (UX)전환 시 깜빡임 및 지연 발생 가능부드러운 전환, 애니메이션 등으로 몰입감 제공

단순한 구현과 SEO를 고려할 때는 서버 사이드 네비게이션을 사용하고, 빠른 전환 속도와 UX를 중요시하는 경우 클라이언트 사이드 네비게이션을 사용하는 것이 좋다고 생각합니다. 혹은, 두 가지 모두의 장점을 취합한 Next.js와 같은 프레임워크를 사용하여 SSR과 클라이언트 사이드 네비게이션을 조합하여 사용할 수도 있습니다.


출처