Logo
Published on
·11 min read

Next.js - 다국어(i18n) 설정 및 static html로 내보내기 Part 2

개요

이전 글인 Part 1에서 Next.js 프로젝트를 생성하고 다국어(i18n) 설정을 했습니다. 하지만, npm run build 명령어를 실행하면 오류가 발생하는 상태에서 글이 끝났습니다.

이번 글에서는 다국어 Static Export까지 가능하도록 설정해보겠습니다.

내용이 길어져 Part 1, Part 2로 나누어 정리했습니다.

  • Part 1: Next.js 프로젝트 생성, 다국어(i18n) 설정
  • Part 2: 다국어 Static Export 적용

다국어 Static Export 설정

Static HTML Export with i18n compatibility in Next.js에 영어로(😥) 잘 설명되어있습니다.

저는 거의 그대로 진행했으며 약간의 순서 변경 및 실수했던 부분에 주의할 점을 추가했습니다.

next-language-detector 설치

아래 명령어로 next-language-detector 라이브러리를 설치합니다.

npm i next-language-detector

i18n 설정 제거

  • next.config.js 파일을 열어서 i18n 설정을 아래와 같이 제거합니다.
//const { i18n } = require('./next-i18next.config') // 제거

const nextConfig = {
  output: 'export',
  reactStrictMode: true,
  //i18n // 제거
}

module.exports = nextConfig

[locale] 폴더 생성

  • pages 폴더 하위에 [locale] 폴더를 생성합니다. (pages/[locale])
    ⚠ 주의할 점: locale 이 아닌 대괄호가 포함된 [locale] 폴더입니다.

[locale] 폴더로 파일 이동

  • pages 폴더 하위에 있는 _app.js, _document.js 파일을 제외한 모든 파일을 [locale] 폴더로 이동합니다.
    • 이 예제에서는 index.jssecond.js 파일을 [locale] 폴더로 이동시킵니다.
    • 결과적으로 pages/[locale]/index.js, pages/[locale]/second.js

lib 폴더

  • 최상위 폴더에 lib 폴더를 생성합니다.

  • lib/getStatic.js 파일을 생성하고 아래 내용을 추가합니다.

import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import i18nextConfig from '../next-i18next.config'

export const getI18nPaths = () =>
  i18nextConfig.i18n.locales.map((lng) => ({
    params: {
      locale: lng
    }
  }))

export const getStaticPaths = () => ({
  fallback: false,
  paths: getI18nPaths()
})

export async function getI18nProps(ctx, ns = ['common']) {
  const locale = ctx?.params?.locale
  let props = {
    ...(await serverSideTranslations(locale, ns))
  }
  return props
}

export function makeStaticProps(ns = {}) {
  return async function getStaticProps(ctx) {
    return {
      props: await getI18nProps(ctx, ns)
    }
  }
}
  • lib/languageDetector.js 파일을 생성하고 아래 내용을 추가합니다.
import languageDetector from 'next-language-detector'
import i18nextConfig from '../next-i18next.config'

export default languageDetector({
  supportedLngs: i18nextConfig.i18n.locales,
  fallbackLng: i18nextConfig.i18n.defaultLocale
})
  • lib/redirect.js 파일을 생성하고 아래 내용을 추가합니다.
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import languageDetector from './languageDetector'

export const useRedirect = (to) => {
  const router = useRouter()
  to = to || router.asPath

  // language detection
  useEffect(() => {
    const detectedLng = languageDetector.detect()
    if (to.startsWith('/' + detectedLng) && router.route === '/404') { // prevent endless loop
      router.replace('/' + detectedLng + router.route)
      return
    }

    languageDetector.cache(detectedLng)
    router.replace('/' + detectedLng + to)
  })

  return <></>
};

export const Redirect = () => {
  useRedirect()
  return <></>
}

// eslint-disable-next-line react/display-name
export const getRedirect = (to) => () => {
  useRedirect(to)
  return <></>
}

pages 폴더 하위에 파일 생성

[locale] 폴더로 이동시켰던 파일들과 동일한 파일명으로 pages 폴더 하위에 생성합니다.

  • pages/index.js, pages/second.js 파일을 생성합니다. 내용은 아래와 같습니다.
import { Redirect } from '../lib/redirect'
export default Redirect

언어를 감지해 해당 언어로 리다이렉트 시키는 역할을 합니다.

components 폴더 생성

  • components 폴더를 생성합니다.

  • components/Link.js 컴포넌트 파일을 생성합니다.
    ⚠ 주의할 점: 원문에는 legacyBehavior가 없는데, Next.js 13버전 이상에서는 없으면 오류가 발생합니다.

import React from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'

const LinkComponent = ({ children, skipLocaleHandling, ...rest }) => {
    const router = useRouter()
    const locale = rest.locale || router.query.locale || ''

    let href = rest.href || router.asPath
    if (href.indexOf('http') === 0) skipLocaleHandling = true
    if (locale && !skipLocaleHandling) {
        href = href
            ? `/${locale}${href}`
            : router.pathname.replace('[locale]', locale)
    }

    // legacyBehavior 없으면 오류 (13버전 이상)
    return (
        <>
            <Link href={href} legacyBehavior>
                <a {...rest}>{children}</a>
            </Link>
        </>
    )
}

export default LinkComponent

페이지 수정

  • pages/[locale]/index.jspages/[locale]/second.js 파일을 수정합니다.

    • Link import를 경로를 @/components/Link 로 수정합니다.
    • 기존 getStaticProps 함수를 제거하고 @/lib/getStatic에 선언된 함수를 사용합니다.
    • 아래 코드와 주석을 확인하세요.
  • pages/[locale]/index.js

