PreviousNext

Next.js 15 Internationalization (i18n) Complete Guide

Learn how to build multilingual websites in Next.js 15 without changing URLs. Master automatic language detection, cookies, and next-intl for 2026.

Next.js 15 Internationalization (i18n) Without Locale Routing

Complete Guide to Implementing Multi-Language Support Using next-intl


Table of Contents

  1. Overview
  2. Prerequisites
  3. Installation
  4. Configuration (Next.js 15)
  5. Implementation
  6. Browser Language Detection
  7. User Preference Management
  8. Complete Code Examples

Overview

This documentation demonstrates how to implement multi-language support in a Next.js 15 application without changing the URL structure. Unlike traditional implementations (e.g., /en/page), this approach maintains clean URLs while providing full translation capabilities.

Key Features 2026

  • Clean URLs: No /en prefix.
  • Async Metadata: Fully compatible with Next.js 15 server components.
  • Cookie Persistence: Remembers user choice.

Prerequisites

  • Next.js 15.0+ (App Router)
  • React 19
  • next-intl

Installation

pnpm add next-intl

Configuration

Step 1: next.config.ts

import type { NextConfig } from "next";
const createNextIntlPlugin = require("next-intl/plugin");
const withNextIntl = createNextIntlPlugin();
 
const nextConfig: NextConfig = {};
 
export default withNextIntl(nextConfig);

Step 2: i18n/request.ts (New for Next.js 15)

In Next.js 15, request cookies are async. You must await them.

import { getRequestConfig } from "next-intl/server";
import { cookies } from "next/headers";
 
export default getRequestConfig(async () => {
  // Await cookies in Next.js 15
  const cookieStore = await cookies();
  const cookieLocale = cookieStore.get("NEXT_LOCALE")?.value;
  const locale = cookieLocale || "en";
 
  return {
    locale,
    messages: (await import(`../messages/${locale}.json`)).default,
  };
});

Creating Translation Files

Create a messages folder in root.

messages/en.json

{
  "HomePage": {
    "title": "Welcome to Desishub",
    "subtitle": "Building for 2026"
  }
}

messages/fr.json

{
  "HomePage": {
    "title": "Bienvenue à Desishub",
    "subtitle": "Construire pour 2026"
  }
}

Implementation

Layout Setup (app/layout.tsx)

import { NextIntlClientProvider } from "next-intl";
import { getMessages, getLocale } from "next-intl/server";
 
export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const locale = await getLocale();
  const messages = await getMessages();
 
  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

Using Translations (app/page.tsx)

import { useTranslations } from "next-intl";
 
export default function Home() {
  const t = useTranslations("HomePage");
 
  return (
    <main>
      <h1>{t("title")}</h1>
      <p>{t("subtitle")}</p>
    </main>
  );
}

Browser Language Detection

We use a simple Client Component LanguageSwitcher to detect the browser language on first load and set a cookie.

"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
 
export default function LanguageSwitcher() {
  const router = useRouter();
 
  useEffect(() => {
    // Check if cookie exists
    const hasCookie = document.cookie.includes("NEXT_LOCALE");
    if (!hasCookie) {
        // Detect and set
        const browserLang = navigator.language.slice(0, 2);
        document.cookie = `NEXT_LOCALE=${browserLang}; path=/`;
        router.refresh();
    }
  }, []);
 
  return null; // Logic only
}

Summary

This setup is ideal for SAAS platforms and Dashboards where you want to keep the URL clean (e.g., app.desishub.com/dashboard) but still offer localized content based on user preference.

Want to Learn More?

Check out our full Next.js Course where we build a multi-language E-commerce store from scratch.