문서
App Router 사용하기
링크 및 경로 이동

링크 및 경로 이동

Next.js에서는 네 가지 방법으로 경로 간 이동을 할 수 있습니다:

이 페이지에서는 각 옵션을 사용하는 방법과 경로 이동의 작동 방식에 대해 자세히 알아보겠습니다.

<Link>는 HTML <a> 태그를 확장하여 프리페칭과 경로 간 클라이언트 사이드 경로 이동을 제공하는 내장 컴포넌트입니다. 이는 Next.js에서 경로 간 이동을 위한 주요하고 권장되는 방법입니다.

next/link에서 가져와서 컴포넌트에 href prop을 전달하여 사용할 수 있습니다:

app/page.tsx
import Link from "next/link";
 
export default function Page() {
  return <Link href="/dashboard">Dashboard</Link>;
}
app/page.js
import Link from "next/link";
 
export default function Page() {
  return <Link href="/dashboard">Dashboard</Link>;
}

<Link>에 전달할 수 있는 다른 선택적 prop들이 있습니다. 자세한 내용은 API 참조를 참조하세요.

예시

동적 세그먼트로 링크하기

동적 세그먼트로 링크할 때, 템플릿 리터럴과 보간을 사용하여 링크 목록을 생성할 수 있습니다. 예를 들어, 블로그 게시물 목록을 생성하려면:

app/blog/PostList.js
import Link from "next/link";
 
export default function PostList({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  );
}

활성 링크 확인하기

usePathname()을 사용하여 링크가 활성 상태인지 확인할 수 있습니다. 예를 들어, 활성 링크에 클래스를 추가하려면 현재 pathname이 링크의 href와 일치하는지 확인할 수 있습니다:

@/app/ui/nav-links.tsx
"use client";
 
import { usePathname } from "next/navigation";
import Link from "next/link";
import { FC, ReactNode } from "react";
 
export const Links: FC<{ children?: ReactNode }> = () => {
  const pathname = usePathname();
 
  return (
    <nav>
      <Link className={`link ${pathname === "/" ? "active" : ""}`} href="/">
        Home
      </Link>
 
      <Link
        className={`link ${pathname === "/about" ? "active" : ""}`}
        href="/about"
      >
        About
      </Link>
    </nav>
  );
};
@/app/ui/nav-links.tsx
"use client";
 
import { usePathname } from "next/navigation";
import Link from "next/link";
 
export function Links() {
  const pathname = usePathname();
 
  return (
    <nav>
      <Link className={`link ${pathname === "/" ? "active" : ""}`} href="/">
        Home
      </Link>
 
      <Link
        className={`link ${pathname === "/about" ? "active" : ""}`}
        href="/about"
      >
        About
      </Link>
    </nav>
  );
}

id로 스크롤하기

Next.js App Router의 기본 동작은 새 경로의 상단으로 스크롤하거나 뒤로 및 앞으로 경로 이동에 대해 스크롤 위치를 유지하는 것입니다.

경로 이동 시 특정 id로 스크롤하려면 URL에 # 해시 링크를 추가하거나 href prop에 해시 링크를 전달할 수 있습니다. 이는 <Link><a> 요소로 렌더링되기 때문에 가능합니다.

<Link href="/dashboard#settings">Settings</Link>
 
// 출력
<a href="/dashboard#settings">Settings</a>

알아두면 좋은 점:

  • 경로 이동 시 페이지가 뷰포트에 보이지 않으면 Next.js가 해당 페이지로 스크롤합니다.

스크롤 복원 비활성화하기

Next.js App Router의 기본 동작은 새 경로의 상단으로 스크롤하거나 뒤로 및 앞으로 경로 이동에 대해 스크롤 위치를 유지하는 것입니다. 이 동작을 비활성화하려면 <Link> 컴포넌트에 scroll={false}를 전달하거나 router.push() 또는 router.replace()scroll: false를 전달할 수 있습니다.

// next/link
<Link href="/dashboard" scroll={false}>
  Dashboard
</Link>
// useRouter
import { useRouter } from "next/navigation";
 
const router = useRouter();
 
router.push("/dashboard", { scroll: false });

useRouter() 훅(Hook)

useRouter 훅(hook)을 사용하면 클라이언트 컴포넌트에서 프로그래밍 방식으로 경로를 변경할 수 있습니다.

app/page.js
"use client";
 
import { useRouter } from "next/navigation";
 
export default function Page() {
  const router = useRouter();
 
  return (
    <button type="button" onClick={() => router.push("/dashboard")}>
      Dashboard
    </button>
  );
}

useRouter의 전체 메서드 목록은 API 참조를 참조하세요.

권장사항: useRouter를 사용해야 하는 특정 요구사항이 없다면 경로 간 경로 이동에 <Link> 컴포넌트를 사용하세요.

redirect 함수

서버 컴포넌트의 경우 대신 redirect 함수를 사용하세요.