import {useTranslation} from "next-i18next";
import {getStaticPaths, makeStaticProps} from "@/lib/getStatic"; // 추가

import Link from "@/components/Link"; // import 경로 변경(기존"next/link")

// 기존 내용 제거
// export async function getStaticProps({locale}) {
//     return {
//         props: {
//             ...(await serverSideTranslations(locale, ['common'])),
//         },
//     };
// }

const getStaticProps = makeStaticProps(['common']) // 추가
export { getStaticPaths, getStaticProps } // 추가

export default function Home() {
    const {t} = useTranslation(['common']);

    return (
        <div>
            <h1>
                {t('first')}
            </h1>
            <div>
                <p><Link href={'/second'}>{t('second')}</Link></p>
                <p><Link href={'/'} locale={'en'}>to English</Link> | <Link href={'/'} locale={'ko'}>한국어로</Link></p>
            </div>
        </div>
    )
}
  • pages/[locale]/second.js
import {useTranslation} from "next-i18next";
import {getStaticPaths, makeStaticProps} from "@/lib/getStatic"; // 추가

import Link from "@/components/Link"; // import 경로 변경(기존"next/link")

// 기존 내용 제거
// export async function getStaticProps({locale}) {
//     return {
//         props: {
//             ...(await serverSideTranslations(locale, ['common'])),
//         },
//     };
// }


const getStaticProps = makeStaticProps(['common']) // 추가
export { getStaticPaths, getStaticProps } // 추가

export default function Second() {
    const {t} = useTranslation(['common']);

    return (
        <div>
            <h1>
                {t('second')}
            </h1>
            <div>
                <p><Link href={'/'}>{t('first')}</Link></p>
                <p><Link href={'/second'} locale={'en'}>to English</Link> | <Link href={'/second'} locale={'ko'}>한국어로</Link></p>
            </div>
        </div>
    )
}

_document.js 파일 수정

pages/_document.js 파일을 열고 아래와 같이 수정합니다.

import Document, { Html, Head, Main, NextScript } from 'next/document'
import i18nextConfig from '../next-i18next.config'

class MyDocument extends Document {
  render() {
    const currentLocale = this.props.__NEXT_DATA__.query.locale || i18nextConfig.i18n.defaultLocale
    return (
      <Html lang={currentLocale}>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

이제 작업은 끝났고 브라우저 확인과 static export가 정상적으로 되는 지 확인 하는 것만 남았습니다.

브라우저에서 확인

  • npm run dev 명령어로 실행하여, 브라우저에서 정상적으로 동작하는 지 확인합니다.

기존 동작과 거의 동일하게 동작하는 것을 확인할 수 있습니다. 기존 동작과 다른 부분은 http://localhost:3000/ 로 표시되던 기본 언어가 http://localhost:3000/ko 로 표시된다는 점입니다.

  • http://localhost:3000/kohttp://localhost:3000/en 에 접속해서 각 페이지가 각 언어로 정상적으로 나오는지 확인합니다.
  • http://localhost:3000/ko/secondhttp://localhost:3000/en/second 에 접속해서 각 페이지가 각 언어로 정상적으로 나오는지 확인합니다.

아래 이미지는 브라우저에서 확인한 화면입니다.

브라우저 확인

참고

저의 경우 브라우저에서 정상 동작했지만, 실행창에서 아래와 같은 오류를 발견했습니다.

- error API Routes cannot be used with "output: export". See more info here:

프로젝트 생성 시에 자동으로 만들어진 api/hello.js 파일 때문에 발생하는 오류로 해당 파일을 삭제하면 오류가 사라집니다.

Static Export

드디어 정적 파일 내보내기를 해보겠습니다.

아래 명령어를 실행합니다.

npm run build

성공 메시지가 나온 후 프로젝트 폴더를 확인하면 out 폴더가 생성되어 있는 것을 볼 수 있습니다.

live-server 등을 이용해서 해당 폴더를 웹 서버에서 실행하면 정상적으로 동작하는 것을 확인할 수 있습니다.

live-server: 실시간 reload를 지원하는 간단한 개발용 웹서버

참고 : https://www.npmjs.com/package/live-server

저는 이렇게 만들어진 프로젝트를 CloudFlare Pages에 배포했습니다.

CloudFlare Pages: 정적 웹사이트를 무료로 배포할 수 있는 서비스로 GitHub과 연결하여 git push로 자동 배포되는 구성이 가능합니다.

코드

지금까지 작업한 코드는 아래 GitHub 저장소에서 확인할 수 있습니다.

https://github.com/hlog-kr/sample-i18n-static-export

  • git clone https://github.com/hlog-kr/sample-i18n-static-export.git 으로 저장소를 복제합니다.

  • 프로젝트 폴더에서 npm install 로 필요한 패키지를 설치합니다.

  • npm run dev 로 브라우저에서 동작 확인이 가능합니다.

  • npm run build 로 정적 파일 생성을 확인할 수 있습니다.

참고 URL

실전에서 바로 쓰는 Next.js:SSR부터 SEO 배포까지 확장성 높은 풀스택 서비스 구축 가이드, 한빛미디어  타입스크립트 리액트 Next.js로 배우는 실전 웹 애플리케이션 개발, 위키북스  모두의 구글 애널리틱스4:GA4로 하는 디지털 마케팅 데이터 분석, 길벗  트래픽을 쓸어 담는 검색엔진 최적화:검색엔진이 가장 좋아하는 사이트 만들기, e비즈북스
(위 링크는 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.)