W
🌐

I Thought Next.js i18n Would Be Easy. I Was Wrong.

2026-06-24

7 min read · 1223 words

When my website was Chinese-only, life was pretty simple.

I could add new pages, publish blog posts, and build project pages without thinking too much about routing or localization.

Then I decided to add an English version.

At the time, I thought internationalization was mostly a translation problem.

Create another language file.

Add a language switcher.

Done.

At least that's what I thought.

Once I actually started building it, I realized the hard part wasn't translation at all.

It was architecture.

Phase 1

Used regular routes and handled language switching inside components.

Phase 2

Stored locale in React state and considered it "internationalization".

Phase 3
Rebuilt the routing structure and migrated to next-intl.
Today

Locale is part of the URL, making SEO and maintenance much easier.

If you think your website might support multiple languages one day, plan the URL structure from the beginning. Refactoring it later is far more expensive than setting it up correctly on day one.

🧭 I Underestimated Internationalization

My requirements were actually pretty simple:

  • Support Chinese and English
  • Have separate language versions of blog content
  • Keep the same page structure
  • Stay SEO-friendly

Nothing sounded particularly difficult.

My original plan was straightforward.

Prepare two translation files and switch between them based on the selected language.

Simple enough.

But once development started, I realized I was focusing on the wrong thing.

I was thinking about text.

I should have been thinking about URLs.

💥 Mistake #1: My Routing Structure Was Wrong From the Start

Originally, my blog routes looked like this:

/blog/[slug]

The page content changed depending on the current locale.

At first, everything seemed fine.

Pages rendered correctly.

Language switching worked.

Nothing appeared broken.

But as the website grew, a few problems started showing up:

URLs couldn't distinguish between languages Search engines couldn't identify language-specific pages Locale logic spread across multiple components Maintenance became increasingly difficult

Looking back, I fell into a common trap.

I assumed that because everything worked, the architecture was correct.

It wasn't.

Just because something works doesn't mean it's built to scale.

Some problems don't appear when your website has three pages.

They appear when it has thirty.

And by then, fixing them becomes much more expensive.

💥 Mistake #2: I Wasn't Really Doing Internationalization

At one point, I tried what felt like the quickest solution.

Store the current language in React state.

When the user clicks a button, update the locale and re-render the content.

The logic was roughly:

Save locale in state Switch locale on click Render translated content

It worked surprisingly well.

For a while.

Then I started noticing the downsides:

URLs never changed Shared links couldn't preserve language Language state could be lost after refresh SEO gained almost nothing

That was the moment everything finally clicked.

Internationalization isn't about switching text. It's about structuring routes.

My First Approach

  • State-based locale
  • Same URL
  • Language state can be lost
  • Poor SEO

What I Use Now

  • Route-based locale
  • Dedicated URLs
  • Persistent language state
  • SEO-friendly

🧩 The Setup I Ended Up With

After trying several different approaches, I eventually moved to next-intl.

The biggest reason was simple.

It feels like it belongs in the App Router ecosystem.

My current structure looks something like this:

app

app/
└── [locale]/
├── layout.jsx
├── page.jsx
├── blog/
│   └── [slug]/
└── projects/

Language is now part of the URL itself:

/zh/blog/nextjs-intl-i18n

/en/blog/nextjs-intl-i18n

Once I made that change, a lot of complexity disappeared almost immediately.

Many of the problems I had been fighting were suddenly gone.

⚙️ Why I Chose next-intl

1️⃣ Locale Comes Directly From the Route

I no longer need to manually manage language state.

The URL already tells me which language I'm viewing.

Simple.

Predictable.

Easy to maintain.

2️⃣ It Fits App Router Naturally

A lot of older internationalization solutions feel like they were adapted to App Router later.

next-intl feels much more aligned with modern Next.js.

Most of its APIs are designed around the App Router experience from the start.

3️⃣ Cleaner Developer Experience

Before, I found myself importing translation files everywhere.

Now it's usually as simple as:

const t = useTranslations("home");
return (
<h1>{t("title")}</h1>
);

Cleaner components.

Less boilerplate.

Less mental overhead.

4️⃣ Better SEO Structure

Separate URLs for each language bring several benefits:

Easier indexing Clear language separation More accurate sharing links Easier hreflang support in the future

🔨 How the Refactor Actually Happened

The migration wasn't a single big rewrite.

It happened gradually.

I tried several approaches along the way, and honestly, I threw away more than one solution before finding something that felt right.

The Beginning

The website only had a Chinese version, and every page used standard routes like /blog/[slug].

First Attempt

To get language switching working quickly, I used React state to store the current locale and dynamically render translated content.

Problems Started Appearing

As more pages were added, language-specific URLs, SEO, and long-term maintainability became harder to manage.

Research

I compared several options, including Next.js internationalization solutions and next-intl, before deciding to redesign the architecture.

Refactor

Locale moved into the routing layer through the app/[locale] structure, and I updated navigation, blogs, and project pages accordingly.

Today

The site runs on App Router and next-intl, with locale-aware URLs across the entire website.

🌍 Why Internationalization Affects SEO

When developers first think about internationalization, translation is usually the first thing that comes to mind.

Search engines care about different things.

Questions like:

  • Does each language have its own URL?
  • Is the page language clearly defined?
  • Is there duplicate content?
  • Are hreflang tags configured correctly?

If all language versions share the same URL and only the text changes, search engines may still treat them as the same page.

That was one of the biggest reasons I eventually abandoned the state-based approach.

I wasn't just trying to improve the user experience.

I was trying to create a structure that search engines could actually understand.

🧠 What I Learned From This

After finishing the migration, one idea kept coming back to me.

Internationalization is an architecture problem before it's a translation problem.

Translation files are relatively easy.

The difficult part is designing a structure that remains maintainable as the project grows.

Routing.

Content organization.

SEO.

Those decisions have a much bigger impact than the translation itself.

Looking back, most of the time I spent wasn't spent translating content.

It was spent fixing architectural decisions that seemed reasonable at the beginning.

✨ Final Thoughts

My current setup isn't perfect.

But it solves the problems I care about right now:

  • Clear routing
  • Better SEO
  • Reasonable maintenance costs
  • Room for future growth

For a personal website, that's more than enough.

There are still a few things I'd like to improve in the future:

  • Multilingual blog synchronization
  • Automatic metadata generation
  • hreflang support
  • Better MDX content management

If I were starting over today, I would plan the internationalization strategy on day one instead of waiting until the website was almost finished.

The refactor ended up taking far longer than configuring next-intl itself.