본문 바로가기
웹 프로그래밍/Next.js

[Next.js 공식문서 정리] Routing : 02. Linking and Navigating

by 청량리 물냉면 2025. 3. 29.
반응형

2. Linking 및 Navigating

  • Link 컴포넌트 사용
  • useRouter hook 사용(클라이언트 사이드)
  • redirect 사용(서버 사이드)
  • History API 사용

1️⃣ Link 컴포넌트

  • prefetching (사용자가 경로를 방문하기 전 백그라운드에서 경로를 미리 로드하는 방식) 및 라우트 간 client side 탐색을 제공하는 HTML a태그의 확장 버전
  • Next.js에서 라우트 간 탐색을 진행하는 가장 기본적이고 권장되는 방식
  • API
    • href (필수, type: String or Object)
    • scroll (기본 true, 브라우저가 뒤 및 앞으로 탐색을 처리할 때 스크롤 위치를 유지, type: Boolean)
    • replace (기본값 false, 브라우저의 history stack에 새 url을 추가하는 대신 현재 기록 state를 변경, type: Boolean)
    • prefetch (기본 null, <Link/> 구성 요소가 사용자의 뷰포트에 들어갈 때 발생. href로 연결된 데이터를 백그라운드에서 미리 가져오고 로드해서 클라이언트 측 탐색 성능을 향상시킨다. 프로덕션에서만 사용 가능한 기능, type: Boolean or null)

2️⃣ useRouter() 훅

클라이언트 구성요소 useRouter에서 경로를 프로그래밍 방식으로 변경 가능

"use client";

import { useRouter } from "next/navigation";

export default function Page() {
  const router = useRouter();

  return (
    <button type="button" onClick={() => router.push("/dashboard")}>
      Dashboard
    </button>
  );
}

useRouter()를 꼭 사용할 이유가 없다면, 라우트 간 탐색 진행 시 Link를 사용하는 것이 권장됨

API

  • router.push(href: string, { scroll: boolean }): 제공된 경로로 클라이언트 측 탐색 진행, 브라우저 히스토리 스택에 새 경로가 쌓인다.
  • router.replace(href: string, { scroll: boolean }): 브라우저 히스토리 스택에 새 경로가 쌓이지 않고, 제공된 경로로 클라이언트 측 탐색이 진행된다
  • router.refresh(): 현재 라우트를 새로고침한다.
    즉, 서버에 새로운 요청을 보내고 데이터를 다시 가져와서 서버 컴포넌트를 다시 렌더링합니다. 이 과정에서 클라이언트 측의 React 상태(useState 등)나 브라우저 상태(예: 스크롤 위치) 는 유지된다.
    즉, 서버에서 새로운 데이터를 받아 업데이트하지만, 영향을 받지 않는 클라이언트 측 상태는 그대로 유지된다.
  • router.prefetch(href: string): 제공된 경로를 미리 설정하여 클라이언트 측 전환을 더 빠르게 한다.
  • router.back(): 브라우저 히스토리 스택에서 이전 경로로 다시 이동
  • router.forward(): 브라우저 히스토리 스택에서 다음 경로로 이동

❗ 알아두면 좋을 내용

♋ Next.js에서 router.push, <Link> 컴포넌트, 그리고 refresh()와 관련된 보안 및 동작 방식에 대한 설명

1️⃣ router.push / router.replace의 보안 문제

router.push 또는 router.replace에 검증되지 않은 URL을 직접 넣으면 XSS(크로스 사이트 스크립팅) 공격에 취약할 수 있다.

const userInput = "javascript:alert('XSS')";
router.push(userInput); // ❌ 보안 취약점 발생 가능

예를 들어, 위 코드와 같이 사용자 입력을 그대로 사용하면, router.push(userInput)이 실행되고 javascript: URL 스킴이 실행될 수도 있고 이로 인해 악성 코드가 동작하게 될 수 있다.

✅ 해결 방법
사용자 입력을 그대로 사용하지 말고, 신뢰할 수 있는 경로인지 검증하거나 encodeURIComponent() 같은 방법을 사용한다.

2️⃣ <Link>의 자동 프리페치
<Link> 컴포넌트는 화면에 보이면 해당 경로를 자동으로 미리 가져온다(prefetch).
즉, 사용자가 해당 링크를 클릭하기 전에 미리 데이터를 가져와서 페이지 전환 속도를 높이는 역할을 한다.
예를 들어

