App Router navigating

작성일: 2025. 10. 09최종 수정: 2025. 10. 11. 01시 39분

Next.js에서 페이지 간 이동은 <Link> 컴포넌트를 사용한다.

typescript
import Link from 'next/link'

export default function Navigation() {
  return (
    <nav>
      <Link href="/"></Link>
      <Link href="/about">소개</Link>
      <Link href="/blog/post-1">블로그</Link>
    </nav>
  )
}

<Link>는 HTML의 <a> 태그를 확장한 것으로, 클라이언트 사이드 네비게이션을 제공한다. 페이지 전체를 새로고침하지 않고 필요한 부분만 교체해서 빠른 이동이 가능하다.

RSC Payload와 JS Bundle

App Router에서는 Server Component와 Client Component가 분리되어 있어서, 페이지 이동 시 두 가지를 각각 불러온다.

  • RSC Payload: Server Component의 렌더링 결과. 서버에서 생성된 HTML과 데이터를 담고 있다.
  • JS Bundle: Client Component의 JavaScript 코드. 브라우저에서 실행될 인터랙티브한 부분이다.

Page Router에서는 JS Bundle만으로 모든 컴포넌트를 처리했지만, App Router에서는 서버와 클라이언트 컴포넌트가 분리되어 각각 필요한 것만 전송한다. 이를 통해 전송량을 줄이고 초기 로딩 속도를 개선한다.

Prefetching

Prefetching은 사용자가 링크를 클릭하기 전에 미리 해당 페이지의 데이터를 불러오는 기능이다. <Link> 컴포넌트는 기본적으로 prefetching을 자동으로 수행한다.

작동 방식

링크가 뷰포트에 나타나면 Next.js는 백그라운드에서 해당 페이지를 미리 불러온다. 사용자가 실제로 클릭했을 때는 이미 데이터가 준비되어 있어서 즉시 페이지가 전환된다.

typescript
// 기본: prefetching 활성화
<Link href="/dashboard">대시보드</Link>

// prefetching 비활성화
<Link href="/dashboard" prefetch={false}>대시보드</Link>

Static vs Dynamic 라우트

Prefetching 동작은 라우트가 정적인지 동적인지에 따라 다르다.

Static 라우트는 빌드 타임에 미리 생성된 페이지다. 데이터가 고정되어 있고 모든 사용자에게 동일한 내용을 보여준다.

typescript
// app/about/page.tsx - Static 라우트
export default function About() {
  return <div>소개 페이지</div>
}

Static 라우트는 RSC Payload와 JS Bundle을 모두 prefetch 한다. 페이지 전체를 미리 불러오기 때문에 이동이 즉각적이다.

Dynamic 라우트는 요청 시점에 렌더링되는 페이지다. 사용자마다 다른 데이터를 보여주거나 실시간 데이터를 표시한다.

typescript
// app/dashboard/page.tsx - Dynamic 라우트
export default async function Dashboard() {
  const user = await getCurrentUser() // 요청 시점에 실행
  return <div>환영합니다, {user.name}님</div>
}

Dynamic 라우트는 RSC Payload만 일부 prefetch 한다. 전체 페이지를 미리 렌더링할 수 없기 때문에 레이아웃이나 로딩 상태만 미리 준비한다.

개발 모드 vs 프로덕션 모드

Prefetching은 프로덕션 모드에서만 활성화된다.

  • 개발 모드 (npm run dev): Prefetching이 비활성화되어 있다. 링크를 클릭할 때만 RSC Payload와 JS Bundle을 불러온다.
  • 프로덕션 모드 (npm run build && npm start): Prefetching이 활성화되어 링크가 뷰포트에 나타나면 자동으로 데이터를 미리 불러온다.

개발 중에는 prefetching이 작동하지 않으므로, 실제 성능을 확인하려면 빌드 후 프로덕션 모드로 실행해야 한다.

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

<Link> 컴포넌트를 사용하면 클라이언트 사이드 네비게이션이 가능하다. 페이지 전체가 새로고침되지 않고, 변경된 부분만 교체된다.

typescript
export default function Page() {
  return (
    <div>
      <Header />  {/* 페이지 이동 시 유지됨 */}
      <Content /> {/* 이 부분만 교체됨 */}
      <Footer />  {/* 페이지 이동 시 유지됨 */}
    </div>
  )
}

Layout으로 감싸진 부분은 그대로 유지되고, 페이지 컴포넌트만 교체되므로 빠르고 부드러운 전환이 가능하다. 이는 SPA(Single Page Application)와 유사한 경험을 제공한다.

프로그래매틱 네비게이션

때로는 버튼 클릭이나 폼 제출 후에 프로그래밍으로 페이지를 이동해야 할 때가 있다. 이럴 때는 useRouter 훅을 사용한다.

typescript
'use client'

import { useRouter } from 'next/navigation'

export default function LoginButton() {
  const router = useRouter()

  const handleLogin = async () => {
    await login()
    router.push('/dashboard')
  }

  return <button onClick={handleLogin}>로그인</button>
}

App Router에서는 next/navigation에서 useRouter를 import 한다. Page Router의 next/router와는 다른 API다.

주요 메서드는 다음과 같다.

  • router.push(href): 새로운 페이지로 이동
  • router.replace(href): 히스토리를 남기지 않고 이동
  • router.back(): 이전 페이지로 이동
  • router.refresh(): 현재 페이지 새로고침

참고 자료