Vite i18n 앱을 Next.js로 마이그레이션해도 문제 없이

발행: (2026년 6월 16일 PM 07:00 GMT+9)
7 분 소요
원문: Dev.to

출처: Dev.to

프레임워크 수준 i18n의 도전

Internationalization (i18n) 은 프론트엔드 애플리케이션 중 가장 복잡한 계층 중 하나입니다. Vite 기반 SPA에서 작업한다면 react-i18next 또는 lingui 같은 라이브러리를 사용해 클라이언트쪽 번역을 처리합니다. 상태는 브라우저 내에서 관리되고, 언어는 보통 로컬 스토리지나 URL 쿼리 파라미터에 저장됩니다.

Next.js로 이동하면 전반적으로 상황이 완전히 바뀝니다. 순수 클라이언트 라우팅 모델에서 서버 준비 아키텍처로 전환되며, SEO 친화적인 로컬(예: /en/about 또는 /es/about)이 표준이 됩니다. 마이그레이션을 신중히 계획하지 않으면 SEO, 수화, 사용자 경험에 손상을 입을 위험이 있습니다.

이 가이드에서는 Vite에서 Next.js로 i18n 로직을 App Router를 사용해 마이그레이션하면서 코드베이스 유지성을 유지하는 방법을 살펴보겠습니다.

Vite 앱에서 i18n 설정은 보통 다음과 같이 보입니다:

// i18n.ts 에서
i18n.use(initReactI18next).init({
  resources,
  lng: localStorage.getItem('lang') || 'en',
  fallbackLng: 'en',
});

Next.js에서는 언어의 “진실 출처”는 URL 경로입니다. Next.js 13+ (App Router)는 로케일을 레이아웃 세그먼트로 기대합니다. 파일 구조는 src/pages/About.tsx에서 app/[locale]/about/page.tsx로 이동해야 합니다.

useEffect 내에서 localStorage를 확인하는 대신, Next.js는 페이지가 렌더링되기 전에 사용자의 선호 언어를 탐지하기 위해 Middleware를 활용합니다. 이를 통해 “잘못된 번역 콘텐츠 플래시”가 방지됩니다.

import  { NextResponse  } from  'next/server';
import type  { NextRequest  } from  'next/server';

const locales = ['en', 'de', 'fr'];

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  const pathnameHasLocale = locales.some(
     (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
   );

  if (pathnameHasLocale) return;

  // 현재 로케일을 감지하는 로직
  const locale = 'en';
  request.nextUrl.pathname = `/${locale}${pathname}`;
  return NextResponse.redirect(request.nextUrl);
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

이곳이 대부분의 개발자가 막히는 부분입니다. Vite에서는 모든 컴포넌트가 클라이언트 컴포넌트이지만, Next.js에서는 서버 컴포넌트와 클라이언트 컴포넌트 중 어떤 것을 선택해야 할지 결정해야 합니다.

서버 컴포넌트: 데이터를 가져오고 SEO를 위해 사용합니다. 여기서 useTranslation 훅이 필요하지 않으며, 직접 JSON 파일을 임포트하거나 next-intl 같은 라이브러리를 사용해 서버쪽에서 사전을 얻을 수 있습니다.

클라이언트 컴포넌트: 상호작용(폼, 버튼)에 사용합니다. 이들 역시 훅을 사용하지만, 현재 로케일을 props로 전달하거나 전용 제공자를 통해 받아야 합니다.

대부분의 Vite 앱은 translation 파일을 public/locales에 보관합니다. 하지만 Next.js에서는 로컬에 colocate(같은 위치에)하거나 동적으로 임포트하여 초기 번들 크기를 줄일 수 있습니다.

이동 과정에서 라우팅 및 컴포넌트 계층 구조 변경이 압도적이라고 느낀다면 ViteToNext.AI를 사용해 Vite 구조를 App Router 호환 형식으로 자동 변환하는 heavy lifting을 Delegating 할 수 있습니다.

Vite에서는 react-helmet을 사용해 제목을 업데이트하고 설명을 관리했습니다. Next.js에서는 generateMetadata 함수를 사용합니다. i18n에 있어 이 함수는 라우트별로 로컬화된 메타 태그를 제공할 수 있게 해주므로 매우 중요합니다.

export async function generateMetadata({ params: { locale } }) {
  const t = await getDictionary(locale);

  return {
    title: t.seo.title,
    description: t.seo.description,
   };
}

수화 불일치: 서버는 “Hello”(영어)를 렌더링하지만, 클라이언트가 로컬 스토리지 체크가 남아 있어 언어가 스페인어라고 생각하면 React가 수화 오류를 발생시킵니다. 항상 클라이언트 상태를 URL 파라미터와 동기화하세요.

하드코딩된 링크: 모든 컴포넌트를 업데이트합니다. 기존의 <Link> 대신 <NextLink>를 사용해야 합니다.

서드파티 라이브러리: Radix나 Headless UI와 같은 UI 라이브러리는 올바른 로케일 제공자로 감싸서 스크린 리더가 언어 변경을 정확히 인식하도록 해야 합니다.

Vite에서 Next.js로 i18n 준비 앱을 마이그레이션하려면 useEffect/localStorage와 같은 부수 효과 기반 언어 감지에서 URL 기반 접근 방식으로 전환해야 합니다. Next.js Middleware를 활용하고 서버와 클라이언트 컴포넌트를 적절히 분리하면 성능 향상과 SEO 개선 효과를 얻으면서 Vite에서 즐겼던 개발자 경험을 유지할 수 있습니다.

추가 자료: vitetonext.codebypaki.online에서 자동화된 마이그레이션 전략을 확인하세요.

0 조회
Back to Blog

관련 글

더 보기 »