본문 바로가기
C.W.K.
Stream
Lesson 10 of 10 · published

앱 안에서 자연스럽게 이동하기

~20 min · Link, useRouter, redirect, prefetch

Level 0호기심
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

Link는 단순한 링크보다 더 많은 일을 해

<Link>는 주소를 바꾸는 데서 끝나지 않아. 클라이언트 전환을 사용하고, 목적 라우트를 미리 가져오며, 공유 레이아웃의 상태를 보존해. 앱 내부 이동에는 기본적으로 Link를 써. 평범한 <a>를 쓰면 문서 전체를 다시 읽으면서 지금까지 유지하던 상태도 사라져.

코드로 이동할 때는 실행 위치를 구분해

클라이언트 컴포넌트에서는 next/navigationuseRouter()를 사용해 push, replace, refresh를 호출할 수 있어. 서버 컴포넌트나 서버 액션에서는 hook을 쓸 수 없으므로 redirect()로 응답 흐름을 끝내.

도구돌려주는 값사용 위치
useRouter()push, replace, refresh 같은 메서드클라이언트
usePathname()현재 pathname 문자열클라이언트
useSearchParams()URLSearchParams클라이언트
useSelectedLayoutSegment()현재 레이아웃의 활성 자식 구간클라이언트

버튼보다 링크가 맞는 이동도 있어

사용자가 새 탭으로 열거나 주소를 복사할 수 있어야 한다면 이동의 의미는 링크야. 클릭 처리기에서 무조건 router.push()를 호출하면 브라우저의 기본 기능과 접근성을 잃기 쉬워. 명령의 결과로 이동해야 할 때만 프로그래밍 방식의 탐색을 골라.

이동 요소가 브라우저의 링크 의미를 가져야 하는지 먼저 판단해. 새 탭·복사·검색 노출이 필요하면 Link, 제출 성공처럼 명령의 결과라면 redirect()나 router 메서드가 맞아. 앱 안의 모든 <a>를 기계적으로 바꾸기 전에 외부 URL과 다운로드 링크는 별도 규칙으로 남겨. prefetch가 빠르다고 모든 링크를 미리 읽으면 데이터와 네트워크 예산을 낭비할 수 있어. 긴 목록이나 민감한 동적 라우트에서는 실제 hover·viewport 조건과 캐시 정책을 확인해. 내비게이션 성능은 클릭 뒤 속도뿐 아니라 클릭하지 않은 경로에 쓴 비용까지 포함해.

내부 Link, 일반 <a>, router.push, 서버 redirect를 같은 목적지에 적용해 network와 layout mount를 비교해. 새 탭·주소 복사·뒤로 가기가 필요한 요소가 버튼으로 구현되지 않았는지 접근성 트리도 확인하고, 긴 목록의 prefetch 요청 수를 측정해.

Code

Link와 미리 가져오기·tsx
import Link from 'next/link';

export function Nav() {
  return (
    <nav className="flex gap-3">
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
      <Link href="/blog/hello-world">Blog</Link>
      <Link href="/dashboard" prefetch={false}>
        Dashboard (no prefetch)
      </Link>
    </nav>
  );
}
코드에서 경로 이동하기·tsx
'use client';
import { useRouter } from 'next/navigation';
import { useTransition } from 'react';

export function LoginForm() {
  const router = useRouter();
  const [pending, start] = useTransition();

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        start(async () => {
          const ok = await login();
          if (ok) router.push('/dashboard');
        });
      }}
    >
      <button disabled={pending}>{pending ? 'Signing in&hellip;' : 'Sign in'}</button>
    </form>
  );
}
클라이언트 JavaScript 없는 서버 리디렉션·tsx
import { redirect } from 'next/navigation';

export default async function Page() {
  const session = await getSession();
  if (!session) redirect('/login');
  return <Dashboard session={session} />;
}

External links

Exercise

앱 내부의 일반 <a href>Link로 바꾸고, 인증이 필요한 경로에는 서버 redirect, 폼 성공 뒤에는 필요한 클라이언트 이동을 적용해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.