hafuture
Back to Blog

Hardcoding vs next-intl: Multilingual SEO Guide

Learn how to build SEO-optimized multilingual websites using next-intl library instead of hardcoding metadata in Next.js App Router.

Next.jsi18nnext-intlSEOTechnical

One of the most common mistakes when building multilingual websites is hardcoding metadata in a single language. Today, I'll share how we solved this problem elegantly using the next-intl library.

The Problem: Metadata Stuck in English

hafuture is a global service supporting Korean, English, and Japanese. While checking Google Search Console one day, we noticed something strange. Even when accessing the Korean page (/ko), the HTML <title> and <meta description> were displayed in English.

<!-- Why is /ko showing English? -->
<title>Free Online Tools for Text, PDF & Images | Hafuture</title>
<meta name="description" content="Fast, private, browser-based tools..." />

This clearly hurts SEO and can result in penalties for Korean search results.

Root Cause: Hardcoded generateMetadata

The problem was in the generateMetadata function in layout.tsx:

// ❌ Wrong approach: Hardcoding
export async function generateMetadata(): Promise<Metadata> {
  return {
    title: {
      default: "Free Online Tools for Text, PDF & Images | Hafuture",
      template: "%s | Hafuture",
    },
    description: "Fast, private, browser-based tools...",
  };
}

Even with next-intl configured, if you don't call translation functions in the metadata section, static English text will be output as-is.

Solution: Using getTranslations

next-intl provides the getTranslations function for server components. With this, you can dynamically generate translated metadata in generateMetadata.

Step 1: Add Metadata Section to Message Files

// messages/en.json
{
  "Metadata": {
    "title": "Free Online Tools for Text, PDF & Images | Hafuture",
    "description": "Fast, private, browser-based tools...",
    "ogTitle": "Free Online Tools for Text, PDF & Images | Hafuture"
  }
}

Step 2: Call getTranslations in generateMetadata

// ✅ Correct approach: Using next-intl
import { getTranslations } from "next-intl/server";

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

  return {
    title: {
      default: t("title"),
      template: t("titleTemplate"),
    },
    description: t("description"),
    openGraph: {
      title: t("ogTitle"),
      description: t("ogDescription"),
    },
  };
}

Hardcoding vs next-intl Comparison

AspectHardcoding (Custom)next-intl Library
Final HTML ResultVaries by skill levelPerfect results guaranteed
Implementation DifficultyHigh (server/client sync needed)Low (designed for Next.js)
Error ProbabilityHigh (metadata omission, etc.)Low (established patterns)
SEO ImpactUnstableConsistently high scores
MaintenanceDifficultEasy (just edit JSON files)

Verification

After the fix, checking the HTML for each language page shows:

Korean (/ko):

<title>텍스트, PDF, 이미지를 위한 무료 온라인 도구 | Hafuture</title>
<meta property="og:locale" content="ko_KR" />

Japanese (/ja):

<title>テキスト、PDF、画像のための無料オンラインツール | Hafuture</title>
<meta property="og:locale" content="ja_JP" />

Now search engines can accurately recognize the metadata for each language!

Try our Tools

Discover Hafuture's tools that are now smarter with multilingual support:

  • Text Tools: Text editing that works perfectly in every language.
  • Image Tools: Image resizing according to global standards.

Conclusion

If you're using Next.js App Router, I strongly recommend adopting next-intl to prevent SEO mistakes and simplify maintenance. Using getTranslations in generateMetadata is the cleanest way to solve the "title/description not translated" problem.

Multilingual support isn't just about changing text. It's the first step in respecting your users' language and culture!

Thank you. 🌏