Navigation API
Source URL: https://next-intl.dev/docs/routing/navigation
Navigation API
섹션 제목: “Navigation API”영상으로 보는 편이신가요?
next-intl은 <Link />, useRouter 같은 Next.js의 내비게이션 API를 감싸는 가벼운 wrapper를 제공하며, 내부적으로 사용자 locale과 pathname을 자동으로 처리합니다.
이 API들을 만들려면 routing 설정과 함께 createNavigation 함수를 호출하면 됩니다.
navigation.ts
import {createNavigation} from 'next-intl/navigation'; import {routing} from './routing';
export const {Link, redirect, usePathname, useRouter, getPathname} = createNavigation(routing);이 함수는 보통 컴포넌트에서 내비게이션 API에 쉽게 접근할 수 있도록 src/i18n/navigation.ts 같은 중앙 모듈에서 호출합니다.
빌드 시점에 locale을 알 수 없다면 어떻게 하나요?
런타임에 locale이 추가되거나 제거될 수 있는 앱을 만드는 경우, createNavigation을 locales 인자 없이 호출할 수 있습니다. 이렇게 하면 런타임에 발견되는 모든 문자열을 유효한 locale로 허용할 수 있습니다. 이 경우 defineRouting 함수는 사용하지 않습니다.
navigation.ts
import {createNavigation} from 'next-intl/navigation';
export const {Link, redirect, usePathname, useRouter, getPathname} = createNavigation({ // ... potentially other routing // config, but no `locales` ... });다만 middleware의 locales 인자는 여전히 필수입니다. 런타임에 사용 가능한 locale 목록을 가져와야 한다면, middleware용 routing 설정을 요청별로 동적으로 제공할 수 있습니다.
API
섹션 제목: “API”생성된 내비게이션 API는 Next.js의 대응 API를 얇게 감싼 wrapper이며, 대부분 동일한 함수 시그니처를 따릅니다. routing 설정과 사용자 locale이 자동으로 반영됩니다.
routing 설정에서 pathnames을 사용 중이라면, href 인자로 받을 수 있는 내부 pathname이 엄격한 타입으로 제한되고 지정된 locale에 맞게 현지화됩니다.
Link
섹션 제목: “Link”이 컴포넌트는 next/link를 감싸며, 필요에 따라 pathname을 현지화합니다.
import {Link} from '@/i18n/navigation';
// When the user is on `/en`, the link will point to `/en/about`
// Search params can be added via `query`
// You can override the `locale` to switch to another language // (this will set the `hreflang` attribute on the anchor tag)pathnames 사용 여부에 따라 동적 params는 다음과 같이 전달할 수 있습니다.
// 1. A final string (when not using `pathnames`)
// 2. An object (when using `pathnames`) SusanNext.js의 useSelectedLayoutSegment hook을 사용하면 부모 레이아웃 내부에서 특정 자식 segment가 활성 상태인지 감지할 수 있습니다. 이 값은 내부 pathname을 반환하므로, Link에 전달할 href와 매칭할 수 있습니다.
NavigationLink.tsx
'use client';
import {useSelectedLayoutSegment} from 'next/navigation'; import {ComponentProps} from 'react'; import {Link} from '@/i18n/navigation';
export default function NavigationLink({ href, ...rest }: ComponentProps<typeof Link>) { const selectedLayoutSegment = useSelectedLayoutSegment(); const pathname = selectedLayoutSegment ? `/${selectedLayoutSegment}` : '/'; const isActive = pathname === href;
return ( ); } <nav> </nav>활성 링크 컴포넌트 만들기에 관한 Next.js 문서도 참고하세요.
내부적으로 Link에 전달되는 href prop을 받는 컴포넌트를 만들어야 한다면, ComponentProps 타입으로 Link의 props를 조합할 수 있습니다.
StyledLink.tsx
import {ComponentProps} from 'react'; import {Link} from '@/i18n/navigation';
type Props = ComponentProps<typeof Link> & { color: 'blue' | 'red'; };
export default function StyledLink({color, href, ...rest}: Props) { return <Link href={href} style={{color}} {...rest} />; }pathnames을 사용 중인 경우, 래핑 컴포넌트의 href prop도 routing 설정 기반의 엄격한 타입이 적용됩니다.
locale prop을 사용할 때 <Link />가 항상 locale prefix를 붙이는 이유는 무엇인가요?
보통 대상 페이지의 locale을 변경하기 위해 locale prop을 제공하면, localePrefix 설정이 always가 아니어도 링크의 href에 항상 locale prefix가 포함되는 것을 볼 수 있습니다.
예시:
localePrefix: 'as-needed'를 사용하고 기본 locale이 en이라면, 아래 링크의 href는 여전히 /en/about입니다.
// Links to `/en/about` About그 이유는 사용자가 /about 같은 prefix 없는 라우트로 이동하기 전에 cookie를 업데이트해야 할 수 있기 때문입니다. prefix가 붙은 pathname은 이를 처리한 뒤 prefix 없는 라우트로 리디렉션합니다. 페이지가 hydrate되기 전에 링크가 클릭될 수 있어 클라이언트 코드가 cookie를 업데이트할 기회를 갖지 못할 수 있으므로, 이 동작이 필요합니다.
이 동작을 피하고 싶다면 useRouter로 locale을 전환할 수 있습니다. 이 방식은 대상 페이지로 이동하기 전에 클라이언트에서 cookie를 업데이트하는 흐름을 활용할 수 있습니다.
pathnames 설정 사용 시 알 수 없는 라우트에 어떻게 링크하나요?
이 경우 내비게이션 API는 엄격한 타입이 적용되어 pathnames 설정에 지정된 라우트만 허용합니다. 특정 위치에서 알 수 없는 라우트로 링크해야 한다면, 케이스별로 타입 체크를 비활성화할 수 있습니다.
// @ts-expect-error알 수 없는 라우트는 그대로 전달되지만, 절대 pathname인 경우에는 შესაბამის locale prefix가 붙습니다.
next-intl의 <Link />는 next/link의 기본 prefetch 동작을 상속합니다.
유일한 예외는 locale prop을 설정한 경우입니다. 이때는 링크를 prefetch하지 않는데, 그렇지 않으면 prefetch 요청 과정에서 locale cookie가 너무 이르게 덮어써질 수 있기 때문입니다.
useRouter
섹션 제목: “useRouter”이벤트 핸들러처럼 프로그래밍 방식으로 이동해야 한다면, next-intl은 Next.js의 useRouter를 감싸고 pathname을 համապատասխան하게 현지화하는 편의 API를 제공합니다.
'use client';
import {useRouter} from '@/i18n/navigation';
const router = useRouter();
// When the user is on `/en`, the router will navigate to `/en/about` router.push('/about');
// Search params can be added via `query` router.push({ pathname: '/users', query: {sortBy: 'name'} });
// You can override the `locale` to switch to another language router.replace('/about', {locale: 'de'});pathnames 사용 여부에 따라 동적 params는 다음과 같이 전달할 수 있습니다.
// 1. A final string (when not using `pathnames`) router.push('/users/12');
// 2. An object (when using `pathnames`) router.push({ pathname: '/users/[userId]', params: {userId: '5'} });usePathname과 useRouter를 함께 사용하면, 같은 pathname으로 이동하면서 locale만 덮어써서 현재 페이지의 locale을 프로그래밍 방식으로 변경할 수 있습니다.
pathnames 사용 여부에 따라 내부 pathname을 해석하기 위해 params를 전달해야 할 수도 있습니다.
'use client';
import {usePathname, useRouter} from '@/i18n/navigation'; import {useParams} from 'next/navigation';
const pathname = usePathname(); const router = useRouter();
// Without `pathnames`: Pass the current `pathname` router.replace(pathname, {locale: 'de'});
// With `pathnames`: Pass `params` as well const params = useParams(); router.replace( // @ts-expect-error -- TypeScript will validate that only known `params` // are used in combination with a given `pathname`. Since the two will // always match for the current route, we can skip runtime checks. {pathname, params}, {locale: 'de'} );더 알아보기:
usePathname
섹션 제목: “usePathname”locale prefix가 포함될 수 있는 현재 pathname에서 prefix를 제외한 값을 가져오려면 usePathname을 호출하면 됩니다.
'use client';
import {usePathname} from '@/i18n/navigation';
// When the user is on `/en`, this will be `/` const pathname = usePathname();pathnames을 사용 중이라면, 반환되는 pathname은 내부 pathname 템플릿에 해당합니다(동적 params는 실제 값으로 치환되지 않음).
// When the user is on `/de/über-uns`, this will be `/about` const pathname = usePathname();
// When the user is on `/de/neuigkeiten/produktneuheit`, // this will be `/news/[articleSlug]` const pathname = usePathname();redirect
섹션 제목: “redirect”렌더링을 중단하고 다른 페이지로 리디렉션하려면 redirect 함수를 호출하면 됩니다. 이 함수는 Next.js의 redirect를 감싸며, 필요에 따라 pathname을 현지화합니다.
현재 locale만 전달하는 경우라도 locale prop은 항상 필수입니다.
import {redirect} from '@/i18n/navigation';
// Redirects to `/en/login` redirect({href: '/login', locale: 'en'});
// Search params can be added via `query` redirect({href: '/users', query: {sortBy: 'name'}, locale: 'en'});pathnames 사용 여부에 따라 동적 params는 다음과 같이 전달할 수 있습니다.
// 1. A final string (when not using `pathnames`) redirect({href: '/users/12', locale: 'en'});
// 2. An object (when using `pathnames`) redirect({ href: { pathname: '/users/[userId]', params: {userId: '5'} }, locale: 'en' });localePrefix 설정이 always가 아닌 경우, forcePrefix 옵션을 true로 설정해 locale prefix를 강제할 수 있습니다. 사용자의 locale을 변경하면서 먼저 locale cookie를 업데이트해야 할 때 유용합니다.
// Will initially redirect to `/en/about` to update the locale // cookie, regardless of your `localePrefix` setting redirect({href: '/about', locale: 'en', forcePrefix: true});💡
permanentRedirect도 지원됩니다.
redirect 호출 후 TypeScript가 타입을 올바르게 좁히지 못하는 이유는 무엇인가요?
TypeScript는 현재 제어 흐름 분석(control flow analysis)에 제한이 있어, redirect 호출 이후 타입을 정확히 좁히거나 도달 불가능 코드를 감지하지 못할 수 있습니다.
import {redirect} from '@/i18n/navigation';
function UserProfile({userId}: {userId?: string}) { if (!userId) { redirect({href: '/login', locale: 'en'}); }
// `userId` should be narrowed to `string` here, // but TypeScript doesn't analyze this correctly }이 제한을 우회하려면 redirect 함수 호출을 return하면 됩니다.
if (!userId) { return redirect({href: '/login', locale: 'en'}); }
// ✅ `userId` is narrowed to `string` heregetPathname
섹션 제목: “getPathname”locale 기반으로 특정 pathname을 구성해야 한다면 getPathname 함수를 호출하면 됩니다.
import {getPathname} from '@/i18n/navigation';
// Will return `/en/about` const pathname = getPathname({ locale: 'en', href: '/about' });
// Search params can be added via `query` const pathname = getPathname({ locale: 'en', href: { pathname: '/users', query: {sortBy: 'name'} } });pathnames 사용 여부에 따라 동적 params는 다음과 같이 전달할 수 있습니다.
// 1. A final string (when not using `pathnames`) const pathname = getPathname({ locale: 'en', href: '/users/12' });
// 2. An object (when using `pathnames`) const pathname = getPathname({ locale: 'en', href: { pathname: '/users/[userId]', params: {userId: '5'} } });사용 사례: