Next.js 15로 다국어 계산기 플랫폼 구축하기

Next.js 15, React 19, next-intl을 사용하여 고성능 다국어 계산기 플랫폼을 구축하는 방법에 대한 상세 분석. 국제화 구현, SEO 최적화 및 성능 최적화 포함.

14 분 읽기
Next.jsReact국제화SEO성능 최적화TypeScript

안녕하세요, 동료 개발자 여러분! 👋

전 세계 사용자들이 이용할 수 있는 웹사이트를 구축해본 적이 있다면, 이것이 얼마나 도전적인 일인지 아실 것입니다. 저는 최근 Free Calculators를 만들기 위한 여정을 시작했습니다 - 12개 언어로 사용자들에게 서비스를 제공하는 플랫폼이죠. 말씀드리자면, 정말 모험이었습니다!

이 게시물에서 저는 이 다국어 계산기 플랫폼 구축 뒤에 숨은 실제 이야기를 공유하고 싶습니다. 단순히 "이것이 우리가 한 일" 버전이 아니라, "실제로 일어난 일, 잘못된 것, 그리고 우리가 배운 것" 버전입니다. 솔직히 말해서, 글로벌 관객을 위해 구축하는 것은 공원 산책이 아니거든요.

우리가 이것을 구축한 이유 (그리고 왜 중요한지)

이것을 상상해보세요: 계산기 앱을 만들고 있습니다. 꽤 간단하죠? 그런데 일본의 누군가는 일본어 버전이 필요할 수도 있고, 브라질의 누군가는 포르투갈어를 선호할 수도 있으며, 독일 사용자들은 숫자를 독일 방식으로 포맷팅하기를 원합니다 (1,234.56 대신 1.234,56). 갑자기 당신의 간단한 계산기가 복잡한 국제 프로젝트가 됩니다.

바로 이것이 우리에게 일어난 일입니다. 우리는 기본 계산기로 시작했지만, 결국 다른 문화, 언어, 숫자 형식의 사용자들에게 서비스를 제공하는 플랫폼을 구축하게 되었습니다. 그리고 아시나요? 이것은 제가 작업한 가장 보람 있는 프로젝트 중 하나가 되었습니다.

기술 스택 결정: 왜 이 도구들을 선택했는지

왜 Next.js 15인가?

솔직히 말하면, Next.js 15가 처음 나왔을 때 우리는 조금 망설였습니다. 결국 "망가진 게 아니면 고치지 마라"죠? 하지만 새로운 App Router와 React 19 통합을 보았을 때, 우리는 도약하기로 결정했습니다. 현명한 선택이었다는 것이 밝혀졌습니다.

// 이것이 우리가 라우팅을 설정한 방법입니다
export const routing = defineRouting({
  locales: [
    'en',
    'zh',
    'ja',
    'ko',
    'fr',
    'de',
    'es',
    'pt',
    'ru',
    'ar',
    'hi',
    'it',
  ],
  defaultLocale: 'en',
  localePrefix: 'as-needed', // 이 설정이 많은 두통을 구해주었습니다
  localeDetection: true,
});

React 19: 업그레이드할 가치가 있나?

짧은 답: 절대적으로! 긴 답: 몇 가지 호환성 문제에 부딪혔지만, 새 기능들의 성능 개선은 상당합니다. 특히 Suspense와 동시성 기능의 개선이 우리의 사용자 경험을 훨씬 더 좋게 만들었습니다.

TypeScript: 우리의 구세주

TypeScript 없이 12개 언어의 번역 파일을 관리한다는 것은 상상할 수 없습니다. 타입 안전성은 리팩토링할 때 우리에게 자신감을 주었습니다. 특히 복잡한 중첩된 번역 객체를 다룰 때요.

// 이것이 우리가 번역 타입을 정의하는 방법입니다
interface TranslationKeys {
  nav: {
    siteName: string;
    home: string;
    calculators: string;
  };
  common: {
    calculate: string;
    clear: string;
    result: string;
  };
}

