Documentation
Documentation
Introduction

Getting Started

Getting StartedInstallationQuick StartProject Structure

Architecture

Architecture OverviewTech StacktRPC MiddlewareDesign Principles

Patterns

Code Patterns & ConventionsFeature ModulesError HandlingType Safety

Database

DatabaseSchema DefinitionDatabase OperationsMigrationsCaching

API

tRPCProceduresRouterstRPC Proxy Setup
APIsOpenAPIREST Endpoints

Auth & Access

AuthenticationConfigurationOAuth ProvidersRolesSession Management
AuthorizationUser RolesPermissions

Routing & i18n

RoutingDeclarative RoutingNavigation
InternationalizationTranslationsLocale Routing

Components & UI

ComponentsButtonsFormsNavigationDialogs
StylesTailwind CSSThemingTypography

Storage

StorageConfigurationUsageBuckets

Configuration

ConfigurationEnvironment VariablesFeature Flags

Templates

Template GuidesCreate New FeatureCreate New PageCreate Database TableCreate tRPC RouterAdd Translations

Development

DevelopmentCommandsAI AgentsBest Practices

Locale Routing

URL-based locale detection

The project uses locale-based routing with automatic locale detection and localized navigation utilities.

URL Structure

All routes are prefixed with the locale:

/en/about
/it/about
/en/dashboard
/it/dashboard

The locale segment is automatically handled by the middleware.

Automatic Locale Detection

The middleware detects the user's locale from multiple sources (in order of priority):

  1. URL path segment (highest priority)
  2. Cookie (NEXT_LOCALE)
  3. Accept-Language header
  4. Default locale (fallback)

Navigation Utilities

All navigation utilities from @/i18n/navigation automatically handle locale prefixing.

The locale parameter is optional in all navigation functions. The current locale is automatically used from context.

Link Component

import { Link } from "@/i18n/navigation";
import { PageAbout } from "@/routes";

<Link href={PageAbout()}>About</Link>
// Renders: /en/about (or /{locale}/about)

// With declarative route
<PageAbout.Link>About</PageAbout.Link>

Client-Side Navigation

"use client";

import { useRouter } from "@/i18n/navigation";
import { PageDashboard, PageUserId } from "@/routes";

function MyComponent() {
  const router = useRouter();

  const handleNavigate = () => {
    // Navigate to a route - locale is automatically applied
    router.push(PageDashboard()); // → /en/dashboard

    // With params
    router.push(PageUserId({ id: "123" }));

    // Replace instead of push
    router.replace(PageHome());

    // Explicit locale (rarely needed)
    router.push(PageDashboard({ locale: "it" })); // → /it/dashboard
  };

  return <Button onClick={handleNavigate}>Go to Dashboard</Button>;
}

Server-Side Redirects

import { redirect } from "@/i18n/navigation";
import { PageLogin } from "@/routes";
import { getAuth } from "@/lib/auth/server";

export default async function ProtectedPage() {
  const auth = await getAuth();

  if (!auth) {
    redirect(PageLogin()); // Locale is automatically applied
  }

  return <div>Protected content</div>;
}

Important

The Next.js redirect() function only works in:

  • Server Components
  • Server Actions

It does NOT work in:

  • tRPC procedures (return redirect URL instead)
  • Middleware (use NextResponse.redirect())
  • API Routes (use NextResponse.redirect())

In tRPC Procedures

Return the redirect URL in the response and handle it client-side:

// Server: tRPC procedure
login: publicProcedure
  .input(loginSchema)
  .mutation(async ({ ctx, input }) => {
    const result = await signIn(input);

    return {
      success: true,
      payload: {
        redirectUrl: PageDashboard(),
      },
    };
  }),

// Client: Handle redirect
const mutation = api.auth.login.useMutation({
  onSuccess: (data) => {
    if (data.payload?.redirectUrl) {
      router.push(data.payload.redirectUrl);
    }
  },
});

usePathname Hook

import { usePathname } from "@/i18n/navigation";
import { usePathnameWithoutLocale } from "@/lib/utils/navigation-utils";

const pathname = usePathname();
// URL: /en/dashboard → pathname: /en/dashboard

const pathname = usePathnameWithoutLocale();
// URL: /en/dashboard → pathname: /dashboard

Check Active Route

import { navUtils } from "@/lib/utils/navigation-utils";

const pathname = usePathname();
const isActive = navUtils.pathMatches(pathname, PageDashboard);

Locale Switching

Locale Switcher Component

import { LocaleSwitcher } from "@/components/locale-switcher";

<LocaleSwitcher />

This renders a dropdown with all configured locales.

Programmatic Locale Switch

"use client";

import { useRouter, usePathname } from "@/i18n/navigation";

function SwitchLocale() {
  const router = useRouter();
  const pathname = usePathname();

  const switchToItalian = () => {
    router.push(pathname, { locale: "it" });
  };

  return <Button onClick={switchToItalian}>Switch to Italian</Button>;
}

Configuration

Supported Locales

Configure locales in src/i18n/config.tsx:

export const locales = ["en", "it"] as const;
export const defaultLocale = "en";

Locale Cookie

The locale is stored in a cookie for persistence:

// src/config/app.ts
Config.Navigation.LOCALE_COOKIE_KEY = "NEXT_LOCALE";

Adding a New Locale

Step 1: Update config

Add the locale to src/i18n/config.tsx

Step 2: Create translation files

cp -r src/messages/dictionaries/en src/messages/dictionaries/es

Step 3: Translate content

Update all JSON files in the new locale folder

Step 4: Add Zod errors

cp src/messages/zod-errors/en.json src/messages/zod-errors/es.json

Step 5: Test routing

Navigate to /es/* routes to verify

Middleware Configuration

The middleware handles locale detection and routing in middleware.ts:

import createMiddleware from "next-intl/middleware";
import { routing } from "@/i18n/routing";

export default createMiddleware(routing);

export const config = {
  matcher: [
    "/((?!api|_next|_vercel|.*\\..*).*)",
    "/",
  ],
};

SEO & Metadata

Localized Metadata

import { getLocalizedMetadata } from "@/i18n/seo";

export async function generateMetadata({ params }: Props) {
  const { locale } = await params;

  return getLocalizedMetadata({
    locale,
    namespace: "pageAbout",
    titleKey: "meta.title",
    descriptionKey: "meta.description",
  });
}

Alternate Links

The middleware automatically adds alternate locale links for SEO:

<link rel="alternate" hreflang="en" href="/en/about" />
<link rel="alternate" hreflang="it" href="/it/about" />

Best Practices

  1. Use declarative routing - Always use route functions like PageHome() instead of hardcoded paths
  2. Let middleware handle locale - Don't manually add locale prefixes to URLs
  3. Handle redirects correctly - Use the right redirect method based on context (server component vs tRPC)
  4. Test all locales - Verify navigation works correctly in all supported locales

Always use the localized navigation utilities from @/i18n/navigation instead of Next.js built-in navigation.

On this page

URL Structure
Automatic Locale Detection
Navigation Utilities
Link Component
Client-Side Navigation
Server-Side Redirects
In tRPC Procedures
usePathname Hook
Check Active Route
Locale Switching
Locale Switcher Component
Programmatic Locale Switch
Configuration
Supported Locales
Locale Cookie
Adding a New Locale
Step 1: Update config
Step 2: Create translation files
Step 3: Translate content
Step 4: Add Zod errors
Step 5: Test routing
Middleware Configuration
SEO & Metadata
Localized Metadata
Alternate Links
Best Practices