Link 컴포넌트와 프로그래밍 방식의 페이지 이동 (useRouter)

현대적인 웹 애플리케이션에서 '페이지 이동'은 단순한 화면 전환 이상의 의미를 갖습니다. 사용자에게 끊김 없는 경험(Seamless Experience)을 제공하면서도, 브라우저의 성능을 최적화해야 하기 때문이죠.
Next.js는 이를 위해 두 가지 강력한 무기를 제공합니다. 바로 선언적인 Link 컴포넌트와 명령적인 useRouter 훅입니다. 상황에 맞는 최적의 도구를 선택하는 안목, 오늘 이 글을 통해 확실히 길러보겠습니다.
1. 왜 Next.js의 내비게이션은 특별할까?
전통적인 HTML의 <a> 태그는 페이지를 이동할 때마다 서버로부터 전체 리소스를 다시 받아옵니다. 화면이 깜빡이고 데이터 낭비가 발생하죠.
반면 Next.js는 Client-side Navigation을 수행합니다. 페이지 전체를 새로고침하는 대신, 변경이 필요한 자바스크립트 뭉치(Bundle)만 받아와서 화면을 갈아 끼웁니다.
핵심 개념: 프리페칭(Prefetching)
Next.js의 내비게이션이 유독 빠른 이유는 비유하자면 '미리 주문하기' 때문입니다.
- 비유: 식당에 들어가서 메뉴판을 보고 주문하면 음식이 나올 때까지 기다려야 하죠. 하지만 여러분이 메뉴판의 특정 항목을 쳐다보기만 해도 주방에서 미리 요리를 시작한다면?
- 원리: Link 컴포넌트가 사용자의 화면(Viewport)에 노출되면, Next.js는 해당 페이지의 데이터를 백그라운드에서 미리 로드합니다. 클릭하는 순간 이미 데이터가 준비되어 있어 즉시 이동이 가능해지는 원리입니다.
2. 실전 예제: 쇼핑몰 서비스 구현하기
이커머스 서비스의 **상품 리스트(선언적 이동)**와 결제 로직(명령적 이동) 시나리오를 통해 두 방식을 살펴보겠습니다.
CASE 1: 상품 상세 페이지로 이동 (Link 컴포넌트)
가장 일반적인 경우입니다. 검색 엔진 최적화(SEO)가 중요하고, 사용자가 직접 클릭하여 이동하는 정적인 링크에 사용합니다.
import Link from 'next/link';
export default function ProductList({ products }) {
return (
<ul className="grid grid-cols-3 gap-4">
{products.map((product) => (
<li key={product.id} className="border p-4">
<h3>{product.name}</h3>
{/* href 속성을 통해 이동할 경로 지정
prefetch={false}를 주면 미리 로딩 기능을 끌 수도 있습니다.
*/}
<Link
href={`/products/${product.id}`}
className="text-blue-500 hover:underline"
>
상세보기 보기
</Link>
</li>
))}
</ul>
);
}
CASE 2: 결제 완료 후 대시보드로 이동 (useRouter)
로그인 성공, 결제 완료, 폼 제출 등 특정 로직이 완료된 후 프로그래밍 방식으로 페이지를 넘겨야 할 때 사용합니다.
'use client'; // 클라이언트 컴포넌트에서만 사용 가능합니다.
import { useRouter } from 'next/navigation';
export default function CheckoutButton() {
const router = useRouter();
const handlePayment = async () => {
// 1. 결제 API 호출 로직 (가상)
const isSuccess = await processPayment();
if (isSuccess) {
// 2. 결제 성공 시 감사 페이지로 이동
// push는 뒤로가기가 가능하고, replace는 현재 기록을 덮어써서 뒤로가기를 막습니다.
router.push('/checkout/success');
} else {
alert('결제에 실패했습니다. 다시 시도해주세요.');
}
};
return (
<button
onClick={handlePayment}
className="bg-black text-white px-6 py-2"
>
결제하기
</button>
);
}
3. 트러블슈팅: 흔히 겪는 실수들
1) 'use client'를 잊지 마세요
useRouter는 리액트 훅입니다. 따라서 파일 최상단에 'use client' 지시어가 선언된 클라이언트 컴포넌트에서만 동작합니다. 서버 컴포넌트에서 페이지 이동이 필요하다면 redirect() 함수를 사용해야 합니다.
2) 중첩된 <a> 태그 오류
Link 컴포넌트 내부에 직접 <a> 태그를 넣으면 HTML 표준 위반으로 에러가 발생할 수 있습니다(Next.js 13 버전 이후부터는 Link 자체가 <a>를 렌더링하므로 불필요합니다). 스타일을 입히고 싶다면 Link 컴포넌트에 직접 className을 부여하세요.
4. 어떤 상황에 무엇을 써야 할까? (Trade-offs)
| 구분 | Link 컴포넌트 | useRouter 훅 |
| 주 사용처 | 네비게이션 바, 리스트 아이템 등 | 버튼 클릭 후 로직 처리, 이벤트 핸들러 |
| SEO | 매우 유리 (크롤러가 링크를 인식함) | 불리 (자바스크립트 실행 필요) |
| 성능 | 자동 프리페칭 지원으로 매우 빠름 | 호출 시점에 이동하므로 상대적으로 느림 |
| 복잡성 | 단순하고 직관적임 | 추가적인 로직 작성이 필요함 |
결론: 최선의 선택은?
Next.js 앱을 설계할 때의 황금률은 **"최대한 Link를 먼저 고려하고, 어쩔 수 없을 때 useRouter를 쓴다"**입니다.
사용자가 어디로 갈지 명확한 모든 곳(메뉴, 상품 링크 등)에는 Link를 사용하여 성능과 SEO 이점을 모두 챙기세요. 하지만 비즈니스 로직의 결과에 따라 이동 경로가 결정되어야 하는 순간에는 useRouter가 여러분의 가장 친절한 도구가 되어줄 것입니다.