국제화 구현: 실제 도전과 해결책

도전 1: 언어 감지의 복잡성

"언어 감지? 쉬워!"라고 생각하실 수도 있습니다. 하지만 그렇지 않습니다. 사용자들은 다른 기기에서 접근하거나, VPN을 사용하거나, 특정 언어 설정을 가질 수 있습니다. 우리는 언어 감지 로직을 완성하는 데 많은 시간을 보냈습니다.

// 우리의 미들웨어 설정
import createMiddleware from 'next-intl/middleware';
import { routing } from '@/i18n/routing';

export default createMiddleware(routing);

export const config = {
  matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
};

도전 2: 번역 파일 관리

12개 언어의 번역 파일을 관리하는 것은 12개의 다른 프로젝트를 관리하는 것과 같습니다. 우리는 도움을 주기 위해 몇 가지 자동화 스크립트를 개발했습니다:

// 우리의 번역 관리 스크립트
const addMissingTranslations = async () => {
  const baseTranslations = await loadTranslations('en');
  const allLocales = await getAllLocales();

  for (const locale of allLocales) {
    const existingTranslations = await loadTranslations(locale);
    const missingKeys = findMissingKeys(baseTranslations, existingTranslations);

    if (missingKeys.length > 0) {
      console.log(`Missing keys in ${locale}:`, missingKeys);
      await addMissingKeys(locale, missingKeys);
    }
  }
};

도전 3: 숫자 포맷팅의 함정

이것은 가장 흥미로운 도전 중 하나였습니다. 다른 나라들이 다른 숫자 형식을 가지고 있다는 것을 아시나요? 독일은 소수점 구분자로 쉼표를 사용하고, 미국은 점을 사용합니다. 우리는 통화 계산을 다룰 때 많은 문제에 부딪혔습니다. 적절한 지역화된 포맷팅을 구현할 때까지요.

SEO 최적화: 검색 엔진이 우리를 사랑하게 만들기

정적 사이트 생성의 힘

우리는 정적 사이트 생성(SSG)을 사용하기로 결정했습니다. 이것은 우리가 지금까지 내린 최고의 결정일지도 모릅니다. 페이지가 번개처럼 빠를 뿐만 아니라, SEO 결과도 훌륭합니다.

// 각 언어에 대한 정적 페이지 생성
export const dynamic = 'force-static';

export function generateStaticParams() {
  return routing.locales
    .filter(locale => locale !== routing.defaultLocale)
    .map(locale => ({ locale }));
}

메타데이터 생성의 기술

각 언어에 대한 올바른 메타데이터를 생성하는 것은 약간의 기교가 필요합니다. 우리는 SEO 친화적인 메타데이터를 자동으로 생성하는 시스템을 개발했습니다:

export async function generateMetadata({
  params,
}: {
  params: Promise<{ locale: string }>;
}): Promise<Metadata> {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: 'Home' });

  return {
    title: t('title'),
    description: t('description'),
    openGraph: {
      title: t('title'),
      description: t('description'),
      locale: locale,
    },
  };
}

성능 최적화: 사용자 경험을 매끄럽게 만들기

이미지 최적화의 중요성

우리는 이미지 로딩 최적화에 많은 시간을 보냈습니다. AVIF와 WebP 형식의 채택으로 페이지 로딩 속도가 40% 향상되었습니다.

// next.config.ts의 이미지 설정
images: {
  formats: ['image/avif', 'image/webp'],
  domains: ['freecalculators.app'],
  deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
  imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
}

캐시 전략: 우리의 비밀 무기

올바른 캐시 전략을 구현하는 것은 몇 가지 시행착오가 필요했습니다. 우리는 계층화된 캐시 접근법으로 끝났습니다:

async headers() {
  return [
    // 정적 자산에 대한 강력한 캐시
    {
      source: '/(.*).(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)',
      headers: [{
        key: 'Cache-Control',
        value: 'public, max-age=31536000, immutable',
      }],
    },
    // 페이지에 대한 협상 캐시
    {
      source: '/(.*)',
      headers: [{
        key: 'Cache-Control',
        value: 'public, max-age=0, must-revalidate',
      }],
    },
  ];
}