app/team/[id]/page.tsx
import { redirect } from "next/navigation";
 
async function fetchTeam(id: string) {
  const res = await fetch("https://...");
  if (!res.ok) return undefined;
  return res.json();
}
 
export default async function Profile({ params }: { params: { id: string } }) {
  const team = await fetchTeam(params.id);
  if (!team) {
    redirect("/login");
  }
 
  // ...
}
app/team/[id]/page.js
import { redirect } from "next/navigation";
 
async function fetchTeam(id) {
  const res = await fetch("https://...");
  if (!res.ok) return undefined;
  return res.json();
}
 
export default async function Profile({ params }) {
  const team = await fetchTeam(params.id);
  if (!team) {
    redirect("/login");
  }
 
  // ...
}

알아두면 좋은 점:

  • redirect는 기본적으로 307(임시 리디렉션) 상태 코드를 반환합니다. 서버 액션에서 사용될 때는 303(See Other)을 반환하며, 이는 일반적으로 POST 요청의 결과로 성공 페이지로 리디렉션할 때 사용됩니다.
  • redirect는 내부적으로 오류를 던지므로 try/catch 블록 외부에서 호출해야 합니다.
  • redirect는 렌더링 프로세스 동안 클라이언트 컴포넌트에서 호출할 수 있지만 이벤트 핸들러에서는 호출할 수 없습니다. 대신 useRouter 훅(hook)을 사용할 수 있습니다.
  • redirect는 절대 URL도 허용하며 외부 링크로 리디렉션하는 데 사용할 수 있습니다.
  • 렌더링 프로세스 전에 리디렉션하려면 next.config.js 또는 미들웨어를 사용하세요.

자세한 정보는 redirect API 참조를 참조하세요.

네이티브 History API 사용하기

Next.js를 사용하면 네이티브 window.history.pushStatewindow.history.replaceState 메서드를 사용하여 페이지를 다시 로드하지 않고 브라우저의 히스토리 스택을 업데이트할 수 있습니다.

pushStatereplaceState 호출은 Next.js 라우터와 통합되어 usePathnameuseSearchParams와 동기화할 수 있습니다.

window.history.pushState

브라우저의 히스토리 스택에 새 항목을 추가하는 데 사용합니다. 사용자는 이전 상태로 돌아갈 수 있습니다. 예를 들어, 제품 목록을 정렬하는 경우:

"use client";
 
import { useSearchParams } from "next/navigation";
 
export default function SortProducts() {
  const searchParams = useSearchParams();
 
  function updateSorting(sortOrder: string) {
    const params = new URLSearchParams(searchParams.toString());
    params.set("sort", sortOrder);
    window.history.pushState(null, "", `?${params.toString()}`);
  }
 
  return (
    <>
      <button onClick={() => updateSorting("asc")}>Sort Ascending</button>
      <button onClick={() => updateSorting("desc")}>Sort Descending</button>
    </>
  );
}

window.history.replaceState

브라우저의 히스토리 스택의 현재 항목을 교체하는 데 사용합니다. 사용자는 이전 상태로 돌아갈 수 없습니다. 예를 들어, 애플리케이션의 로케일을 전환하는 경우:

"use client";
 
import { usePathname } from "next/navigation";
 
export function LocaleSwitcher() {
  const pathname = usePathname();
 
  function switchLocale(locale: string) {
    // 예: '/en/about' 또는 '/fr/contact'
    const newPath = `/${locale}${pathname}`;
    window.history.replaceState(null, "", newPath);
  }
 
  return (
    <>
      <button onClick={() => switchLocale("en")}>English</button>
      <button onClick={() => switchLocale("fr")}>French</button>
    </>
  );
}

라우팅 및 경로 이동의 작동 방식

App Router는 라우팅 및 경로 이동에 하이브리드 접근 방식을 사용합니다. 서버에서는 애플리케이션 코드가 자동으로 경로 세그먼트별로 코드 분할됩니다. 그리고 클라이언트에서는 Next.js가 경로 세그먼트를 프리페치하고 캐시합니다. 이는 사용자가 새 경로로 이동할 때 브라우저가 페이지를 다시 로드하지 않고 변경된 경로 세그먼트만 다시 렌더링된다는 것을 의미합니다 - 이는 경로 이동 경험과 성능을 향상시킵니다.

1. 코드 분할

코드 분할을 통해 애플리케이션 코드를 더 작은 번들로 분할하여 브라우저에서 다운로드하고 실행할 수 있습니다. 이는 각 요청에 대한 데이터 전송량과 실행 시간을 줄여 성능을 향상시킵니다.

서버 컴포넌트를 사용하면 애플리케이션 코드가 경로 세그먼트별로 자동으로 코드 분할됩니다. 이는 경로 이동 시 현재 경로에 필요한 코드만 로드된다는 것을 의미합니다.

2. 프리페칭 (Prefetching)

