lkomar.pl
← Wszystkie notatki
Next.jsnext-intli18ndark modeTailwind

Adding i18n and dark mode to an already-shipped Next.js app

Retrofitting next-intl and a theme toggle onto a site that was already live, without a rewrite.


title: Adding i18n and dark mode to an already-shipped Next.js app date: 2026-09 description: Retrofitting next-intl and a theme toggle onto a site that was already live, without a rewrite. tags: [Next.js, next-intl, i18n, dark mode, Tailwind]

This site started as a plain, English-only App Router page. Adding Polish and a dark mode toggle later meant retrofitting both without breaking what was already working.

Routing comes first

next-intl wants locale-aware routing, which means every route moves under an app/[locale]/ segment. That's the part that actually touches the file structure: app/page.tsx becomes app/[locale]/page.tsx, and a middleware.ts handles locale detection and redirects.

The trade-off worth knowing up front: once routes live under [locale], every internal link needs to go through the library's Link wrapper (from @/i18n/navigation here) instead of next/link, so the current locale carries through. Easy to forget once, in one component, and end up with a link that silently drops you back to the default locale.

Splitting UI copy from content

The translation files (messages/en.json, messages/pl.json) hold UI copy: nav labels, button text, section headings. They don't hold content that's inherently tied to one language, like this note. That split keeps the JSON files small and means adding a new note never touches the translation files at all.

Dark mode is mostly a CSS variable problem

The theme toggle itself is a small client component (next-themes handles the class-on-html + localStorage persistence). The actual work was making sure every color in the design was a CSS variable with a light and dark value, rather than a hardcoded Tailwind color, so flipping the dark class was enough to restyle the whole page. Anywhere I'd used a raw bg-white or text-gray-900 needed a second pass.

What I'd do differently

I'd set up next-intl's routing structure from day one, even for a single-language site. Moving routes under [locale] after the fact touches every page file, every internal link, and the metadata/sitemap generation. None of it is hard, but it's all mechanical work that a from-the-start setup avoids entirely.