<Link href="/dashboard">Go to Dashboard</Link>

위 코드에서 <Link>가 화면에 나타나면 Next.js가 자동으로 /dashboard 페이지 데이터를 미리 가져오게 된다.

3️⃣ refresh()의 동작 방식
refresh()를 사용하면 서버에서 데이터를 다시 가져오지만, 캐싱된 fetch 요청 데이터가 있으면 같은 응답이 나올 수도 있다.
또한 쿠키나 헤더 같은 동적 API 값이 변하면 응답이 달라질 수도 있다.

import { refresh } from "next/navigation";

async function handleRefresh() {
  refresh(); // 서버 데이터를 다시 불러옴
}

✅ 해결 방법

  • cache: "no-store" 옵션을 사용하여 항상 최신 데이터를 가져온다.
  • revalidatePath() 같은 API를 사용하여 특정 페이지만 다시 갱신한다.

요약

  1. router.push와 router.replace에 검증되지 않은 URL을 직접 넣지 마라 (XSS 공격 위험).
  2. <Link> 컴포넌트는 화면에 나타나면 자동으로 미리 데이터를 가져온다 (퍼포먼스 최적화).
  3. refresh()는 캐시된 데이터를 그대로 반환할 수도 있으며, 쿠키나 헤더 값이 변하면 응답도 달라질 수 있다.

3️⃣ next/navigation에서 가져오기 (redirect 함수)

redirect 함수

  • 서버 컴포넌트에서 사용 가능
  • 리다이렉트는 서버 액션, 서버 컴포넌트, 라우트 핸들러에서 사용된다
  • redirect(path, type)
  • type : Server Action에서 동작x (기본 push), Server Action 제외 다른 곳에선 기본이 replace

❗ 알아두면 좋을 내용

  • redirect는 기본적으로 307 (Temporary Redirect) 상태 코드를 반환한다.하지만 Server Action에서 사용될 경우, 일반적으로 POST 요청의 성공 페이지로 리디렉션할 때 사용되는 303 (See Other) 상태 코드를 반환한다.
  • redirect는 내부적으로 오류를 발생시키므로 try/catch 블록 밖에서 호출해야 한다.
  • Client Component에서 렌더링 과정 중에는 호출할 수 있지만, 이벤트 핸들러에서는 사용할 수 없다 → 이벤트 핸들러에서는 대신 useRouter 훅을 사용할 수 있다.
  • 절대 URL을 포함한 외부 링크로도 리디렉션이 가능하다.
  • 렌더링 전에 리디렉션하려면 next.config.js 또는 Middleware를 사용해야 한다.

4️⃣ History API

Next.js에서는 페이지를 새로고침하지 않고 브라우저의 기록 스택을 업데이트하기 위해 기본적인 window.history.pushStatewindow.history.replaceState 메서드를 사용할 수 있다.
pushStatereplaceState 호출은 Next.js 라우터와 통합되어 usePathnameuseSearchParams와 동기화 가능하다.

  • window.history.pushState: 브라우저 히스토리스택에 새 엔트리 추가. 뒤로 가기 가능
  • window.history.replaceState: 최근 엔트리를 새 엔트리로 교체, 뒤로 가기 불가능
    • window.history.replaceState(null, '', newPath) → 이렇게 씀

라우팅 및 네비게이션 동작 방식

앱라우터에서는 라우팅 및 네비게이션을 위해 하이브리드 접근 방식 이용

  • 서버에서 애플리케이션 코드는 경로 세그먼트별로 자동 코드 분할됨 / 클라이언트에서는 경로 세그먼트를 prefetch 및 캐시함
  • 즉, 사용자가 새 경로로 이동할 때 브라우저는 페이지를 다시 로드하지 않고 경로 세그먼트만 리렌더링하여 탐색 경험과 성능을 향상시킨다.

✳ 1. Code Splitting

  • 애플리케이션코드를 더 작은 번들로 분할하여 브라우저에서 다운 및 실행 가능 → 요청에 대한 데이터 전송량 및 실행시간 감소
  • 서버 컴포넌트에서는, 애플리케이션 코드를 경로 세그먼트별로 자동 코드분할 가능 → 현재 경로에 필요한 코드만 네비게이션에 로드됨

✳ 2. Prefetching

  • 사용자가 경로방문 전 백그라운드에서 미리 경로를 로드하는 것.
  • <Link> 컴포넌트, router.prefetch()를 이용해 경로를 미리 fetch 할 수 있다.

