NextJS 공식문서 파헤치기 (Linking and Navigating)

  • NextJS 에서 경로를 탐색하는 방법
    • <Link> 컴포넌트 사용
    • useRouter 훅 사용(클라이언트 컴포넌트)
    • redirect 함수 사용(서버 컴포넌트)
    • 네이티브 Histroy API 사용

1️⃣ <Link> 컴포넌트

  • <Link> 컴포넌트는 HTML <a> 태그를 확장하여 경로간 prefetching 과 클라이언트 측 탐색을 제공하는 내장 컴포넌트이다.
  • NextJS에서 경로를 탐색하는 기본적이고 권장되는 방법
  • next/link 에서 import 하여 사용할 수 있다.
  • 컴포넌트에 href prop을 전달하여 사용 가능
  •  
import Link from 'next/link'

export default function Page() {
  return <Link href="/dashboard">Dashboard</Link>
}

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>
  )
}

3️⃣ redirect 함수

  • 서버 컴포넌트의 경우 redirect 함수를 사용해야 한다.
  • redirect는 기본적으로 307 상태 코드를 반환한다. 서버 작업에서 사용되는 경우 303을 반환 이는 일반적으로 POST 요청의 결과로 성공 페이지로 리디렉션하는 데 사용된다.
  • redirect내부적으로 오류가 발생하므로 try/catch 블록 외부에서 호출해야 한다.
  • 렌더링 프로세스 중에 클라이언트 컴포넌트에서 호출할 수 있지만, 이벤트 핸들러에서는 호출할 수 없다. (useRouter 사용해야함)
  • 렌더링 프로세스 전에 리디렉션 하고싶다면 next.config.js 또는 Middleware를 사용해야 한다.
import { redirect } from 'next/navigation'

async function fetchTeam(id: string) {
  const res = await fetch('https://...')
  if (!res.ok) return undefined
  return res.json()
}

export default async function Profile({ params }: { params: { id: string } }) {
  const team = await fetchTeam(params.id)
  if (!team) {
    redirect('/login')
  }

  // ...
}

4️⃣ 네이티브 Histroy API

  • 현재 페이지의 URL을 새로운 URL로 변경을 위해 사용함
  • window.history.pushState, window.history.replaceState 메서드들은 페이지 리로딩 없이 브라우저 히스토리 스택을 업데이트한다.
  • pushState, replaceState 호출은 NextJS 라우터에 통합되어 usePathnameuseSearchParams를 동기화 한다.
// window.history.pushState(state,unused,url) => unused 에는 빈 문자열이 들어가야함
// 브라우저의 기록 스택에 새 항목을 추가하는 데 사용
// 사용자는 이전 상태로 돌아갈 수 있다.
'use client'

import { useSearchParams } from 'next/navigation'

export default function SortProducts() {
  const searchParams = useSearchParams()

  // 제품 목록 정렬
  function updateSorting(sortOrder: string) {
    const params = new URLSearchParams(searchParams.toString())
    params.set('sort', sortOrder)
    window.history.pushState(null, '', `?${params.toString()}`)
  }

  return (
    <>
      <button onClick={() => updateSorting('asc')}>Sort Ascending</button>
      <button onClick={() => updateSorting('desc')}>Sort Descending</button>
    </>
  )
}
// window.history.replaceState(state, unused, url)
// 브라우저의 기록 스택에서 현재 항목을 대체하는 데 사용
// 사용자는 이전 상태로 돌아갈 수 없다.
'use client'

import { usePathname } from 'next/navigation'

export function LocaleSwitcher() {
  const pathname = usePathname()

  // locale을 전환
  function switchLocale(locale: string) {
    // e.g. '/en/about' or '/fr/contact'
    const newPath = `/${locale}${pathname}`
    window.history.replaceState(null, '', newPath)
  }

  return (
    <>
      <button onClick={() => switchLocale('en')}>English</button>
      <button onClick={() => switchLocale('fr')}>French</button>
    </>
  )
}

5️⃣ 라우팅 및 탐색 작동 방식

  • App Roter는 라우팅과 탐색에 하이브리드 방식 사용
  • 서버에서 애플리케이션 코드는 경로 세그먼트별로 자동으로 코드 분할 된다.
  • Next.js는 경로 세그먼트를 prefetch 하고 캐시한다.
  • 따라서 사용자가 새 경로로 탐색할 때 브라우저는 페이지를 다시 로드하지 않고 변경된 경로 세그먼트만 다시 렌더링하여 탐색 경험과 성능을 개선한다.
  1. 📌 코드 분할
    • 코드를 더 작은 번들로 분할하여 브라우저에서 다운로드하고 실행할 수 있다.
    • 이렇게 하면 각 요청에 대해 전송되는 데이터 양과 실행 시간이 줄어들어 성능이 향상된다.
    • 서버 컴포넌트를 사용하면 애플리케이션 코드를 경로 세그먼트별로 자동으로 코드 분할할 수 있다. => 현재 경로에 필요한 코드만 탐색에 로드된다.
  2. 📌 prefetching
    • 사용자가 경로를 방문하기 전에 백그라운드에서 경로를 미리 로드하는 방법
    • Nextjs에서 경로를 prefetch 하는 방법
      • <Link> 컴포넌트: 브라우저의 Viewport 내에 있으면 Link의 경로에 해당하는 페이지를 백그라운드에서 미리 가져온다. (prefetch는 개발 환경이 아닌 프로덕션 환경에서만 가능하다)
      • router.prefetch(): useRouter 훅은 프로그래밍 방식으로 경로를 미리 가져오는 데 사용할 수 있다. 특정 사용자 상호작용 이후에 특정 경로를 미리 로드하고자 할 때 유용하다.
  3. 📌 캐싱
    • NextJS에는 Router Cache 라는 메모리 내 클라이언트 측 캐시가 있다.
    • 사용자가 앱을 탐색할 때 미리 가져온 경로 세그먼트와 방문한 경로의 React Server Component Payload가 캐시에 저장된다.
    • 탐색 시 서버에 새로운 요청을 하는 대신 캐시를 최대한 재사용하여 요청 수와 전송되는 데이터 수를 줄여 성능을 향상 시킨다.
  4. 📌 부분 렌더링
    • 탐색 중에 변경되는 경로 구간만 클라이언트에서 다시 렌더링되고, 공유되는 구간은 보존된다는 것을 의미한다.
    • 부분 렌더링이 없다면 각 탐색은 클라이언트에서 전체 페이지를 다시 렌더링하게 된다.
    • 변경되는 세그먼트만 렌더링하면 전송되는 데이터 양과 실행 시간이 줄어들어 성능이 향상된다.
    • ex) /blog/[slug]/page, /blog/first/, blog/second 의 경로가 있다고 했을때, /blog 의 하위 요소인 부분들은 blog layout 이라는 공유 layout이 유지된다.
  5. 📌 소프트 네비게이션
    • 브라우저는 페이지 간 이동시에 "하드 탐색"을 수행한다.
    • 하지만 NextJS 앱 라우터는 페이지 간에 "소프트 탐색"을 활성화하여 부분 렌더링할 수 있게 한다.
    • 따라서 탐색 중에 클ㄹ이언트 React 상태를 보존할 수 있다.
  6. 📌 뒤로 및 앞으로 탐색
    • 기본적으로 NextJS는 앞뒤 탐색을 위한 스크롤 위치를 유지하고 라우터 캐시에 있는 경로 세그먼트를 재사용한다.

📚 REFERENCE

https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating
https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState