Navigating
Next.js에서 페이지 간 이동하는 방법을 다룬다. Link 컴포넌트와 useRouter 훅을 사용하여 클라이언트 사이드 네비게이션을 구현한다.
a 태그 vs Link 컴포넌트
HTML의 <a> 태그는 매번 서버에 새로운 페이지를 요청한다. 페이지 전체가 새로고침되며 JavaScript 상태가 초기화된다.
// 느린 방식 - 전체 페이지 새로고침
<a href="/about">소개</a>
Next.js의 Link 컴포넌트는 클라이언트 사이드 네비게이션을 제공한다. 페이지 전체를 새로고침하지 않고 필요한 부분만 업데이트한다.
// 빠른 방식 - 클라이언트 사이드 네비게이션
import Link from 'next/link';
<Link href="/about">소개</Link>
Link 컴포넌트를 사용하면 다음과 같은 장점이 있다.
- 페이지 전환이 즉각적이다
- JavaScript 상태가 유지된다
- viewport에 보이는 링크를 미리 로드한다 (자세한 내용은 [[prefetching]] 참조)
- 브라우저 히스토리가 정상적으로 동작한다
Link 컴포넌트
next/link에서 제공하는 Link 컴포넌트는 페이지 간 이동의 기본이다.
기본 사용법
import Link from 'next/link';
export default function Navigation() {
return (
<header>
<Link href="/">index</Link>
<Link href="/search">search</Link>
<Link href="/book/1">book/1</Link>
</header>
);
}
href prop에 이동할 경로를 문자열로 전달한다.
동적 경로에 Link 사용
동적 경로는 문자열 보간이나 객체 형태로 전달할 수 있다.
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>
);
}
객체 형태로도 전달할 수 있다.
<Link
href={{
pathname: '/posts/[id]',
query: { id: post.id },
}}
>
{post.title}
</Link>
Query String도 함께 전달할 수 있다.
// 문자열 방식
<Link href="/posts/1?comments=true">포스트 1</Link>
// 객체 방식
<Link
href={{
pathname: '/posts/[id]',
query: { id: 1, comments: 'true' },
}}
>
포스트 1
</Link>
외부 링크
외부 링크는 일반 <a> 태그를 사용한다.
{/* 외부 링크는 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 전용이다.
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의 모든 파라미터에 접근할 수 있다.
// 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에 통합된다.
// 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을 브라우저 히스토리에 추가하고 이동한다. 뒤로가기 버튼으로 이전 페이지로 돌아갈 수 있다.
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이나 동적 세그먼트도 전달할 수 있다.
// 문자열 형태
router.push('/posts/123?sort=latest');
// 객체 형태
router.push({
pathname: '/posts/[id]',
query: { id: '123', sort: 'latest' },
});
router.replace()
router.replace()는 현재 히스토리를 대체한다. 뒤로가기를 눌렀을 때 이전 페이지로 돌아가지 않는다.
const handleRedirect = () => {
// 히스토리에 추가하지 않고 대체
router.replace('/new-page');
};
로그인 후 리다이렉트처럼 사용자가 뒤로가기로 돌아가지 못하게 하고 싶을 때 사용한다.
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()
브라우저 히스토리에서 뒤로 이동한다.
import { useRouter } from 'next/router';
export default function PostDetail() {
const router = useRouter();
return (
<div>
<button onClick={() => router.back()}>뒤로 가기</button>
<h1>포스트 상세</h1>
</div>
);
}
router.reload()
현재 페이지를 새로고침한다. 서버에서 다시 페이지를 가져온다.
const handleReload = () => {
router.reload();
};
push vs replace 비교
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>
);
}