✳ 3. Caching

  • Router Cache: 인메모리 클라이언트측 캐시 (in-memory client-side cache)
  • 사용자가 앱을 탐색할 때, 프리패치된 route segments와 방문한 경로의 react server component payload가 캐시에 저장된다 → 탐색 시 서버에 새로운 요청을 하는 대신, 캐시를 최대한 재사용하여 요청 수와 전송되는 데이터 수를 줄여 성능을 향상시킨다.

✳ 4. Partial Rendering

탐색 중에 변경되는 route segments만 클라이언트에서 리렌더링되고, 공유 세그먼트(shared segments)는 보존된다.
예를 들어서 dashboard/settingsdashboard/analytics 둘을 탐색할 때, settings 페이지는 언마운트되고, analytics 페이지는 새로운 상태로 마운트된다. 그리고 dashboard 레이아웃은 그대로 유지됨.

이러한 동작은 같은 동적 세그먼트, 예를 들어 /blog/[slug]/page에서 두 경로 사이에도 존재한다. (즉, /blog/first에서 /blog/second로 이동할 때)

부분 렌더링이 없다면 각각의 navigation은 전체 페이지를 리렌더링해야 된다. 변경되는 부분만 렌더링하면, 전송되는 데이터 양과 실행 시간이 줄어들어 성능이 향상된다.

✳ 5. soft navigation
브라우저는 페이지 간 이동 시 "강제 이동(hard navigation)"을 수행한다. Next.js 앱 라우터는 페이지 간에 "부드러운 이동(soft navigation)"을 가능하게 하여 변경된 경로 세그먼트만 다시 렌더링(부분 렌더링)하도록 한다. 이를 통해 클라이언트 React 상태가 탐색 중에도 유지된다.

✳ 6. Back and Forward Navigation

페이지를 앞뒤로 탐색할 수 있도록, 스크롤 위치를 유지하고 라우터 캐시에서 경로 세그먼트를 재사용한다.

✳ 7. pages/app/사이의 라우팅

1️⃣ pages/app/ 이동 시 라우팅 동작

Next.js 14에서는 pages/app/을 동시에 사용 가능하다.
즉, 기존 pages/ 기반 라우팅을 유지하면서 일부 페이지를 app/으로 점진적으로 전환할 수 있다.
이 과정에서 Next.js는 클라이언트 라우터 필터(client router filter)를 활용하여 어떤 요청이 app/에 속하는지 판단한다.

2️⃣ 클라이언트 라우터 필터 (Client Router Filter)

Next.js는 성능 최적화를 위해 모든 app/ 경로를 클라이언트로 전달하는 대신, 확률적(probabilistic) 방식을 사용하여 app/ 페이지 여부를 판단한다.
이러한 방식은 메모리를 덜 사용하지만, 확률적으로 잘못된 판단(false positive)이 발생할 가능성이 있다.

  • False Positive (오탐): pages/에 속한 경로인데, Next.js가 app/에 속한 것으로 잘못 판단하는 경우
  • 기본적으로 0.01% 확률로 발생하도록 설정되어 있음
  • 확률을 더 낮추면(= 더 정확하게 만들면), 필터 크기가 커져 클라이언트 번들 크기가 증가

3️⃣ next.config.js에서 오탐률 조정

확률을 조정할 수 있는 옵션: experimental.clientRouterFilterAllowedRate

// next.config.js
module.exports = {
  experimental: {
    clientRouterFilterAllowedRate: 0.001, // 오탐 확률을 0.001%로 낮춤
  },
};
  • 값을 낮추면(false positive 확률 ↓):
    → 더 정확하게 app/ 페이지를 판별하지만, 클라이언트 번들 크기가 증가
  • 값을 높이면(false positive 확률 ↑):
    → 클라이언트 번들은 가벼워지지만, app/ 경로 판단 오류가 더 자주 발생

4️⃣ ⚠️주의할 점

  • 이 기능은 실험적(experimental) 옵션이므로, 향후 변경될 가능성이 있음.
  • 무조건 낮추는 것이 좋은 것이 아님 → 번들 크기 증가와의 균형을 고려해야 함.
  • app/으로 완전히 마이그레이션하면 이 옵션이 필요 없어짐.

 


출처

https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating

 

Routing: Linking and Navigating | Next.js

Learn how navigation works in Next.js, and how to use the Link Component and `useRouter` hook.

nextjs.org

 

반응형