사용자 경험 설계: 모든 사람이 환영받는다고 느끼게 하기

반응형 설계의 도전

우리의 계산기가 모든 기기에서 완벽하게 작동하는 것을 보장하는 것은 지속적인 도전입니다. 우리는 모바일 우선 접근법을 채택했습니다:

// 모바일 우선 반응형 레이아웃
<div className="flex flex-col md:flex-row gap-4">
  <div className="w-full md:w-1/2">
    {/* 계산기 인터페이스 */}
  </div>
  <div className="w-full md:w-1/2">
    {/* 결과 표시 */}
  </div>
</div>

접근성: 단순한 규정 준수를 넘어서

우리는 접근성이 단순한 법적 요구사항 이상이라는 것을 깨달았습니다 - 이것이 옳은 일입니다. 우리는 모든 상호작용 요소에 적절한 ARIA 라벨을 추가했습니다:

<button
  aria-label={t('common.calculate')}
  aria-describedby="calculator-help"
  className="bg-blue-600 text-white px-4 py-2 rounded"
>
  {t('common.calculate')}
</button>

개발 도구 체인: 개발을 더 효율적으로 만들기

오류 모니터링: 우리의 안전망

우리는 오류 모니터링을 위해 Sentry를 통합했습니다. 이것은 우리가 예상하지 못한 많은 문제들을 발견하는 데 도움이 되었습니다:

export default withSentryConfig(withNextIntl(nextConfig), {
  org: 'my-projects-pd',
  project: 'free-calculators',
  widenClientFileUpload: true,
  disableLogger: true,
  automaticVercelMonitors: true,
});

자동화된 번역 관리

우리는 번역 파일을 관리하기 위한 몇 가지 스크립트를 개발했고, 이 스크립트들은 이제 우리 워크플로우의 필수적인 부분입니다.

우리가 배운 교훈

1. 처음부터 국제화를 생각하기

다국어 지원이 필요할 수도 있다고 생각한다면, 처음부터 설계하세요. 나중에 국제화를 추가하는 것은 처음부터 설계하는 것보다 훨씬 어렵습니다.

2. 사용자 경험이 기술적 과시보다 중요

우리는 성능 최적화에 많은 시간을 보냈지만, 궁극적으로 사용자들이 가장 관심을 갖는 것은 인터페이스가 직관적인지, 계산이 정확한지입니다.

3. 자동화가 핵심

12개 언어의 번역을 수동으로 관리하는 것은 비현실적입니다. 자동화 스크립트는 우리 프로젝트의 생명선입니다.

4. 테스트, 테스트, 그리고 다시 테스트

다른 언어들은 다른 텍스트 길이, 다른 숫자 형식, 다른 문화적 기대를 가지고 있습니다. 철저한 테스트는 필수입니다.

미래 계획

우리는 더 많은 언어를 추가하고, 번역 관리 프로세스를 개선하고, AI 지원 번역의 가능성을 탐색하는 것을 고려하고 있습니다. 하지만 가장 중요한 것은, 사용자들에게 최고의 계산기 경험을 계속 제공하고 싶다는 것입니다.

결론

다국어 웹 애플리케이션을 구축하는 것은 도전이지만, 매우 보람 있는 경험이기도 합니다. 현명한 기술 선택과 많은 테스트를 통해, 우리는 정말로 글로벌 사용자들에게 서비스를 제공하는 플랫폼을 성공적으로 만들었습니다.

다국어 애플리케이션 구축을 고려하고 있다면, 이 글이 당신에게 약간의 영감을 주기를 바랍니다. 기억하세요, 모든 도전은 학습의 기회이고, 모든 실수는 성공으로 가는 디딤돌입니다.


프로젝트 링크: https://www.freecalculators.app 기술 스택: Next.js 15, React 19, TypeScript, Tailwind CSS, next-intl