์ž‘๊ฐ€:
    ์ƒ์„ฑ:2025-11-01๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ:2026-05-31

    2025๋…„์— next-intl์„ ์‚ฌ์šฉํ•˜์—ฌ Next.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตญ์ œํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•

    ๋ชฉ์ฐจ

    next-intl์ด๋ž€?

    next-intl์€ Next.js App Router๋ฅผ ์œ„ํ•ด ํŠน๋ณ„ํžˆ ์„ค๊ณ„๋œ ์ธ๊ธฐ ์žˆ๋Š” ๊ตญ์ œํ™”(i18n) ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ๋›ฐ์–ด๋‚œ TypeScript ์ง€์›๊ณผ ๋‚ด์žฅ ์ตœ์ ํ™”๋ฅผ ์ œ๊ณตํ•˜์—ฌ ๋‹ค๊ตญ์–ด Next.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์›ํ™œํ•˜๊ฒŒ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ์›ํ•˜์‹ ๋‹ค๋ฉด next-i18next ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ๊ณ ํ•˜๊ฑฐ๋‚˜, ์ง์ ‘ Intlayer๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
    ๋น„๊ต ๋‚ด์šฉ์€ next-i18next vs next-intl vs Intlayer์—์„œ ํ™•์ธํ•˜์„ธ์š”.

    ๋”ฐ๋ผ์•ผ ํ•  ๋ชจ๋ฒ” ์‚ฌ๋ก€

    ๊ตฌํ˜„์— ๋“ค์–ด๊ฐ€๊ธฐ ์ „์—, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค:

    • HTML lang ๋ฐ dir ์†์„ฑ ์„ค์ • ๋ ˆ์ด์•„์›ƒ์—์„œ getLocaleDirection(locale)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ dir์„ ๊ณ„์‚ฐํ•˜๊ณ , ์˜ฌ๋ฐ”๋ฅธ ์ ‘๊ทผ์„ฑ๊ณผ SEO๋ฅผ ์œ„ํ•ด <html lang={locale} dir={dir}>๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.
    • ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ณ„ ๋ฉ”์‹œ์ง€ ๋ถ„๋ฆฌ ๋กœ์ผ€์ผ๊ณผ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ณ„๋กœ JSON ํŒŒ์ผ์„ ๊ตฌ์„ฑํ•˜์—ฌ (์˜ˆ: common.json, about.json) ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ๋กœ๋“œํ•˜๋„๋ก ํ•˜์„ธ์š”.
    • ํด๋ผ์ด์–ธํŠธ ํŽ˜์ด๋กœ๋“œ ์ตœ์†Œํ™” ํŽ˜์ด์ง€์—์„œ NextIntlClientProvider์— ํ•„์š”ํ•œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋งŒ ์ „์†กํ•˜์„ธ์š” (์˜ˆ: pick(messages, ['common', 'about'])).
    • ์ •์  ํŽ˜์ด์ง€ ์„ ํ˜ธ ์„ฑ๋Šฅ๊ณผ SEO ํ–ฅ์ƒ์„ ์œ„ํ•ด ๊ฐ€๋Šฅํ•œ ํ•œ ์ •์  ํŽ˜์ด์ง€๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
    • ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ์˜ i18n

    ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋Š” ํŽ˜์ด์ง€๋‚˜ client๋กœ ํ‘œ์‹œ๋˜์ง€ ์•Š์€ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์ฒ˜๋Ÿผ ์ •์ ์ด๋ฉฐ ๋นŒ๋“œ ์‹œ ๋ฏธ๋ฆฌ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฒˆ์—ญ ํ•จ์ˆ˜๋ฅผ props๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    • TypeScript ํƒ€์ž… ์„ค์ •
      ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „๋ฐ˜์— ๊ฑธ์ณ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ๋กœ์ผ€์ผ์— ๋Œ€ํ•œ ํƒ€์ž…์„ ์„ค์ •ํ•˜์„ธ์š”.
    • ๋ฆฌ๋””๋ ‰์…˜์„ ์œ„ํ•œ ํ”„๋ก์‹œ
      ๋กœ์ผ€์ผ ๊ฐ์ง€์™€ ๋ผ์šฐํŒ…์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‚ฌ์šฉ์ž๋ฅผ ์ ์ ˆํ•œ ๋กœ์ผ€์ผ ์ ‘๋‘์‚ฌ๊ฐ€ ๋ถ™์€ URL๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•˜๊ธฐ ์œ„ํ•ด ํ”„๋ก์‹œ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
    • ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ, ์‚ฌ์ดํŠธ๋งต, robots.txt์˜ ๊ตญ์ œํ™”
      Next.js์—์„œ ์ œ๊ณตํ•˜๋Š” generateMetadata ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ, ์‚ฌ์ดํŠธ๋งต, robots.txt๋ฅผ ๊ตญ์ œํ™”ํ•˜์—ฌ ๋ชจ๋“  ๋กœ์ผ€์ผ์—์„œ ๊ฒ€์ƒ‰ ์—”์ง„์ด ๋” ์ž˜ ์ธ์‹ํ•˜๋„๋ก ํ•˜์„ธ์š”.
    • ๋งํฌ ํ˜„์ง€ํ™”
      Link ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋งํฌ๋ฅผ ํ˜„์ง€ํ™”ํ•˜๊ณ  ์‚ฌ์šฉ์ž๋ฅผ ์ ์ ˆํ•œ ๋กœ์ผ€์ผ ์ ‘๋‘์‚ฌ๊ฐ€ ๋ถ™์€ URL๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•˜์„ธ์š”. ์ด๋Š” ๋ชจ๋“  ๋กœ์ผ€์ผ์—์„œ ํŽ˜์ด์ง€์˜ ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅ์„ฑ์„ ๋ณด์žฅํ•˜๋Š” ๋ฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.
    • ํ…Œ์ŠคํŠธ ๋ฐ ๋ฒˆ์—ญ ์ž๋™ํ™” ํ…Œ์ŠคํŠธ์™€ ๋ฒˆ์—ญ ์ž๋™ํ™”๋Š” ๋‹ค๊ตญ์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ ์ง€ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ ์†Œ์š”๋˜๋Š” ์‹œ๊ฐ„์„ ์ค„์ด๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.
    ๊ตญ์ œํ™” ๋ฐ SEO์— ๋Œ€ํ•ด ์•Œ์•„์•ผ ํ•  ๋ชจ๋“  ๋‚ด์šฉ์„ ์ •๋ฆฌํ•œ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”: next-intl๊ณผ ํ•จ๊ป˜ํ•˜๋Š” ๊ตญ์ œํ™”(i18n).

    Next.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ next-intl ์„ค์ • ๋‹จ๊ณ„๋ณ„ ๊ฐ€์ด๋“œ

    ide.intlayer.org
    GitHub์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ…œํ”Œ๋ฆฟ์„ ์ฐธ์กฐํ•˜์„ธ์š”.

    ๋‹ค์Œ์€ ์šฐ๋ฆฌ๊ฐ€ ์ƒ์„ฑํ•  ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค:

    bash
    .
    โ”œโ”€โ”€ global.ts
    โ”œโ”€โ”€ locales
    โ”‚   โ”œโ”€โ”€ en
    โ”‚   โ”‚  โ”œโ”€โ”€ common.json
    โ”‚   โ”‚  โ””โ”€โ”€ about.json
    โ”‚   โ”œโ”€โ”€ fr
    โ”‚   โ”‚  โ”œโ”€โ”€ common.json
    โ”‚   โ”‚  โ””โ”€โ”€ about.json
    โ”‚   โ””โ”€โ”€ es
    โ”‚      โ”œโ”€โ”€ common.json
    โ”‚      โ””โ”€โ”€ about.json
    โ””โ”€โ”€ src # Src๋Š” ์„ ํƒ ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค
        โ”œโ”€โ”€ proxy.ts
        โ”œโ”€โ”€ app
        โ”‚   โ”œโ”€โ”€ i18n.ts
        โ”‚   โ””โ”€โ”€ [locale]
        โ”‚       โ”œโ”€โ”€ layout.tsx
        โ”‚       โ”œโ”€โ”€ (home) # / (ํ™ˆ ๋ฆฌ์†Œ์Šค๋กœ ๋ชจ๋“  ํŽ˜์ด์ง€๋ฅผ ์˜ค์—ผ์‹œํ‚ค์ง€ ์•Š๊ธฐ ์œ„ํ•œ ๋ผ์šฐํŠธ ๊ทธ๋ฃน)
        โ”‚       โ”‚   โ”œโ”€โ”€ layout.tsx
        โ”‚       โ”‚   โ””โ”€โ”€ page.tsx
        โ”‚       โ””โ”€โ”€ about # /about
        โ”‚           โ”œโ”€โ”€ layout.tsx
        โ”‚           โ””โ”€โ”€ page.tsx
        โ””โ”€โ”€ components
            โ”œโ”€โ”€ ClientComponent.tsx
            โ””โ”€โ”€ ServerComponent.tsx
    
    1. ์˜์กด์„ฑ ์„ค์น˜

      npm์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜์„ธ์š”:

      bash
      npm install next-intl
      
      • next-intl: Next.js App Router์šฉ ํ•ต์‹ฌ ๊ตญ์ œํ™” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ๋ฒˆ์—ญ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ํ›…, ์„œ๋ฒ„ ํ•จ์ˆ˜, ํด๋ผ์ด์–ธํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
    2. ํ”„๋กœ์ ํŠธ ๊ตฌ์„ฑ

      ์ง€์›ํ•˜๋Š” ๋กœ์ผ€์ผ์„ ์ •์˜ํ•˜๊ณ  next-intl์˜ ์š”์ฒญ ๊ตฌ์„ฑ์„ ์„ค์ •ํ•˜๋Š” ๊ตฌ์„ฑ ํŒŒ์ผ์„ ๋งŒ๋“œ์„ธ์š”. ์ด ํŒŒ์ผ์€ i18n ์„ค์ •์˜ ๋‹จ์ผ ์ง„์‹ค ์†Œ์Šค๋กœ ์ž‘๋™ํ•˜๋ฉฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „๋ฐ˜์— ๊ฑธ์ณ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

      ๋กœ์ผ€์ผ ๊ตฌ์„ฑ์„ ์ค‘์•™ ์ง‘์ค‘ํ™”ํ•˜๋ฉด ๋ถˆ์ผ์น˜๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ  ํ–ฅํ›„ ๋กœ์ผ€์ผ์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•˜๊ธฐ๊ฐ€ ๋” ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค. getRequestConfig ํ•จ์ˆ˜๋Š” ๋ชจ๋“  ์š”์ฒญ ์‹œ ์‹คํ–‰๋˜๋ฉฐ ๊ฐ ํŽ˜์ด์ง€์— ํ•„์š”ํ•œ ๋ฒˆ์—ญ๋งŒ ๋กœ๋“œํ•˜์—ฌ ์ฝ”๋“œ ๋ถ„ํ• ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ณ  ๋ฒˆ๋“ค ํฌ๊ธฐ๋ฅผ ์ค„์ž…๋‹ˆ๋‹ค.

      src/i18n.ts
      import { notFound } from "next/navigation";
      import createMiddleware from "next-intl/middleware";
      import { createNavigation } from "next-intl/navigation";
      
      // ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๊ฐ–์ถ˜ ์ง€์› ๋กœ์ผ€์ผ ์ •์˜
      export const locales = ["en", "fr", "es"] as const;
      export type Locale = (typeof locales)[number];
      export const defaultLocale: Locale = "en";
      
      export function isRTL(locale: Locale | (string & {})) {
        return /^(ar|fa|he|iw|ur|ps|sd|ug|yi|ckb|ku)(-|$)/i.test(locale);
      }
      
      // ์ฝ”๋“œ ๋ถ„ํ• ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ๋กœ์ผ€์ผ๋ณ„๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋™์ ์œผ๋กœ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค
      // Promise.all์€ ๋” ๋‚˜์€ ์„ฑ๋Šฅ์„ ์œ„ํ•ด ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ๋ณ‘๋ ฌ๋กœ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค
      async function loadMessages(locale: Locale) {
        // ๋ ˆ์ด์•„์›ƒ/ํŽ˜์ด์ง€์—์„œ ํ•„์š”ํ•œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋งŒ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค
        const [common, home, about] = await Promise.all([
          import(`../locales/${locale}/common.json`).then((m) => m.default),
          import(`../locales/${locale}/home.json`).then((m) => m.default),
          import(`../locales/${locale}/about.json`).then((m) => m.default),
          // ... ํ–ฅํ›„ JSON ํŒŒ์ผ์€ ์—ฌ๊ธฐ์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
        ]);
      
        return { common, home, about } as const;
      }
      
      // ์ง€์—ญํ™”๋œ URL์„ ์ƒ์„ฑํ•˜๋Š” ํ—ฌํผ ํ•จ์ˆ˜ (์˜ˆ: /about vs /fr/about)
      export function localizedPath(locale: string, path: string) {
        return locale === defaultLocale ? path : `/${locale}${path}`;
      }
      
      // getRequestConfig๋Š” ๋ชจ๋“  ์š”์ฒญ ์‹œ ์‹คํ–‰๋˜๋ฉฐ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์— ๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค
      // next-intl์ด Next.js์˜ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์— ์—ฐ๊ฒฐ๋˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค
      export default async function getRequestConfig({
        requestLocale,
      }: {
        requestLocale: Promise<string | undefined>;
      }) {
        const requested: Locale = ((await requestLocale) as Locale) ?? defaultLocale;
      
        if (!locales.includes(requested)) notFound();
      
        return {
          locale: requested,
          messages: await loadMessages(requested),
        };
      }
      
      export function getCookie(locale: Locale) {
        return [
          `NEXT_LOCALE=${locale}`,
          "Path=/",
          `Max-Age=${60 * 60 * 24 * 365}`, // 1๋…„
          "SameSite=Lax",
        ].join("; ");
      }
      
      const routingOptions = {
        locales,
        defaultLocale,
        localePrefix: "as-needed", // /en/... ๊ฒฝ๋กœ๋ฅผ /...๋กœ ๋ณ€๊ฒฝ
        // ์„ ํƒ ์‚ฌํ•ญ: ์ง€์—ญํ™”๋œ ๊ฒฝ๋กœ๋ช…
        // pathnames: {
        //   '/': '/',
        //   '/about': {en: '/about', fr: '/a-propos', es: '/acerca-de'},
        //   '/blog/[slug]': '/blog/[slug]'
        // }
        //  localeDetection: true, // ์ฟ ํ‚ค๋กœ ์ธํ•œ "/" -> "/en" ๋ฆฌ๋””๋ ‰์…˜ ๋ฐฉ์ง€
      } as const;
      
      export const { Link, redirect, usePathname, useRouter, getPathname } =
        createNavigation(routingOptions);
      
      export const proxy = createMiddleware(routingOptions);
      
    3. ๋™์  ๋กœ์ผ€์ผ ๋ผ์šฐํŠธ ์ •์˜

      ์•ฑ ํด๋”์— [locale] ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋กœ์ผ€์ผ ๊ธฐ๋ฐ˜ ๋™์  ๋ผ์šฐํŒ…์„ ์„ค์ •ํ•˜์„ธ์š”. ์ด๋ฅผ ํ†ตํ•ด Next.js๋Š” ๊ฐ ๋กœ์ผ€์ผ์ด URL ์„ธ๊ทธ๋จผํŠธ๊ฐ€ ๋˜๋Š” ๋กœ์ผ€์ผ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ˆ: /en/about, /fr/about).

      ๋™์  ๋ผ์šฐํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Next.js๊ฐ€ ๋นŒ๋“œ ์‹œ ๋ชจ๋“  ๋กœ์ผ€์ผ์— ๋Œ€ํ•ด ์ •์  ํŽ˜์ด์ง€๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด ์„ฑ๋Šฅ๊ณผ SEO๊ฐ€ ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค. ๋ ˆ์ด์•„์›ƒ ์ปดํฌ๋„ŒํŠธ๋Š” ๋กœ์ผ€์ผ์— ๋”ฐ๋ผ HTML์˜ lang ๋ฐ dir ์†์„ฑ์„ ์„ค์ •ํ•˜๋Š”๋ฐ, ์ด๋Š” ์ ‘๊ทผ์„ฑ๊ณผ ๊ฒ€์ƒ‰ ์—”์ง„ ์ดํ•ด์— ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

      src/app/[locale]/layout.tsx
      import type { ReactNode } from "react";
      import { locales } from "@/i18n";
      import { getLocaleDirection, setRequestLocale } from "next-intl/server";
      
      // ๋นŒ๋“œ ์‹œ ๋ชจ๋“  ๋กœ์ผ€์ผ์— ๋Œ€ํ•ด ์ •์  ํŽ˜์ด์ง€๋ฅผ ๋ฏธ๋ฆฌ ์ƒ์„ฑ (SSG)
      // ์ด๋Š” ์„ฑ๋Šฅ๊ณผ SEO๋ฅผ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค
      export function generateStaticParams() {
        return locales.map((locale) => ({ locale }));
      }
      
      export default function LocaleLayout({
        children,
        params,
      }: {
        children: ReactNode;
        params: Promise<{ locale: string }>;
      }) {
        // Next.js App Router์—์„œ params๋Š” Promise์ž…๋‹ˆ๋‹ค (await ๊ฐ€๋Šฅ)
        // ์ด๋ฅผ ํ†ตํ•ด ๋™์  ๋ผ์šฐํŠธ ์„ธ๊ทธ๋จผํŠธ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
        const { locale } = await params;
      
        // ์ค‘์š”: setRequestLocale์€ next-intl์— ์ด ์š”์ฒญ์— ์‚ฌ์šฉํ•  ๋กœ์ผ€์ผ์„ ์•Œ๋ ค์ค๋‹ˆ๋‹ค
        // ์ด๊ฒƒ์ด ์—†์œผ๋ฉด getTranslations()๊ฐ€ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•  ๋กœ์ผ€์ผ์„ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค
        setRequestLocale(locale);
      
        // ์˜ฌ๋ฐ”๋ฅธ HTML ๋ Œ๋”๋ง์„ ์œ„ํ•ด ํ…์ŠคํŠธ ๋ฐฉํ–ฅ(LTR/RTL)์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค
        const dir = getLocaleDirection(locale);
      
        return (
          <html lang={locale} dir={dir}>
            <body>{children}</body>
          </html>
        );
      }
      }
      
      src/app/[locale]/about/page.tsx
      import { getTranslations, getMessages, getFormatter } from "next-intl/server";
      import { NextIntlClientProvider } from "next-intl";
      import pick from "lodash/pick";
      import ServerComponent from "@/components/ServerComponent";
      import ClientComponent from "@/components/ClientComponent";
      
      export default async function AboutPage({
        params,
      }: {
        params: Promise<{ locale: string }>;
      }) {
        const { locale } = await params;
      
        // ๋ฉ”์‹œ์ง€๋Š” ์„œ๋ฒ„ ์ธก์—์„œ ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์—๋Š” ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ์ „๋‹ฌํ•˜์„ธ์š”.
        // ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๋กœ ์ „์†ก๋˜๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ฒˆ๋“ค์ด ์ตœ์†Œํ™”๋ฉ๋‹ˆ๋‹ค.
        const messages = await getMessages();
        const clientMessages = pick(messages, ["common", "about"]);
      
        // ์—„๊ฒฉํžˆ ์„œ๋ฒ„ ์ธก์—์„œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๋ฒˆ์—ญ/ํฌ๋งทํŒ…
        // ์ด๋“ค์€ ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜๋ฉฐ ์ปดํฌ๋„ŒํŠธ์— props๋กœ ์ „๋‹ฌ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
        const tAbout = await getTranslations("about");
        const tCounter = await getTranslations("about.counter");
        const format = await getFormatter();
      
        const initialFormattedCount = format.number(0);
      
        return (
          // NextIntlClientProvider๋Š” ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฒˆ์—ญ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค
          // ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋งŒ ์ „๋‹ฌํ•˜์„ธ์š”
          <NextIntlClientProvider locale={locale} messages={clientMessages}>
            <main>
              <h1>{tAbout("title")}</h1>
              <ClientComponent />
              <ServerComponent
                formattedCount={initialFormattedCount}
                label={tCounter("label")}
                increment={tCounter("increment")}
              />
            </main>
          </NextIntlClientProvider>
        );
      }
      
    4. ๋ฒˆ์—ญ ํŒŒ์ผ ์ƒ์„ฑํ•˜๊ธฐ

      ๊ฐ ๋กœ์ผ€์ผ๊ณผ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ณ„๋กœ JSON ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”. ์ด ๊ตฌ์กฐ๋Š” ๋ฒˆ์—ญ์„ ๋…ผ๋ฆฌ์ ์œผ๋กœ ๊ตฌ์„ฑํ•˜๊ณ  ๊ฐ ํŽ˜์ด์ง€์— ํ•„์š”ํ•œ ๋ฒˆ์—ญ๋งŒ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

      ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ณ„๋กœ ๋ฒˆ์—ญ์„ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ(e.g., common.json, about.json)์€ ์ฝ”๋“œ ๋ถ„ํ• ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋ฉฐ ๋ฒˆ๋“ค ํฌ๊ธฐ๋ฅผ ์ค„์—ฌ์ค๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ ํŽ˜์ด์ง€์— ํ•„์š”ํ•œ ๋ฒˆ์—ญ๋งŒ ๋กœ๋“œํ•˜์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      locales/en/common.json
      {
        "welcome": "Welcome",
        "greeting": "Hello, world!"
      }
      
      locales/fr/common.json
      {
        "welcome": "Bienvenue",
        "greeting": "Bonjour le monde!"
      }
      
      locales/en/about.json
      {
        "title": "About",
        "description": "About page description",
        "counter": {
          "label": "Counter",
          "increment": "Increment"
        }
      }
      
      locales/fr/about.json
      {
        "title": "ร€ propos",
        "description": "Description de la page ร€ propos",
        "counter": {
          "label": "Compteur",
          "increment": "Incrรฉmenter"
        }
      }
      
    5. ํŽ˜์ด์ง€์—์„œ ๋ฒˆ์—ญ ํ™œ์šฉํ•˜๊ธฐ

      ์„œ๋ฒ„์—์„œ ๋ฒˆ์—ญ์„ ๋กœ๋“œํ•˜๊ณ  ์ด๋ฅผ ์„œ๋ฒ„ ๋ฐ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๋ชจ๋‘์— ์ „๋‹ฌํ•˜๋Š” ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“œ์„ธ์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ Œ๋”๋ง ์ „์— ๋ฒˆ์—ญ์ด ๋กœ๋“œ๋˜์–ด ์ฝ˜ํ…์ธ  ๊นœ๋ฐ•์ž„์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      ์„œ๋ฒ„ ์ธก์—์„œ ๋ฒˆ์—ญ์„ ๋กœ๋“œํ•˜๋ฉด SEO๊ฐ€ ํ–ฅ์ƒ๋˜๊ณ  FOUC(๋ฒˆ์—ญ๋˜์ง€ ์•Š์€ ์ฝ˜ํ…์ธ  ๊นœ๋ฐ•์ž„)๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. pick์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋งŒ ํด๋ผ์ด์–ธํŠธ ํ”„๋กœ๋ฐ”์ด๋”์— ์ „๋‹ฌํ•จ์œผ๋กœ์จ ๋ธŒ๋ผ์šฐ์ €์— ์ „์†ก๋˜๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ฒˆ๋“ค ํฌ๊ธฐ๋ฅผ ์ตœ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค.

      src/app/[locale]/about/page.tsx
      import { getTranslations, getMessages, getFormatter } from "next-intl/server";
      import { NextIntlClientProvider } from "next-intl";
      import pick from "lodash/pick";
      import ServerComponent from "@/components/ServerComponent";
      import ClientComponent from "@/components/ClientComponent";
      
      export default async function AboutPage({
        params,
      }: {
        params: Promise<{ locale: string }>;
      }) {
        const { locale } = await params;
      
        // ๋ฉ”์‹œ์ง€๋Š” ์„œ๋ฒ„ ์ธก์—์„œ ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์—๋Š” ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ์ „๋‹ฌํ•˜์„ธ์š”.
        // ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๋กœ ์ „์†ก๋˜๋Š” JavaScript ๋ฒˆ๋“ค์ด ์ตœ์†Œํ™”๋ฉ๋‹ˆ๋‹ค.
        const messages = await getMessages();
        const clientMessages = pick(messages, ["common", "about"]);
      
        // ์—„๊ฒฉํžˆ ์„œ๋ฒ„ ์ธก ๋ฒˆ์—ญ/ํฌ๋งทํŒ…
        // ์ด๋“ค์€ ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜๋ฉฐ ์ปดํฌ๋„ŒํŠธ์— props๋กœ ์ „๋‹ฌ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
        const tAbout = await getTranslations("about");
        const tCounter = await getTranslations("about.counter");
        const format = await getFormatter();
      
        const initialFormattedCount = format.number(0);
      
        return (
          // NextIntlClientProvider๋Š” ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฒˆ์—ญ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
          // ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋งŒ ์ „๋‹ฌํ•˜์„ธ์š”.
          <NextIntlClientProvider locale={locale} messages={clientMessages}>
            <main>
              <h1>{tAbout("title")}</h1>
              <ClientComponent />
              <ServerComponent
                formattedCount={initialFormattedCount}
                label={tCounter("label")}
                increment={tCounter("increment")}
              />
            </main>
          </NextIntlClientProvider>
        );
      }
      
    6. ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฒˆ์—ญ ์‚ฌ์šฉํ•˜๊ธฐ

      ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋Š” useTranslations ๋ฐ useFormatter ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฒˆ์—ญ ๋ฐ ํฌ๋งทํŒ… ๊ธฐ๋Šฅ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ›…๋“ค์€ NextIntlClientProvider ์ปจํ…์ŠคํŠธ์—์„œ ๊ฐ’์„ ์ฝ์–ด์˜ต๋‹ˆ๋‹ค.

      ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฒˆ์—ญ์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด React ํ›…์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. useTranslations์™€ useFormatter ํ›…์€ next-intl๊ณผ ์›ํ™œํ•˜๊ฒŒ ํ†ตํ•ฉ๋˜๋ฉฐ, ๋กœ์ผ€์ผ์ด ๋ณ€๊ฒฝ๋  ๋•Œ ๋ฐ˜์‘ํ˜• ์—…๋ฐ์ดํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

      ํŽ˜์ด์ง€์˜ ํด๋ผ์ด์–ธํŠธ ๋ฉ”์‹œ์ง€์— ํ•„์š”ํ•œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ์„ธ์š” (ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์‹ค์ œ๋กœ ํ•„์š”๋กœ ํ•˜๋Š” ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋งŒ ํฌํ•จํ•˜์„ธ์š”).
      src/components/ClientComponent.tsx
      "use client";
      
      import React, { useState } from "react";
      import { useTranslations, useFormatter } from "next-intl";
      
      const ClientComponent = () => {
        // ์ค‘์ฒฉ๋œ ๊ฐ์ฒด์— ์ง์ ‘ ๋ฒ”์œ„๋ฅผ ์ง€์ •
        // useTranslations/useFormatter๋Š” NextIntlClientProvider ์ปจํ…์ŠคํŠธ์—์„œ ์ฝ๋Š” ํ›…์ž…๋‹ˆ๋‹ค.
        // ์ด ํ›…๋“ค์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ NextIntlClientProvider๋กœ ๊ฐ์‹ธ์ ธ ์žˆ์„ ๋•Œ๋งŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
        const t = useTranslations("about.counter");
        const format = useFormatter();
        const [count, setCount] = useState(0);
      
        return (
          <div>
            <p>{format.number(count)}</p>
            <button
              aria-label={t("label")}
              onClick={() => setCount((count) => count + 1)}
            >
              {t("increment")}
            </button>
          </div>
        );
      };
      
    7. ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฒˆ์—ญ ์‚ฌ์šฉํ•˜๊ธฐ

      ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋Š” React ํ›…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋กœ๋ถ€ํ„ฐ props๋ฅผ ํ†ตํ•ด ๋ฒˆ์—ญ๊ณผ ํฌ๋งคํ„ฐ๋ฅผ ์ „๋‹ฌ๋ฐ›์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋™๊ธฐ์ ์œผ๋กœ ์œ ์ง€ํ•˜๋ฉฐ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์— ์ค‘์ฒฉ๋  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

      ํด๋ผ์ด์–ธํŠธ ๊ฒฝ๊ณ„ ๋‚ด์— ์ค‘์ฒฉ๋  ์ˆ˜ ์žˆ๋Š” ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋Š” ๋™๊ธฐ์ ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฒˆ์—ญ๋œ ๋ฌธ์ž์—ด๊ณผ ํฌ๋งท๋œ ๊ฐ’์„ props๋กœ ์ „๋‹ฌํ•จ์œผ๋กœ์จ ๋น„๋™๊ธฐ ์ž‘์—…์„ ํ”ผํ•˜๊ณ  ์˜ฌ๋ฐ”๋ฅธ ๋ Œ๋”๋ง์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ๋ถ€๋ชจ ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฒˆ์—ญ๊ณผ ํฌ๋งท์„ ๋ฏธ๋ฆฌ ๊ณ„์‚ฐํ•˜์„ธ์š”.

      src/components/ServerComponent.tsx
      // ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๋‚ด์— ์ค‘์ฒฉ๋œ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋Š” ๋™๊ธฐ์ ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
      // React๋Š” ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ๊ฒฝ๊ณ„ ๊ฐ„์— ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ์ง๋ ฌํ™”ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค
      // ํ•ด๊ฒฐ์ฑ…: ๋ถ€๋ชจ์—์„œ ๋ฒˆ์—ญ/ํฌ๋งท์„ ๋ฏธ๋ฆฌ ๊ณ„์‚ฐํ•˜๊ณ  props๋กœ ์ „๋‹ฌ
      type ServerComponentProps = {
        formattedCount: string; // ํฌ๋งท๋œ ์นด์šดํŠธ ๋ฌธ์ž์—ด
        label: string; // ๋ฒ„ํŠผ ๋ผ๋ฒจ
        increment: string; // ์ฆ๊ฐ€ ํ…์ŠคํŠธ
      };
      
      const ServerComponent = ({
        formattedCount,
        label,
        increment,
      }: ServerComponentProps) => {
        return (
          <div>
            <p>{formattedCount}</p>
            <button aria-label={label}>{increment}</button>
          </div>
        );
      };
      
      ํŽ˜์ด์ง€๋‚˜ ๋ ˆ์ด์•„์›ƒ์—์„œ next-intl/server์˜ getTranslations์™€ getFormatter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฒˆ์—ญ๊ณผ ํฌ๋งทํŒ…์„ ๋ฏธ๋ฆฌ ๊ณ„์‚ฐํ•œ ํ›„, ์ด๋ฅผ props๋กœ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•˜์„ธ์š”.
    8. ์ฝ˜ํ…์ธ ์˜ ์–ธ์–ด ๋ณ€๊ฒฝํ•˜๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

      next-intl์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ˜ํ…์ธ ์˜ ์–ธ์–ด๋ฅผ ๋ณ€๊ฒฝํ•˜๋ ค๋ฉด, ๋™์ผํ•œ ๊ฒฝ๋กœ๋ช…์„ ๊ฐ€๋ฆฌํ‚ค๋ฉด์„œ ๋กœ์ผ€์ผ์„ ์ „ํ™˜ํ•˜๋Š” ๋กœ์ผ€์ผ ์ธ์‹ ๋งํฌ๋ฅผ ๋ Œ๋”๋งํ•˜์„ธ์š”. ํ”„๋กœ๋ฐ”์ด๋”๊ฐ€ URL์„ ์ž๋™์œผ๋กœ ์žฌ์ž‘์„ฑํ•˜๋ฏ€๋กœ ํ˜„์žฌ ๊ฒฝ๋กœ๋งŒ ์ง€์ •ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

      src/components/LocaleSwitcher.tsx
      "use client";
      
      import Link from "next/link";
      import { usePathname } from "next/navigation";
      import { useLocale } from "next-intl";
      import { defaultLocale, getCookie, type Locale, locales } from "@/i18n";
      
      const getLocaleLabel = (locale: Locale): string => {
        try {
          const displayNames = new Intl.DisplayNames([locale], { type: "language" });
          return displayNames.of(locale) ?? locale.toUpperCase();
        } catch {
          return locale.toUpperCase();
        }
      };
      
      const localeFlags: Record<Locale, string> = {
        en: "๐Ÿ‡ฌ๐Ÿ‡ง",
        fr: "๐Ÿ‡ซ๐Ÿ‡ท",
        es: "๐Ÿ‡ช๐Ÿ‡ธ",
      };
      
      export default function LocaleSwitcher() {
        const activeLocale = useLocale();
        const pathname = usePathname();
      
        // ๊ฒฝ๋กœ์—์„œ ๋กœ์ผ€์ผ ์ ‘๋‘์‚ฌ๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ ๊ธฐ๋ณธ ๊ฒฝ๋กœ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค
        const getBasePath = (path: string) => {
          for (const locale of locales) {
            if (path.startsWith(`/${locale}`)) {
              return path.slice(locale.length + 1) || "/";
            }
          }
          return path;
        };
      
        const basePath = getBasePath(pathname);
      
        return (
          <nav aria-label="์–ธ์–ด ์„ ํƒ๊ธฐ">
            <div>
              {(locales as readonly Locale[]).map((locale) => {
                const isActive = locale === activeLocale;
                // ๊ธฐ๋ณธ ๋กœ์ผ€์ผ์ธ์ง€ ์—ฌ๋ถ€์— ๋”ฐ๋ผ href๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
                const href =
                  locale === defaultLocale ? basePath : `/${locale}${basePath}`;
                return (
                  <Link
                    key={locale}
                    href={href}
                    aria-current={isActive ? "page" : undefined}
                    onClick={() => {
                      document.cookie = getCookie(locale);
                    }}
                  >
                    <span>{localeFlags[locale]}</span>
                    <span>{getLocaleLabel(locale)}</span>
                    <span>{locale.toUpperCase()}</span>
                  </Link>
                );
              })}
            </div>
          </nav>
        );
      }
      
    9. ์„ ํƒ์‚ฌํ•ญ

      next-intl์€ ํ™œ์„ฑ ๋กœ์ผ€์ผ์„ ์ž๋™์œผ๋กœ ์ ์šฉํ•˜๋Š” ํ˜„์ง€ํ™”๋œ ๋งํฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํฌํ•จํ•˜๋Š” ์„œ๋ธŒํŒจํ‚ค์ง€ next-intl/navigation์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ด๋ฏธ @/i18n ํŒŒ์ผ์—์„œ ์ด๋ฅผ ์ถ”์ถœํ•ด ๋‘์—ˆ์œผ๋ฏ€๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

      src/components/MyComponent.tsx
      import { Link } from "@/i18n";
      
      return <Link href="/about">t("about.title")</Link>;
      
    10. ์„œ๋ฒ„ ์•ก์…˜ ๋‚ด์—์„œ ํ™œ์„ฑ ๋กœ์ผ€์ผ ์ ‘๊ทผํ•˜๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

      ์„œ๋ฒ„ ์•ก์…˜์€ next-intl/server๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ˜„์žฌ ๋กœ์ผ€์ผ์„ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํ˜„์ง€ํ™”๋œ ์ด๋ฉ”์ผ์„ ๋ณด๋‚ด๊ฑฐ๋‚˜ ์ œ์ถœ๋œ ๋ฐ์ดํ„ฐ์™€ ํ•จ๊ป˜ ์–ธ์–ด ์„ ํ˜ธ๋„๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

      src/app/actions/get-current-locale.ts
      "use server";
      
      import { getLocale } from "next-intl/server";
      
      export async function getCurrentLocale() {
        return getLocale();
      }
      
      export async function handleContactForm(formData: FormData) {
        const locale = await getCurrentLocale();
      
        // ํ…œํ”Œ๋ฆฟ, ๋ถ„์„ ๋ ˆ์ด๋ธ” ๋“ฑ์„ ์„ ํƒํ•˜๊ธฐ ์œ„ํ•ด locale์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
        console.log(`locale ${locale}์—์„œ ๋ฐ›์€ ์—ฐ๋ฝ์ฒ˜ ์–‘์‹`);
      }
      
      getLocale๋Š” next-intl ํ”„๋ก์‹œ๊ฐ€ ์„ค์ •ํ•œ locale์„ ์ฝ๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„ ์–ด๋””์„œ๋‚˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค: Route Handlers, Server Actions, ๊ทธ๋ฆฌ๊ณ  edge functions.
    11. ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ตญ์ œํ™”ํ•˜๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

      ์ฝ˜ํ…์ธ  ๋ฒˆ์—ญ๋„ ์ค‘์š”ํ•˜์ง€๋งŒ, ๊ตญ์ œํ™”์˜ ์ฃผ์š” ๋ชฉํ‘œ๋Š” ์›น์‚ฌ์ดํŠธ๋ฅผ ์ „ ์„ธ๊ณ„์— ๋” ์ž˜ ๋ณด์ด๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. I18n์€ ์ ์ ˆํ•œ SEO๋ฅผ ํ†ตํ•ด ์›น์‚ฌ์ดํŠธ ๊ฐ€์‹œ์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ๋†€๋ผ์šด ์ˆ˜๋‹จ์ž…๋‹ˆ๋‹ค.

      ์ ์ ˆํ•˜๊ฒŒ ๊ตญ์ œํ™”๋œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋Š” ๊ฒ€์ƒ‰ ์—”์ง„์ด ํŽ˜์ด์ง€์—์„œ ์–ด๋–ค ์–ธ์–ด๊ฐ€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ์ง€ ์ดํ•ดํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” hreflang ๋ฉ”ํƒ€ ํƒœ๊ทธ ์„ค์ •, ์ œ๋ชฉ๊ณผ ์„ค๋ช… ๋ฒˆ์—ญ, ๊ฐ ๋กœ์ผ€์ผ์— ๋Œ€ํ•ด ์ •๊ทœํ™”๋œ URL์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์ž‘์—…์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

      src/app/[locale]/about/layout.tsx
      import type { Metadata } from "next";
      import { locales, defaultLocale, localizedPath } from "@/i18n";
      import { getTranslations } from "next-intl/server";
      
      // generateMetadata๋Š” ๊ฐ ๋กœ์ผ€์ผ๋งˆ๋‹ค ์‹คํ–‰๋˜์–ด SEO ์นœํ™”์ ์ธ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
      // ์ด๊ฒƒ์€ ๊ฒ€์ƒ‰ ์—”์ง„์ด ๋Œ€์ฒด ์–ธ์–ด ๋ฒ„์ „์„ ์ดํ•ดํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค.
      export async function generateMetadata({
        params,
      }: {
        params: { locale: string };
      }): Promise<Metadata> {
        const { locale } = params;
        const t = await getTranslations({ locale, namespace: "about" });
      
        const url = "/about";
        const languages = Object.fromEntries(
          locales.map((locale) => [locale, localizedPath(locale, url)])
        );
      
        return {
          title: t("title"),
          description: t("description"),
          alternates: {
            canonical: localizedPath(locale, url),
            languages: { ...languages, "x-default": url },
          },
        };
      }
      
      // ... ํŽ˜์ด์ง€ ๋‚˜๋จธ์ง€ ์ฝ”๋“œ
      
    12. ์‚ฌ์ดํŠธ๋งต ๊ตญ์ œํ™”ํ•˜๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

      ๋ชจ๋“  ๋กœ์ผ€์ผ ๋ฒ„์ „์˜ ํŽ˜์ด์ง€๋ฅผ ํฌํ•จํ•˜๋Š” ์‚ฌ์ดํŠธ๋งต์„ ์ƒ์„ฑํ•˜์„ธ์š”. ์ด๋Š” ๊ฒ€์ƒ‰ ์—”์ง„์ด ๋ชจ๋“  ์–ธ์–ด ๋ฒ„์ „์˜ ์ฝ˜ํ…์ธ ๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ณ  ์ƒ‰์ธํ™”ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

      ์ ์ ˆํ•˜๊ฒŒ ๊ตญ์ œํ™”๋œ ์‚ฌ์ดํŠธ๋งต์€ ๊ฒ€์ƒ‰ ์—”์ง„์ด ๋ชจ๋“  ์–ธ์–ด ๋ฒ„์ „์˜ ํŽ˜์ด์ง€๋ฅผ ์ฐพ๊ณ  ์ƒ‰์ธํ™”ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๊ตญ์ œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์—์„œ ๊ฐ€์‹œ์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

      src/app/sitemap.ts
      import type { MetadataRoute } from "next";
      import { defaultLocale, locales } from "@/i18n";
      
      const origin = "https://example.com";
      
      const formatterLocalizedPath = (locale: string, path: string) =>
        locale === defaultLocale ? `${origin}${path}` : `${origin}/${locale}${path}`;
      
      /**
       * ๋ชจ๋“  ๋กœ์ผ€์ผ๊ณผ ํ•ด๋‹น ๋กœ์ปฌ๋ผ์ด์ฆˆ๋œ ๊ฒฝ๋กœ์˜ ๋งต์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
       *
       * ์˜ˆ์‹œ ์ถœ๋ ฅ:
       * {
       *   "en": "https://example.com",
       *   "fr": "https://example.com/fr",
       *   "es": "https://example.com/es",
       *   "x-default": "https://example.com"
       * }
       */
      const getLocalizedMap = (path: string) =>
        Object.fromEntries([
          ...locales.map((locale) => [locale, formatterLocalizedPath(locale, path)]),
          ["x-default", formatterLocalizedPath(defaultLocale, path)],
        ]);
      
      // ๋” ๋‚˜์€ SEO๋ฅผ ์œ„ํ•ด ๋ชจ๋“  ๋กœ์ผ€์ผ ๋ณ€ํ˜•์„ ํฌํ•จํ•˜๋Š” ์‚ฌ์ดํŠธ๋งต ์ƒ์„ฑ
      // alternates ํ•„๋“œ๋Š” ๊ฒ€์ƒ‰ ์—”์ง„์— ์–ธ์–ด ๋ฒ„์ „์„ ์•Œ๋ฆฝ๋‹ˆ๋‹ค
      export default function sitemap(): MetadataRoute.Sitemap {
        return [
          {
            url: formatterLocalizedPath(defaultLocale, "/"),
            lastModified: new Date(),
            changeFrequency: "monthly",
            priority: 1.0,
            alternates: { languages: getLocalizedMap("/") },
          },
          {
            url: formatterLocalizedPath(defaultLocale, "/about"),
            lastModified: new Date(),
            changeFrequency: "monthly",
            priority: 0.7,
            alternates: { languages: getLocalizedMap("/about") },
          },
        ];
      }
      
    13. robots.txt ๊ตญ์ œํ™”ํ•˜๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

      ๋ณดํ˜ธ๋œ ๊ฒฝ๋กœ์˜ ๋ชจ๋“  ๋กœ์ผ€์ผ ๋ฒ„์ „์„ ์ ์ ˆํžˆ ์ฒ˜๋ฆฌํ•˜๋Š” robots.txt ํŒŒ์ผ์„ ๋งŒ๋“œ์„ธ์š”. ์ด๋ฅผ ํ†ตํ•ด ๊ฒ€์ƒ‰ ์—”์ง„์ด ์–ด๋–ค ์–ธ์–ด๋กœ๋“  ๊ด€๋ฆฌ์ž(admin)๋‚˜ ๋Œ€์‹œ๋ณด๋“œ ํŽ˜์ด์ง€๋ฅผ ์ธ๋ฑ์‹ฑํ•˜์ง€ ์•Š๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      ๋ชจ๋“  ๋กœ์ผ€์ผ์— ๋Œ€ํ•ด robots.txt๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌ์„ฑํ•˜๋ฉด, ๊ฐ ๋กœ์ผ€์ผ๋ณ„๋กœ ๊ฒฝ๋กœ๊ฐ€ ๋‹ค๋ฅผ ๋•Œ ๋ฏผ๊ฐํ•œ ํŽ˜์ด์ง€๊ฐ€ ๊ฒ€์ƒ‰ ์—”์ง„์— ์ธ๋ฑ์‹ฑ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      src/app/robots.ts
      import type { MetadataRoute } from "next";
      import { locales, defaultLocale } from "@/i18n";
      
      const origin = "https://example.com";
      // ๋ชจ๋“  ๋กœ์ผ€์ผ์— ๋Œ€ํ•œ ๊ฒฝ๋กœ ์ƒ์„ฑ (์˜ˆ: /admin, /fr/admin, /es/admin)
      const withAllLocales = (path: string) => [
        path,
        ...locales
          .filter((locale) => locale !== defaultLocale)
          .map((locale) => "/" + locale + path),
      ];
      
      export default function robots(): MetadataRoute.Robots {
        const disallow = [
          ...withAllLocales("/dashboard"),
          ...withAllLocales("/admin"),
        ];
      
        return {
          rules: { userAgent: "*", allow: ["/"], disallow },
          host: origin,
          sitemap: origin + "/sitemap.xml",
        };
      }
      
    14. ๋กœ์ผ€์ผ ๋ผ์šฐํŒ…์„ ์œ„ํ•œ ํ”„๋ก์‹œ ์„ค์ •

      ์„ ํƒ์‚ฌํ•ญ

      ์‚ฌ์šฉ์ž์˜ ์„ ํ˜ธ ๋กœ์ผ€์ผ์„ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ณ  ์ ์ ˆํ•œ ๋กœ์ผ€์ผ ์ ‘๋‘์‚ฌ๊ฐ€ ๋ถ™์€ URL๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•˜๋Š” ํ”„๋ก์‹œ๋ฅผ ๋งŒ๋“œ์„ธ์š”. next-intl์€ ์ด๋ฅผ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ํŽธ๋ฆฌํ•œ ํ”„๋ก์‹œ ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

      ํ”„๋ก์‹œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ดํŠธ๋ฅผ ๋ฐฉ๋ฌธํ•  ๋•Œ ์ž๋™์œผ๋กœ ์„ ํ˜ธํ•˜๋Š” ์–ธ์–ด๋กœ ๋ฆฌ๋””๋ ‰์…˜๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์‚ฌ์šฉ์ž์˜ ์„ ํ˜ธ๋„๋ฅผ ์ €์žฅํ•˜์—ฌ ํ–ฅํ›„ ๋ฐฉ๋ฌธ ์‹œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

      src/proxy.ts
      import { proxy } from "@/i18n";
      
      // ๋ฏธ๋“ค์›จ์–ด๋Š” ๋ผ์šฐํŠธ ์ด์ „์— ์‹คํ–‰๋˜์–ด ๋กœ์ผ€์ผ ๊ฐ์ง€ ๋ฐ ๋ผ์šฐํŒ…์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
      // localeDetection: true๋Š” Accept-Language ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ์ผ€์ผ์„ ์ž๋™ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค.
      export default proxy;
      
      export const config = {
        // API, Next ๋‚ด๋ถ€, ์ •์  ์ž์‚ฐ์€ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.
        // ์ •๊ทœ์‹: api, _next๋กœ ์‹œ์ž‘ํ•˜๊ฑฐ๋‚˜ ์ (.)์ด ํฌํ•จ๋œ ๊ฒฝ๋กœ(ํŒŒ์ผ)๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ๊ฒฝ๋กœ์™€ ๋งค์นญ๋ฉ๋‹ˆ๋‹ค.
        matcher: ["/((?!api|_next|.*\\..*).*)"],
      };
      
    15. ๋กœ์ผ€์ผ์— ๋Œ€ํ•œ TypeScript ํƒ€์ž… ์„ค์ •

      ์„ ํƒ์‚ฌํ•ญ

      TypeScript ์„ค์ •์€ ํ‚ค์— ๋Œ€ํ•œ ์ž๋™์™„์„ฑ๊ณผ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ์ œ๊ณตํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

      ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— global.ts ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

      global.ts
      import type { locales } from "@/i18n";
      
      type Messages = {
        common: typeof import("./locales/en/common.json");
        home: typeof import("./locales/en/home.json");
        about: typeof import("./locales/en/about.json");
        // ... ํ–ฅํ›„ JSON ํŒŒ์ผ๋„ ์—ฌ๊ธฐ์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
      };
      
      declare module "next-intl" {
        interface AppConfig {
          Locale: (typeof locales)[number];
          Messages: Messages;
        }
      }
      

      ์ด ์ฝ”๋“œ๋Š” ๋ชจ๋“ˆ ํ™•์žฅ(Module Augmentation)์„ ์‚ฌ์šฉํ•˜์—ฌ locales์™€ messages๋ฅผ next-intl์˜ AppConfig ํƒ€์ž…์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

    16. Intlayer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฒˆ์—ญ ์ž๋™ํ™”ํ•˜๊ธฐ

      ์„ ํƒ์‚ฌํ•ญ

      Intlayer๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ˜„์ง€ํ™” ๊ณผ์ •์„ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด ์„ค๊ณ„๋œ ๋ฌด๋ฃŒ์ด์ž ์˜คํ”ˆ ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. next-intl์ด ๋ฒˆ์—ญ ๋กœ๋”ฉ๊ณผ ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ๋ฐ˜๋ฉด, Intlayer๋Š” ๋ฒˆ์—ญ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์ž๋™ํ™”ํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค.

      ๋ฒˆ์—ญ์„ ์ˆ˜๋™์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์€ ์‹œ๊ฐ„ ์†Œ๋ชจ๊ฐ€ ํฌ๊ณ  ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. Intlayer๋Š” ๋ฒˆ์—ญ ํ…Œ์ŠคํŠธ, ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ๋ฅผ ์ž๋™ํ™”ํ•˜์—ฌ ์‹œ๊ฐ„์„ ์ ˆ์•ฝํ•˜๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „๋ฐ˜์— ๊ฑธ์ณ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

      Intlayer๋Š” ๋‹ค์Œ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค:

      • ์ฝ”๋“œ๋ฒ ์ด์Šค ๋‚ด ์›ํ•˜๋Š” ์œ„์น˜์— ์ฝ˜ํ…์ธ  ์„ ์–ธํ•˜๊ธฐ
        Intlayer๋Š” .content.{ts|js|json} ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ๋ฒ ์ด์Šค ๋‚ด ์›ํ•˜๋Š” ์œ„์น˜์— ์ฝ˜ํ…์ธ ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ฝ˜ํ…์ธ ๋ฅผ ๋” ์ž˜ ์กฐ์งํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ฝ”๋“œ๋ฒ ์ด์Šค์˜ ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

      • ๋ˆ„๋ฝ๋œ ๋ฒˆ์—ญ ํ…Œ์ŠคํŠธํ•˜๊ธฐ
        Intlayer๋Š” CI/CD ํŒŒ์ดํ”„๋ผ์ธ์ด๋‚˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์— ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ๋Š” ํ…Œ์ŠคํŠธ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ฒˆ์—ญ ํ…Œ์ŠคํŠธ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”.

      • ๋ฒˆ์—ญ ์ž๋™ํ™” Intlayer๋Š” ๋ฒˆ์—ญ์„ ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ๋Š” CLI์™€ VSCode ํ™•์žฅ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” CI/CD ํŒŒ์ดํ”„๋ผ์ธ์— ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฒˆ์—ญ ์ž๋™ํ™”์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”. ์‚ฌ์šฉ์ž๋Š” ์ž์‹ ์˜ API ํ‚ค์™€ ์›ํ•˜๋Š” AI ์ œ๊ณต์ž๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ƒํ™ฉ์— ๋งž๋Š” ๋ฒˆ์—ญ์„ ์ œ๊ณตํ•˜๋ฏ€๋กœ, ๋‚ด์šฉ ์ฑ„์šฐ๊ธฐ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

      • ์™ธ๋ถ€ ์ฝ˜ํ…์ธ  ์—ฐ๊ฒฐ Intlayer๋Š” ์™ธ๋ถ€ ์ฝ˜ํ…์ธ  ๊ด€๋ฆฌ ์‹œ์Šคํ…œ(CMS)์— ์ฝ˜ํ…์ธ ๋ฅผ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์ตœ์ ํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ๊ฐ€์ ธ์™€ JSON ๋ฆฌ์†Œ์Šค์— ์‚ฝ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์™ธ๋ถ€ ์ฝ˜ํ…์ธ  ๊ฐ€์ ธ์˜ค๊ธฐ์—์„œ ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”.

      • ๋น„์ฃผ์–ผ ์—๋””ํ„ฐ
        Intlayer๋Š” ๋น„์ฃผ์–ผ ์—๋””ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ˜ํ…์ธ ๋ฅผ ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌด๋ฃŒ ๋น„์ฃผ์–ผ ์—๋””ํ„ฐ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ฒˆ์—ญ ๋น„์ฃผ์–ผ ํŽธ์ง‘์—์„œ ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”.

      ๊ทธ๋ฆฌ๊ณ  ๋” ๋งŽ์€ ๊ธฐ๋Šฅ๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค. Intlayer๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ํ™•์ธํ•˜๋ ค๋ฉด Intlayer์˜ ๊ด€์‹ฌ์‚ฌ ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

    ๋Œ“๊ธ€

    ์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ๋กœ ์˜๊ฒฌ์„ ๋‚˜๋ˆ ๋ณด์„ธ์š”.

    ๊ด€๋ จ ๊ฒŒ์‹œ๋ฌผ

    ์ตœ๊ทผ ๊ฒŒ์‹œ๋ฌผ