Next.js 국제화 완전 가이드: next-intl 모범 사례

작년에 다국어 지원이 필요한 Next.js 프로젝트를 맡았습니다. 설정 파일에 가득한 i18n 옵션을 처음 봤을 때는 꽤 막막했습니다. 문서를 한참 살펴본 뒤에야 App Router와 기존 Pages Router가 국제화를 다루는 방식이 완전히 다르다는 사실을 알았습니다. 일주일 동안 씨름한 끝에 next-intl 설정을 마쳤고, 그 과정에서 여러 시행착오도 겪었습니다.
이번 글에서는 Next.js의 국제화 방식, 특히 App Router에서 next-intl로 다국어 기능을 깔끔하게 구현하는 방법을 살펴보겠습니다.
왜 next-intl을 선택해야 할까요?
Next.js에 국제화 기능이 기본으로 들어 있지 않느냐고 생각할 수 있습니다. 실제로 Pages Router 시절에는 Next.js가 i18n 라우팅을 기본 지원했습니다. 하지만 App Router에서는 이 기능이 제거되었습니다.
공식 권장 방식은 서드파티 라이브러리를 사용하는 것입니다. next-intl은 그중 가장 인기 있는 선택지 중 하나입니다.
next-intl의 장점:
- App Router 기본 지원 - App Router에 맞춰 설계되어 사용하기 편합니다.
- 타입 안전성 - TypeScript와 함께 사용하면 번역 텍스트의 타입을 검사할 수 있습니다.
- 유연한 라우팅 방식 - 하위 경로, 도메인, Cookie 등 여러 언어 전환 방식을 지원합니다.
- 강력한 기능 - 복수형, 날짜 및 숫자 형식화, 리치 텍스트 등을 지원합니다.
- 뛰어난 성능 - Server Component 친화적이며 정적 렌더링을 지원합니다.
다른 방식과 비교해 next-intl은 문서도 비교적 명확해서 입문 과정이 덜 힘듭니다.
기본 설정: 처음부터 시작하기
1. 의존성 설치
먼저 next-intl을 설치합니다.
npm install next-intl
# 또는
pnpm add next-intl
# 또는
yarn add next-intl
2. 번역 파일 만들기
프로젝트 루트에 messages 폴더를 만들고(locales 등 다른 이름도 가능), 언어별 JSON 파일을 생성합니다.
messages/
├── en.json
├── zh.json
└── ja.json
messages/zh.json:
{
"HomePage": {
"title": "欢迎来到我的网站",
"description": "这是一个支持多语言的 Next.js 应用"
},
"Navigation": {
"home": "首页",
"about": "关于",
"contact": "联系我们"
}
}
messages/en.json:
{
"HomePage": {
"title": "Welcome to My Website",
"description": "This is a multilingual Next.js application"
},
"Navigation": {
"home": "Home",
"about": "About",
"contact": "Contact Us"
}
}
중첩 구조가 필수는 아니지만, 페이지나 컴포넌트별로 묶으면 관리가 훨씬 편합니다.
3. i18n.ts 설정
지원 언어를 설정할 i18n.ts 또는 i18n/config.ts를 만듭니다.
import { getRequestConfig } from 'next-intl/server';
export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`./messages/${locale}.json`)).default
}));
이 설정은 next-intl에 번역 파일의 위치를 알려 줍니다. locale 매개변수는 URL에서 자동으로 추출됩니다.
4. 미들웨어 만들기
프로젝트 루트에 middleware.ts를 만듭니다. 다국어 라우팅을 처리하는 핵심 파일입니다.
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
// 지원 언어 목록
locales: ['en', 'zh', 'ja'],
// 기본 언어
defaultLocale: 'zh',
// URL에 기본 언어를 항상 표시할지 여부
localePrefix: 'as-needed'
});
export const config = {
// api, _next/static, _next/image, favicon.ico를 제외한 모든 경로와 일치
matcher: ['/', '/(zh|en|ja)/:path*', '/((?!api|_next|_next/static|_next/image|favicon.ico).*)']
};
localePrefix 옵션:
'always'- 기본 언어를 포함한 모든 언어에 접두사를 표시합니다(/zh/about,/en/about).'as-needed'- 기본 언어에는 접두사를 표시하지 않습니다(/about,/en/about).'never'- 모든 언어에서 접두사를 표시하지 않습니다. 도메인 같은 다른 언어 식별 방식이 필요합니다.
저는 보통 'as-needed'를 사용합니다. 중국어 사용자에게 편하고 URL도 더 간결해 보이기 때문입니다.
5. app 디렉터리 구조 조정
가장 중요한 단계입니다. 모든 라우트를 [locale] 동적 라우트 아래로 옮겨야 합니다.
변경 전:
app/
├── page.tsx
├── about/
│ └── page.tsx
└── layout.tsx
변경 후:
app/
├── [locale]/
│ ├── page.tsx
│ ├── about/
│ │ └── page.tsx
│ └── layout.tsx
└── layout.tsx (선택 사항, 전역 설정에 사용)
6. 루트 Layout 설정
app/[locale]/layout.tsx에서 언어를 설정합니다.
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
const locales = ['en', 'zh', 'ja'];
export function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
export default async function LocaleLayout({
children,
params: { locale }
}: {
children: React.ReactNode;
params: { locale: string };
}) {
// 지원 언어인지 확인
if (!locales.includes(locale)) {
notFound();
}
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
정적 생성을 사용한다면 generateStaticParams가 어떤 언어의 페이지를 생성해야 하는지 Next.js에 알려 준다는 점에 주의하세요.
컴포넌트에서 번역 사용하기
설정을 마치면 컴포넌트에서 번역을 사용할 수 있습니다.
Server Component에서 사용
import { useTranslations } from 'next-intl';
export default function HomePage() {
const t = useTranslations('HomePage');
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
);
}
useTranslations의 인자는 번역 파일의 네임스페이스, 즉 최상위 key입니다. 인자를 전달하지 않으면 t('HomePage.title')처럼 전체 경로를 사용할 수 있습니다.
Client Component에서 사용
Client Component에서도 사용법은 같습니다.
'use client';
import { useTranslations } from 'next-intl';
export default function Navigation() {
const t = useTranslations('Navigation');
return (
<nav>
<a href="/">{t('home')}</a>
<a href="/about">{t('about')}</a>
<a href="/contact">{t('contact')}</a>
</nav>
);
}
Server Component와 Client Component의 사용법이 같다는 점이 next-intl의 편리한 부분입니다.
다국어 라우팅 처리
현재 언어 가져오기
import { useLocale } from 'next-intl';
export default function LanguageSwitcher() {
const locale = useLocale();
return <div>Current language: {locale}</div>;
}
언어 전환기 만들기
모든 다국어 웹사이트에 필요한 기능입니다.
'use client';
import { useLocale } from 'next-intl';
import { usePathname, useRouter } from 'next/navigation';
export default function LanguageSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const switchLanguage = (newLocale: string) => {
// 경로의 언어 부분 교체
const newPath = pathname.replace(`/${locale}`, `/${newLocale}`);
router.push(newPath);
};
return (
<select value={locale} onChange={(e) => switchLanguage(e.target.value)}>
<option value="zh">中文</option>
<option value="en">English</option>
<option value="ja">日本語</option>
</select>
);
}
다만 이 방식에는 작은 문제가 있습니다. 기본 언어가 중국어인 페이지의 URL이 /about이라면 영어로 전환했을 때 /en/about이 되어야 합니다. 따라서 위 코드를 조금 개선해야 합니다.
const switchLanguage = (newLocale: string) => {
// 현재 언어 접두사 제거
let path = pathname;
if (pathname.startsWith(`/${locale}`)) {
path = pathname.substring(locale.length + 1);
}
// 새 언어 접두사 추가(기본 언어이면서 as-needed 설정인 경우 제외)
const newPath = newLocale === 'zh' ? path : `/${newLocale}${path}`;
router.push(newPath);
};
Link 컴포넌트 사용
next-intl은 언어 접두사를 자동으로 처리하는 향상된 Link 컴포넌트를 제공합니다.
import { Link } from '@/navigation'; // 사전 설정 필요
<Link href="/about">
{t('about')}
</Link>
navigation.ts를 설정합니다.
import { createSharedPathnamesNavigation } from 'next-intl/navigation';
export const locales = ['en', 'zh', 'ja'] as const;
export const localePrefix = 'as-needed';
export const { Link, redirect, usePathname, useRouter } =
createSharedPathnamesNavigation({ locales, localePrefix });
이렇게 내보낸 Link, useRouter 등의 컴포넌트는 언어 경로를 자동으로 처리합니다.
고급 기능
1. 매개변수가 있는 번역
번역 문장에는 변수를 삽입해야 할 때가 많습니다. next-intl에서는 다음과 같이 작성합니다.
messages/zh.json:
{
"welcome": "欢迎回来,{username}!",
"items": "你有 {count} 个新消息"
}
사용법:
const t = useTranslations();
<p>{t('welcome', { username: 'John' })}</p>
<p>{t('items', { count: 5 })}</p>
2. 복수형 처리
언어마다 복수형 규칙이 다릅니다. next-intl은 이를 처리할 수 있습니다.
messages/en.json:
{
"messages": {
"one": "You have {count} message",
"other": "You have {count} messages"
}
}
사용법:
t('messages', { count: 1 }) // "You have 1 message"
t('messages', { count: 5 }) // "You have 5 messages"
중국어에는 복수형 개념이 없으므로 다음과 같이 쓸 수 있습니다.
messages/zh.json:
{
"messages": "你有 {count} 条消息"
}
3. 날짜와 숫자 형식화
next-intl은 전용 형식화 함수를 제공합니다.
import { useFormatter } from 'next-intl';
export default function DateExample() {
const format = useFormatter();
const now = new Date();
return (
<div>
<p>{format.dateTime(now, { dateStyle: 'full' })}</p>
{/* 중국어: 2025年12月25日星期三 */}
{/* 영어: Wednesday, December 25, 2025 */}
<p>{format.number(1234567.89, { style: 'currency', currency: 'CNY' })}</p>
{/* 중국어: ¥1,234,567.89 */}
{/* 영어: CN¥1,234,567.89 */}
</div>
);
}
4. 리치 텍스트 번역
번역 내용에 HTML 태그나 React 컴포넌트가 포함될 때가 있습니다.
messages/zh.json:
{
"richText": "我同意<terms>服务条款</terms>和<privacy>隐私政策</privacy>"
}
사용법:
import { useTranslations } from 'next-intl';
export default function Agreement() {
const t = useTranslations();
return (
<p>
{t.rich('richText', {
terms: (chunks) => <a href="/terms">{chunks}</a>,
privacy: (chunks) => <a href="/privacy">{chunks}</a>
})}
</p>
);
}
번역 파일 관리 모범 사례
프로젝트가 커질수록 번역 파일도 관리하기 어려워집니다. 실용적인 팁 몇 가지를 소개합니다.
1. 기능 모듈별로 분리하기
모든 번역을 하나의 거대한 JSON에 넣지 말고 페이지나 기능별로 나눕니다.
messages/
├── zh/
│ ├── common.json # 공통 번역(버튼, 오류 메시지 등)
│ ├── home.json # 홈 페이지
│ ├── about.json # 소개 페이지
│ └── auth.json # 인증 관련
├── en/
│ ├── common.json
│ ├── home.json
│ ├── about.json
│ └── auth.json
그런 다음 i18n.ts에서 합칩니다.
import { getRequestConfig } from 'next-intl/server';
export default getRequestConfig(async ({ locale }) => {
const common = (await import(`./messages/${locale}/common.json`)).default;
const home = (await import(`./messages/${locale}/home.json`)).default;
const about = (await import(`./messages/${locale}/about.json`)).default;
const auth = (await import(`./messages/${locale}/auth.json`)).default;
return {
messages: {
common,
home,
about,
auth
}
};
});
2. TypeScript 타입 검사 사용하기
제가 특히 유용하다고 느낀 기능입니다. 번역 파일의 타입을 정의하면 오타를 방지할 수 있습니다.
types/i18n.ts:
import zh from '@/messages/zh.json';
type Messages = typeof zh;
declare global {
interface IntlMessages extends Messages {}
}
TypeScript를 설정합니다(tsconfig.json).
{
"compilerOptions": {
"types": ["./types/i18n"]
}
}
이제 t('xxx')를 사용할 때 해당 key가 없으면 TypeScript가 오류를 표시합니다. 매우 실용적입니다.
3. 공통 번역 추출하기
‘저장’, ‘취소’, ‘확인’ 같은 공통 텍스트는 여러 곳에서 사용되므로 별도로 관리하는 것이 좋습니다.
messages/zh/common.json:
{
"actions": {
"save": "保存",
"cancel": "取消",
"delete": "删除",
"confirm": "确认",
"edit": "编辑"
},
"status": {
"success": "操作成功",
"error": "操作失败",
"loading": "加载中..."
}
}
사용법:
const t = useTranslations('common.actions');
<button>{t('save')}</button>
4. 번역 관리 도구 사용하기
프로젝트 규모가 커지면 JSON 파일을 수동으로 관리하기가 매우 번거롭습니다. 다음 도구를 고려할 수 있습니다.
- Tolgee - 실시간 편집을 지원하는 오픈 소스 번역 관리 플랫폼
- Localazy - 자동화된 번역 워크플로
- i18n Ally (VSCode 확장) - 편집기에서 번역을 직접 관리
저는 주로 i18n Ally를 사용합니다. 코드를 작성하면서 번역 내용을 바로 확인하고 수정할 수 있어 편리합니다.
5. 누락된 번역 처리하기
개발 중에는 특정 언어의 번역이 아직 끝나지 않은 경우가 자주 있습니다. 이럴 때는 폴백 로직을 설정할 수 있습니다.
// i18n.ts
export default getRequestConfig(async ({ locale }) => {
const messages = (await import(`./messages/${locale}.json`)).default;
const fallback = locale !== 'zh'
? (await import(`./messages/zh.json`)).default
: {};
return {
messages: {
...fallback,
...messages
}
};
});
영어 번역이 누락되면 중국어로 자동 대체됩니다.
성능 최적화
1. 코드 분할
번역 파일이 크다면 필요할 때만 로드할 수 있습니다.
// 필요할 때만 로드
export default function AdminPage() {
const t = useTranslations('admin'); // admin 네임스페이스만 로드
// ...
}
2. 정적 생성
자주 바뀌지 않는 페이지는 정적 생성을 사용하면 성능을 크게 높일 수 있습니다.
// app/[locale]/about/page.tsx
export const dynamic = 'force-static';
export function generateStaticParams() {
return [
{ locale: 'zh' },
{ locale: 'en' },
{ locale: 'ja' }
];
}
3. 번역 미리 로드하기
첫 화면에 표시되는 콘텐츠는 번역 파일을 미리 로드할 수 있습니다.
import { getTranslations } from 'next-intl/server';
// 서버에서 미리 로드
export default async function Home() {
const t = await getTranslations('HomePage');
return <h1>{t('title')}</h1>;
}
자주 발생하는 문제와 해결 방법
1. 동적 라우트의 언어 전환
동적 라우트(/blog/[slug] 등)에서 언어를 전환할 때는 slug를 유지해야 합니다.
const switchLanguage = (newLocale: string) => {
const segments = pathname.split('/').filter(Boolean);
// 이전 언어 접두사 제거
if (['zh', 'en', 'ja'].includes(segments[0])) {
segments.shift();
}
// 새 언어 접두사 추가(필요한 경우)
if (newLocale !== 'zh' || localePrefix === 'always') {
segments.unshift(newLocale);
}
router.push('/' + segments.join('/'));
};
2. SEO 최적화
다국어 웹사이트에서는 SEO에 특히 주의해야 합니다.
// app/[locale]/layout.tsx
export async function generateMetadata({ params: { locale } }) {
const t = await getTranslations({ locale, namespace: 'metadata' });
return {
title: t('title'),
description: t('description'),
alternates: {
canonical: `https://example.com/${locale}`,
languages: {
'zh-CN': 'https://example.com/zh',
'en-US': 'https://example.com/en',
'ja-JP': 'https://example.com/ja'
}
}
};
}
3. 언어 감지
사용자가 처음 방문할 때 언어를 자동으로 감지할 수 있습니다.
// middleware.ts
import createMiddleware from 'next-intl/middleware';
import { NextRequest } from 'next/server';
const intlMiddleware = createMiddleware({
locales: ['en', 'zh', 'ja'],
defaultLocale: 'zh',
localeDetection: true // 자동 감지 활성화
});
export default function middleware(request: NextRequest) {
return intlMiddleware(request);
}
next-intl은 Accept-Language 요청 헤더를 기준으로 언어를 자동 선택합니다.
4. 언어 기본 설정 유지하기
사용자가 언어를 선택했다면 그 선택을 기억하는 것이 좋습니다.
// Cookie에 저장
const switchLanguage = (newLocale: string) => {
document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000`;
router.push(newPath);
};
next-intl 미들웨어가 이 Cookie를 자동으로 읽습니다.
실전 사례: 완성된 국제화 프로젝트
마지막으로 이전에 작업했던 소규모 프로젝트의 핵심 코드를 정리해 소개합니다.
프로젝트 구조:
├── app/
│ ├── [locale]/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── blog/
│ │ └── [slug]/
│ │ └── page.tsx
├── components/
│ ├── LanguageSwitcher.tsx
│ └── Navigation.tsx
├── messages/
│ ├── zh/
│ │ ├── common.json
│ │ └── blog.json
│ ├── en/
│ │ ├── common.json
│ │ └── blog.json
├── i18n.ts
├── middleware.ts
└── navigation.ts
navigation.ts(라우팅 설정):
import { createSharedPathnamesNavigation } from 'next-intl/navigation';
export const locales = ['zh', 'en'] as const;
export const localePrefix = 'as-needed';
export const { Link, redirect, usePathname, useRouter } =
createSharedPathnamesNavigation({ locales, localePrefix });
components/Navigation.tsx:
'use client';
import { Link } from '@/navigation';
import { useTranslations } from 'next-intl';
import LanguageSwitcher from './LanguageSwitcher';
export default function Navigation() {
const t = useTranslations('common.navigation');
return (
<nav className="flex items-center justify-between p-4">
<div className="flex gap-4">
<Link href="/">{t('home')}</Link>
<Link href="/blog">{t('blog')}</Link>
<Link href="/about">{t('about')}</Link>
</div>
<LanguageSwitcher />
</nav>
);
}
이 프로젝트를 배포한 뒤 언어 전환은 매우 매끄러웠고 별다른 문제도 발생하지 않았습니다.
정리
App Router 시대의 Next.js 국제화는 분명 조금 복잡하지만, next-intl을 익히면 생각보다 어렵지 않습니다.
- 핵심 설정: 미들웨어 + i18n.ts +
[locale]디렉터리 - 번역 사용: Server와 Client Component 모두에서
useTranslationsHook 사용 - 라우팅 처리: next-intl이 제공하는 Link와 Router 사용
- 파일 관리: 모듈별 분리 + TypeScript 타입 검사
- 성능 최적화: 정적 생성 + 필요할 때만 로드
솔직히 처음 이 방식을 접했을 때는 꽤 복잡하게 느껴졌습니다. 특히 미들웨어와 동적 라우트 부분이 그랬습니다. 하지만 몇 번 작성해 보면 익숙해지고, 이제는 국제화 프로젝트에서 거의 항상 이 구성을 사용합니다.
다국어 프로젝트를 진행 중이거나 준비하고 있다면 next-intl을 적극 권합니다. 학습 비용은 있지만 장기적으로 충분히 시간을 투자할 가치가 있습니다.
참고 자료
이 글이 Next.js 국제화 때문에 고민하는 분들에게 도움이 되기를 바랍니다!
Next.js 국제화 전체 설정 절차
next-intl 설치부터 다국어 라우팅과 번역 파일 관리 설정까지 다루는 전체 절차
⏱️ Estimated time: 2 hr
- 1
Step 1: next-intl 설치 및 기본 설정
설치:
```bash
npm install next-intl
```
번역 파일 생성:
```
messages/
zh.json
en.json
```
middleware.ts 설정:
```ts
import createMiddleware from 'next-intl/middleware'
import { routing } from './i18n/routing'
export default createMiddleware(routing)
export const config = {
matcher: ['/', '/(zh|en)/:path*']
}
```
app/[locale]/layout.tsx 설정:
```tsx
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'
export default async function LocaleLayout({
children,
params: { locale }
}) {
const messages = await getMessages()
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
)
}
```
핵심:
• [locale] 동적 라우트를 사용합니다.
• layout에서 번역을 제공합니다.
• middleware로 언어 전환을 처리합니다. - 2
Step 2: 다국어 라우팅 방식 설정
방식 1: 하위 경로(권장)
• URL 형식: /zh/about, /en/about
• 설정이 간단함
• SEO 친화적
방식 2: 도메인
• URL 형식: zh.example.com, en.example.com
• 여러 도메인 설정 필요
• 더 전문적인 구성
방식 3: Cookie
• Cookie로 언어 전환
• URL에 언어 접두사가 없음
• 한 언어를 주로 쓰는 사용자에게 적합
하위 경로 설정:
```ts
// i18n/routing.ts
export const routing = {
locales: ['zh', 'en'],
defaultLocale: 'zh'
}
```
사용법:
```tsx
import { useTranslations } from 'next-intl'
export function Page() {
const t = useTranslations('common')
return <h1>{t('title')}</h1>
}
```
핵심: 프로젝트에 맞는 라우팅 방식을 선택하세요. 대부분의 프로젝트에는 하위 경로만으로 충분합니다. - 3
Step 3: 번역 파일 관리
번역 파일 생성:
```json
// messages/zh.json
{
"common": {
"title": "환영합니다",
"description": "다국어 웹사이트입니다"
},
"nav": {
"home": "홈",
"about": "소개"
}
}
```
번역 사용:
```tsx
import { useTranslations } from 'next-intl'
export function Page() {
const t = useTranslations('common')
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
)
}
```
타입 안전성:
```ts
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server'
export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`../messages/${locale}.json`)).default
}))
```
핵심:
• 중첩 구조로 번역을 정리합니다.
• TypeScript와 함께 사용해 타입 안전성을 확보합니다.
• i18n Ally VSCode 확장으로 개발 효율을 높입니다.
FAQ
App Router에 next-intl이 필요한 이유는 무엇인가요?
Pages Router:
• i18n 라우팅을 기본 지원
• next.config.js에서 i18n 필드 설정
• 언어 전환 자동 처리
App Router:
• 내장 i18n 기능 제거
• 서드파티 라이브러리 필요
• next-intl이 가장 인기 있는 선택지 중 하나
next-intl의 장점:
• App Router 기본 지원
• 타입 안전성
• 유연한 라우팅 방식
• 복수형과 날짜 형식화 등 강력한 기능
• 뛰어난 성능
권장 사항: App Router를 사용한다면 next-intl을 우선 고려하세요.
next-intl은 몇 가지 라우팅 방식을 지원하나요?
방식 1: 하위 경로(권장)
• URL 형식: /zh/about, /en/about
• 설정이 간단함
• SEO 친화적
• 대부분의 프로젝트에 적합
방식 2: 도메인
• URL 형식: zh.example.com, en.example.com
• 여러 도메인 설정 필요
• 더 전문적인 구성
• 대규모 프로젝트에 적합
방식 3: Cookie
• Cookie로 언어 전환
• URL에 언어 접두사가 없음
• 한 언어를 주로 쓰는 사용자에게 적합
• 설정이 복잡함
선택 기준:
• 대부분의 프로젝트 → 하위 경로
• 대규모 프로젝트 → 도메인
• 특수 요구 사항 → Cookie
핵심: 프로젝트에 맞는 방식을 고르되, 대부분은 하위 경로로 충분합니다.
next-intl은 어떻게 설정하나요?
```bash
npm install next-intl
```
번역 파일 생성:
```
messages/
zh.json
en.json
```
middleware.ts 설정:
```ts
import createMiddleware from 'next-intl/middleware'
import { routing } from './i18n/routing'
export default createMiddleware(routing)
export const config = {
matcher: ['/', '/(zh|en)/:path*']
}
```
app/[locale]/layout.tsx 설정:
```tsx
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'
export default async function LocaleLayout({
children,
params: { locale }
}) {
const messages = await getMessages()
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
)
}
```
핵심:
• [locale] 동적 라우트를 사용합니다.
• layout에서 번역을 제공합니다.
• middleware로 언어 전환을 처리합니다.
번역 파일은 어떻게 관리하나요?
```json
// messages/zh.json
{
"common": {
"title": "환영합니다",
"description": "다국어 웹사이트입니다"
},
"nav": {
"home": "홈",
"about": "소개"
}
}
```
번역 사용:
```tsx
import { useTranslations } from 'next-intl'
export function Page() {
const t = useTranslations('common')
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
)
}
```
타입 안전성:
```ts
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server'
export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`../messages/${locale}.json`)).default
}))
```
핵심:
• 중첩 구조로 번역을 정리합니다.
• TypeScript와 함께 사용해 타입 안전성을 확보합니다.
• i18n Ally VSCode 확장으로 개발 효율을 높입니다.
권장 사항: 기능 모듈별로 번역 파일을 나눠 파일 하나가 지나치게 커지지 않게 하세요.
next-intl은 어떤 기능을 지원하나요?
• 텍스트 번역(t 함수)
• 복수형 처리
• 날짜 형식화
• 숫자 형식화
• 리치 텍스트 지원
사용 예:
```tsx
import { useTranslations, useFormatter } from 'next-intl'
export function Page() {
const t = useTranslations('common')
const format = useFormatter()
return (
<div>
<h1>{t('title')}</h1>
<p>{format.dateTime(new Date(), { dateStyle: 'long' })}</p>
<p>{format.number(1234.56, { style: 'currency', currency: 'USD' })}</p>
</div>
)
}
```
장점:
• 강력한 기능
• 타입 안전성
• 뛰어난 성능
• Server Component 친화적
권장 사항: next-intl의 기능을 충분히 활용해 사용자 경험을 개선하세요.
언어 전환은 어떻게 구현하나요?
```tsx
import { Link } from '@/i18n/navigation'
<Link href="/about" locale="en">
English
</Link>
<Link href="/about" locale="zh">
中文
</Link>
```
useRouter 사용:
```tsx
'use client'
import { useRouter, usePathname } from '@/i18n/navigation'
export function LanguageSwitcher() {
const router = useRouter()
const pathname = usePathname()
const switchLanguage = (locale: string) => {
router.replace(pathname, { locale })
}
return (
<button onClick={() => switchLanguage('en')}>
English
</button>
)
}
```
핵심:
• next-intl이 제공하는 Link와 useRouter를 사용합니다.
• 현재 경로는 유지하고 언어만 전환합니다.
• 좋은 사용자 경험을 제공합니다.
권장 사항: 내비게이션 바나 푸터에 언어 전환 버튼을 추가하세요.
6분 읽기 · 게시일: 2025년 12월 25일 · 수정일: 2026년 9월 8일
Next.js 완전 가이드
검색으로 들어왔다면 같은 시리즈의 이전 글이나 다음 글로 이동하는 것이 가장 빠릅니다.
이전
Next.js OAuth 로그인 실전: Google, GitHub, WeChat 소셜 로그인 연동 가이드
OAuth 원리부터 실전 설정까지, 택배 대리 수령 비유로 인증 흐름을 이해하고 NextAuth.js로 Google, GitHub, WeChat 로그인을 구현하는 방법과 전체 오류 해결 과정을 설명합니다.
45편 중 12편
다음
Next.js 국제화와 정적 생성: SSG 다국어 사이트 실전 가이드
빌드 오류 해결부터 성능 최적화까지, App Router로 다국어 정적 생성을 안정적으로 구현하는 방법을 단계별로 설명합니다. 전체 코드 예제, generateStaticParams 설정, 빌드 시간 최적화 팁을 포함합니다.
45편 중 14편



댓글
GitHub로 로그인하여 댓글을 남기세요