프리페칭은 사용자가 방문하기 전에 백그라운드에서 경로를 미리 로드하는 방법입니다.

Next.js에서 경로를 미리 불러오는 두 가지 방법이 있습니다:

  • <Link> 컴포넌트: 경로는 사용자의 뷰포트에 보이게 되면 자동으로 미리 불러와집니다. 프리페칭은 페이지가 처음 로드되거나 스크롤을 통해 뷰로 들어올 때 발생합니다.
  • router.prefetch(): useRouter 훅을 사용하여 프로그래밍 방식으로 경로를 불러올 수 있습니다.

<Link>의 기본 프리페칭 동작(즉, prefetch prop이 지정되지 않거나 null로 설정된 경우)은 loading.js 사용 여부에 따라 다릅니다. 공유 레이아웃에서 첫 번째 loading.js 파일까지의 렌더링된 "트리"만 불러와지고 30초 동안 캐시됩니다. 이는 전체 동적 경로를 가져오는 비용을 줄이고, 사용자에게 더 나은 시각적 피드백을 제공하기 위해 즉각적인 로딩 상태를 표시할 수 있다는 것을 의미합니다.

prefetch prop을 false로 설정하여 프리페칭을 비활성화할 수 있습니다. 또는 prefetch prop을 true로 설정하여 로딩 경계를 넘어 전체 페이지 데이터를 프리페치(prefetch)할 수 있습니다.

자세한 내용은 <Link> API 참조를 참조하세요.

알아두면 좋은 점:

  • 프리페칭(prefetching)은 개발 환경에서는 활성화되지 않고 프로덕션 환경에서만 활성화됩니다.

3. 캐싱

Next.js에는 라우터 캐시라고 하는 인메모리 클라이언트 사이드 캐시가 있습니다. 사용자가 앱을 탐색할 때 프리페치(prefetch)된 경로 세그먼트와 방문한 경로의 React 서버 컴포넌트 페이로드가 캐시에 저장됩니다.

이는 경로 이동 시 서버에 새 요청을 하는 대신 가능한 한 캐시가 재사용된다는 것을 의미합니다 - 요청 수와 전송되는 데이터를 줄여 성능을 향상시킵니다.

라우터 캐시의 작동 방식과 구성 방법에 대해 자세히 알아보세요.

4. 부분 렌더링

부분 렌더링은 경로 이동 시 변경된 경로 세그먼트만 클라이언트에서 다시 렌더링되고 공유 세그먼트는 보존된다는 것을 의미합니다.

예를 들어, /dashboard/settings/dashboard/analytics 두 개의 형제 경로 사이를 이동할 때 settingsanalytics 페이지가 렌더링되고 공유된 dashboard 레이아웃은 보존됩니다.

부분 렌더링의 작동 방식

부분 렌더링 없이는 각 경로 이동이 클라이언트에서 전체 페이지를 다시 렌더링하게 됩니다. 변경된 세그먼트만 렌더링하면 전송되는 데이터 양과 실행 시간이 줄어들어 성능이 향상됩니다.

5. 소프트 경로 이동

브라우저는 페이지 간 이동 시 "하드 경로 이동"을 수행합니다. Next.js App Router는 페이지 간 "소프트 경로 이동"을 가능하게 하여 변경된 경로 세그먼트만 다시 렌더링되도록 합니다(부분 렌더링). 이를 통해 경로 이동 중에 클라이언트 React 상태가 보존될 수 있습니다.

6. 뒤로 및 앞으로 경로 이동

기본적으로 Next.js는 뒤로 및 앞으로 경로 이동에 대해 스크롤 위치를 유지하고 라우터 캐시의 경로 세그먼트를 재사용합니다.

7. pages/app/ 간의 라우팅

pages/에서 app/으로 점진적으로 마이그레이션할 때 Next.js 라우터는 두 디렉토리 간의 하드 경로 이동을 자동으로 처리합니다. pages/에서 app/으로의 전환을 감지하기 위해 앱 경로를 확률적으로 검사하는 클라이언트 라우터 필터가 사용되는데, 이는 때때로 잘못된 감지를 초래할 수 있습니다. 기본적으로 이러한 경우는 매우 드물며, 잘못된 감지 가능성은 0.01%로 설정되어 있습니다. 이 가능성은 next.config.jsexperimental.clientRouterFilterAllowedRate 옵션을 통해 조정할 수 있습니다. 다만, 잘못된 감지 비율을 낮추면 클라이언트 번들에서 생성되는 필터의 크기가 증가할 수 있다는 점에 유의해야 합니다.

또는, 이 처리를 완전히 비활성화하고 pages/app/ 간의 라우팅을 수동으로 관리하려면 next.config.js에서 experimental.clientRouterFilter를 false로 설정할 수 있습니다. 이 기능이 비활성화되면 앱 경로와 겹치는 페이지의 동적 경로는 기본적으로 정상적으로 이동되지 않을 수 있습니다.