Next.js usage

The Next adapter is where most of the routing-specific behaviour lives.

Path routing

pathRouting lives in the init() options you give the adapter.

ts
// src/lib/ways.ts
import { init as initWays } from '@18ways/next/server';
 
export const {
  waysMiddleware,
} = initWays({
  apiKey: 'YOUR_18WAYS_PUBLIC_API_KEY',
  baseLocale: 'en-GB',
  persistLocaleCookie: true,
  pathRouting: {
    exclude: ['/dashboard'],
  },
});

That tells the adapter which paths middleware should localize.

With that config in place:

  • /pricing can become /fr-FR/pricing
  • /dashboard stays /dashboard

The adapter also auto-excludes obvious non-page routes like /_next and /api.

Middleware flow

waysMiddleware() is the middleware API from init(). It:

  • resolves the locale from cookie, pathname, browser preference, or baseLocale
  • normalizes the incoming pathname if a locale prefix should be added or removed
  • creates the right NextResponse for redirects, rewrites, or passthrough requests
  • writes the locale preference cookie using the policy from init()
  • forwards the locale-aware request headers your app can read later

For the normal case, this is enough:

tsx
// src/middleware.ts
import type { NextRequest } from 'next/server';
import { waysMiddleware } from '@/lib/ways';
 
export default function middleware(
  request: NextRequest
) {
  return waysMiddleware(request);
}

When you do need to customize the middleware result, pass options explicitly:

tsx
return waysMiddleware(request, {
  /**
   * Defaults to noop.
   * Use this to add or replace
   * request headers before the
   * response is created.
   */
  transformRequestHeaders: (context) =>
    context.requestHeaders,
 
  /**
   * Defaults to noop.
   * Use this to decorate or replace
   * the final NextResponse.
   */
  transformResponse: (response) => response,
});

Reach for transformRequestHeaders or transformResponse only when you need extra behavior on top of the default middleware result.

Locale resolution order

The current adapter resolves locale in this order:

  1. 18ways_locale cookie
  2. locale prefix in the pathname
  3. browser preferences
  4. baseLocale

That order matters because it lets an explicit user preference override everything else.

Client-side locale changes

Use useLocale() from @18ways/next/client.

tsx
'use client';
 
import {
  LanguageSwitcher,
} from '@18ways/react';
import { useLocale } from '@18ways/next/client';
 
export function LocaleControls() {
  const { locale, setLocale } = useLocale();
 
  return (
    <LanguageSwitcher
      currentLocale={locale}
      onLocaleChange={setLocale}
    />
  );
}

Use useLocale() when you want your own controls. Use LanguageSwitcher when you want the ready-made selector wired to the same locale state.

When path routing is enabled, setLocale() updates the URL. When it is disabled, it updates the locale state and refreshes the route.

There are two useful client helpers:

tsx
import {
  useLocalizedHref,
  useUnlocalizedPathname,
} from '@18ways/next/client';
  • useLocalizedHref() builds locale-aware internal hrefs.
  • useUnlocalizedPathname() lets you compare routes without worrying about the current locale prefix.

Those links should follow the same rules your middleware enforces. If middleware is adding or removing locale prefixes for a route, useLocalizedHref() should be the helper you reach for on the client.

Translated metadata

generateWaysMetadata() can translate string metadata fields for you.

tsx
export async function generateMetadata() {
  return generateWaysMetadata((t) => ({
    title: t('Pricing'),
    description: t(
      'Simple pricing for runtime translation'
    ),
  }));
}

When path routing is enabled, the adapter also emits locale-specific canonical and alternate language URLs.

Middleware composition

The adapter does not assume it owns your whole middleware. waysMiddleware() can be called inside your auth layer, or customized with transformRequestHeaders and transformResponse.

What belongs in the Next track

Document behaviour here when it depends on:

  • middleware
  • pathname prefixes
  • translated metadata
  • SSR locale resolution

If the behaviour is just React rendering, it belongs in the React track instead.