• Blog

  • Snippets

Linking and Navigating

May 31, 2024

NEXT JS

App Route

Routing

NEXT JS 공식문서를 참고하여 번역함.

경로 이동을 하는 데 4가지 방법이 있습니다:


이 페이지에서는 각 옵션을 사용하는 방법과 탐색 기능의 작동 방식을 자세히 설명합니다.

# <Link> Component

<Link>는 HTML의 <a> 태그를 확장하여 라우트 간에 미리 가져오기(prefetching)와 클라이언트 측 탐색 기능을 제공하는 내장된 구성 요소입니다. 이는 Next.js에서 라우트 간 탐색을 위한 주요하고 권장되는 방법입니다.

이를 사용하려면 next/link에서 가져와서 해당 컴포넌트에 href 속성을 전달하면 됩니다:

app/page.tsx

import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}

<Link>에 전달할 수 있는 다른 선택적 속성들도 있습니다. 자세한 내용은 API reference를 확인하세요.

Examples

Linking to Dynamic Segments

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

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>
)
}

Checking Active Links

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

app/components/links.tsx

'use client'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
export function Links() {
const pathname = usePathname()
return (
<nav>
<ul>
<li>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
Home
</Link>
</li>
<li>
<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</li>
</ul>
</nav>
)
}

id로 스크롤 하기

Next.js 앱 라우터의 기본 동작은 새로운 라우트로 이동할 때 맨 위로 스크롤하거나, 뒤로 및 앞으로 탐색할 때 스크롤 위치를 유지하는 것입니다.

탐색할 때 특정 id로 스크롤하려면 URL 끝에 # 해시 링크를 추가하거나 href 속성에 해시 링크를 전달하면 됩니다. 이는 <Link><a> 요소로 렌더링되기 때문에 가능합니다.

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

알아두면 좋은 정보:
- 탐색 시 페이지가 뷰포트에 보이지 않으면 Next.js가 해당 페이지로 스크롤합니다.

스크롤 복구 비활성화

Next.js 앱 라우터의 기본 동작은 새로운 라우트로 이동할 때 맨 위로 스크롤하거나, 뒤로 및 앞으로 탐색할 때 스크롤 위치를 유지하는 것입니다. 이 동작을 비활성화하려면 <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 reference를 확인하세요.

권장 사항: useRouter를 사용해야 하는 특정 요구 사항이 없는 한, 라우트 간 탐색에는 <Link> 컴포넌트를 사용하세요.

# redirect function

서버 컴포넌트의 경우, 대신 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')
}
// ...
}

알아두면 좋은 정보:
- redirect 함수는 기본적으로 307 (임시 리디렉션) 상태 코드를 반환합니다. 서버 액션에서 사용될 때는 303 (다른 위치 보기) 상태 코드를 반환하며, 이는 POST 요청의 결과로 성공 페이지로 리디렉션할 때 흔히 사용됩니다.
- redirect 함수는 내부적으로 오류를 발생시키므로 try/catch 블록 외부에서 호출해야 합니다.
- redirect 함수는 클라이언트 컴포넌트의 렌더링 과정에서 호출할 수 있지만, 이벤트 핸들러에서는 사용할 수 없습니다. 대신 useRouter hook을 사용할 수 있습니다.
- redirect는 absolute URL도 허용하며 외부 링크로 리디렉션하는 데 사용할 수 있습니다.
- 렌더링 과정 전에 리디렉션을 수행하려면, next.config.js 또는 Middleware를 사용하세요.

# Using the native History API

Next.js는 페이지를 새로 고치지 않고도 브라우저의 히스토리 스택을 업데이트하기 위해 native 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) {
// e.g. '/en/about' or '/fr/contact'
const newPath = `/${locale}${pathname}`
window.history.replaceState(null, '', newPath)
}
return (
<>
<button onClick={() => switchLocale('en')}>English</button>
<button onClick={() => switchLocale('fr')}>French</button>
</>
)
}

# 라우팅 및 탐색 작동 방식

앱 라우터는 라우팅과 내비게이션에 하이브리드 접근 방식을 사용합니다. 서버에서는 애플리케이션 코드가 경로 세그먼트에 따라 자동으로 코드 분할됩니다. 클라이언트에서는 Next.js가 경로 세그먼트를 prefetches하고 caches합니다. 이는 사용자가 새로운 경로로 이동할 때 브라우저가 페이지를 새로 고치지 않으며, 변경되는 경로 세그먼트만 다시 렌더링되어 내비게이션 경험과 성능이 향상됨을 의미합니다.

1. Code Splitting

코드 분할은 애플리케이션 코드를 더 작은 번들로 나누어 브라우저가 다운로드하고 실행할 수 있게 합니다. 이렇게 하면 각 요청에 대한 전송 데이터 양과 실행 시간이 줄어들어 성능이 향상됩니다.

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

2. Prefetching

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

Next.js에서 경로를 프리패칭하는 두 가지 방법이 있습니다:


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

prefetch 속성을 false로 설정하여 프리패칭을 비활성화할 수 있습니다. 또는 prefetch 속성을 true로 설정하여 loading boundaries를 넘어 전체 페이지 데이터를 프리패칭할 수 있습니다.

자세한 내용은 <Link> API reference를 확인하세요.

알아두면 좋은 정보:
- 프리패칭은 개발 환경에서는 활성화되지 않고, 프로덕션 환경에서만 활성화됩니다.

3. Caching

Next.js에는 Router Cache라고 불리는 메모리 내 클라이언트 측 캐시가 있습니다. 사용자가 앱 내를 탐색할 때, 프리패칭된 경로 세그먼트와 방문한 경로의 React 서버 컴포넌트 페이로드가 캐시에 저장됩니다.

이는 내비게이션 시 캐시를 최대한 재사용하여 서버에 새로운 요청을 보내는 대신 요청 수와 전송되는 데이터 양을 줄여 성능을 향상시킨다는 것을 의미합니다.

Router Cache가 어떻게 작동하는지 및 설정 방법에 대해 자세히 알아보세요.

4. Partial Rendering

부분 렌더링은 내비게이션 시 변경되는 경로 세그먼트만 클라이언트에서 다시 렌더링되고, 공유되는 세그먼트는 유지된다는 것을 의미합니다.

예를 들어, 두 형제 경로인 /dashboard/settings/dashboard/analytics 사이를 내비게이션할 때, settings 페이지와 analytics 페이지는 렌더링되고, 공유되는 dashboard 레이아웃은 유지됩니다.

image-01.png

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

5. Soft Navigation

브라우저는 페이지 간 내비게이션 시 "hard navigation"을 수행합니다. Next.js 앱 라우터는 페이지 간 "soft navigation"을 가능하게 하여 변경된 경로 세그먼트만 다시 렌더링되도록 합니다(부분 렌더링). 이를 통해 내비게이션 동안 클라이언트의 React 상태가 유지됩니다.

6. Back and Forward Navigation

기본적으로 Next.js는 뒤로 및 앞으로 내비게이션할 때 스크롤 위치를 유지하고, Router Cache에서 경로 세그먼트를 재사용합니다.

7. Routing between pages/ and app/

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

잘못된 양성(false positives): "거짓 양성" 또는 "잘못된 양성"은 테스트나 검사 결과에서 실제로는 존재하지 않는 것을 존재한다고 잘못 판정하는 경우를 의미합니다. 예를 들어, 어떤 검사에서 실제로는 질병이 없는 사람을 질병이 있다고 진단하는 경우가 거짓 양성입니다.

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