Navigating

작성일: 2025. 10. 05최종 수정: 2025. 10. 05. 06시 15분

Next.js에서 페이지 간 이동하는 방법을 다룬다. Link 컴포넌트와 useRouter 훅을 사용하여 클라이언트 사이드 네비게이션을 구현한다.

HTML의 <a> 태그는 매번 서버에 새로운 페이지를 요청한다. 페이지 전체가 새로고침되며 JavaScript 상태가 초기화된다.

typescript
// 느린 방식 - 전체 페이지 새로고침
<a href="/about">소개</a>

Next.js의 Link 컴포넌트는 클라이언트 사이드 네비게이션을 제공한다. 페이지 전체를 새로고침하지 않고 필요한 부분만 업데이트한다.

typescript
// 빠른 방식 - 클라이언트 사이드 네비게이션
import Link from 'next/link';

<Link href="/about">소개</Link>

Link 컴포넌트를 사용하면 다음과 같은 장점이 있다.

  • 페이지 전환이 즉각적이다
  • JavaScript 상태가 유지된다
  • viewport에 보이는 링크를 미리 로드한다 (자세한 내용은 [[prefetching]] 참조)
  • 브라우저 히스토리가 정상적으로 동작한다

next/link에서 제공하는 Link 컴포넌트는 페이지 간 이동의 기본이다.

기본 사용법

typescript
import Link from 'next/link';

export default function Navigation() {
  return (
    <header>
      <Link href="/">index</Link>
      &nbsp;
      <Link href="/search">search</Link>
      &nbsp;
      <Link href="/book/1">book/1</Link>
    </header>
  );
}

href prop에 이동할 경로를 문자열로 전달한다.

동적 경로는 문자열 보간이나 객체 형태로 전달할 수 있다.

typescript
import Link from 'next/link';

export default function PostList() {
  const posts = [
    { id: 1, title: '첫 번째 포스트' },
    { id: 2, title: '두 번째 포스트' },
    { id: 3, title: '세 번째 포스트' },
  ];

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          {/* 문자열 보간 방식 */}
          <Link href={`/posts/${post.id}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  );
}

객체 형태로도 전달할 수 있다.

typescript
<Link
  href={{
    pathname: '/posts/[id]',
    query: { id: post.id },
  }}
>
  {post.title}
</Link>

Query String도 함께 전달할 수 있다.

typescript
// 문자열 방식
<Link href="/posts/1?comments=true">포스트 1</Link>

// 객체 방식
<Link
  href={{
    pathname: '/posts/[id]',
    query: { id: 1, comments: 'true' },
  }}
>
  포스트 1
</Link>

외부 링크

외부 링크는 일반 <a> 태그를 사용한다.

typescript
{/* 외부 링크는 a 태그 사용 */}
<a href="<https://github.com>" target="_blank" rel="noopener noreferrer">
  GitHub
</a>

{/* 내부 링크는 Link 컴포넌트 사용 */}
<Link href="/about">소개</Link>

useRouter 훅

useRouter 훅은 현재 라우터 정보에 접근하고 프로그래매틱하게 네비게이션을 제어할 수 있게 한다.

주의: next/router 사용useRouter는 next/router에서 import 해야 한다. next/navigation의 useRouter는 App Router 전용이다.

typescript
import { useRouter } from 'next/router';

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

  console.log(router.pathname);  // 현재 경로 패턴 (예: /posts/[id])
  console.log(router.asPath);    // 브라우저 주소창의 전체 경로 (예: /posts/123?sort=latest)
  console.log(router.query);     // 쿼리 파라미터 객체 (예: { id: '123', sort: 'latest' })

  return <div>페이지</div>;
}

router 주요 속성

  • pathname: 현재 페이지의 경로 패턴 (동적 세그먼트는 대괄호 형태)
  • asPath: 실제 브라우저에 표시되는 전체 경로
  • query: URL 파라미터와 Query String을 담은 객체
  • route: pathname과 동일

Query String 접근

router.query로 URL의 모든 파라미터에 접근할 수 있다.

typescript
// pages/search.tsx
import { useRouter } from 'next/router';

export default function Search() {
  const router = useRouter();
  const { q, sort } = router.query;

  return (
    <div>
      <h1>검색어: {q}</h1>
      <p>정렬: {sort}</p>
    </div>
  );
}

/search?q=nextjs&sort=latest에 접속하면 q"nextjs", sort"latest"가 된다.

동적 라우팅 파라미터와 Query String이 모두 router.query에 통합된다.

typescript
// pages/posts/[id].tsx
import { useRouter } from 'next/router';

export default function Post() {
  const router = useRouter();
  const { id, comments } = router.query;

  return (
    <div>
      <h1>포스트 ID: {id}</h1>
      {comments && <p>댓글 표시 모드</p>}
    </div>
  );
}

/posts/123?comments=true에 접속하면 id"123", comments"true"가 된다.

프로그래매틱 네비게이션

특정 조건이 충족되었을 때 코드로 페이지를 이동할 수 있다. router.push()router.replace()를 사용한다.

router.push()

router.push()는 새로운 URL을 브라우저 히스토리에 추가하고 이동한다. 뒤로가기 버튼으로 이전 페이지로 돌아갈 수 있다.

typescript
import { useRouter } from 'next/router';

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

  const handleLogin = async () => {
    // 로그인 로직...
    const success = true;

    if (success) {
      // 로그인 성공 후 대시보드로 이동
      router.push('/dashboard');
    }
  };

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

Query String이나 동적 세그먼트도 전달할 수 있다.

typescript
// 문자열 형태
router.push('/posts/123?sort=latest');

// 객체 형태
router.push({
  pathname: '/posts/[id]',
  query: { id: '123', sort: 'latest' },
});

router.replace()

router.replace()는 현재 히스토리를 대체한다. 뒤로가기를 눌렀을 때 이전 페이지로 돌아가지 않는다.

typescript
const handleRedirect = () => {
  // 히스토리에 추가하지 않고 대체
  router.replace('/new-page');
};

로그인 후 리다이렉트처럼 사용자가 뒤로가기로 돌아가지 못하게 하고 싶을 때 사용한다.

typescript
import { useRouter } from 'next/router';

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

  const handleLogin = async () => {
    // 로그인 로직...
    const success = true;

    if (success) {
      // 뒤로가기로 로그인 페이지로 돌아가지 않도록 replace 사용
      router.replace('/dashboard');
    }
  };

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

router.back()

브라우저 히스토리에서 뒤로 이동한다.

typescript
import { useRouter } from 'next/router';

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

  return (
    <div>
      <button onClick={() => router.back()}>뒤로 가기</button>
      <h1>포스트 상세</h1>
    </div>
  );
}

router.reload()

현재 페이지를 새로고침한다. 서버에서 다시 페이지를 가져온다.

typescript
const handleReload = () => {
  router.reload();
};

push vs replace 비교

typescript
import { useRouter } from 'next/router';

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

  return (
    <div>
      {/* push: 히스토리에 추가, 뒤로가기 가능 */}
      <button onClick={() => router.push('/page-a')}>
        Page A로 이동 (push)
      </button>

      {/* replace: 히스토리 대체, 뒤로가기로 돌아올 수 없음 */}
      <button onClick={() => router.replace('/page-b')}>
        Page B로 이동 (replace)
      </button>

      {/* back: 이전 페이지로 */}
      <button onClick={() => router.back()}>
        뒤로 가기
      </button>
    </div>
  );
}

참고 자료