Как запомнить выбор языка между сессиями в Next.js (Pages Router) v16

Сохраняйте явный выбор языка пользователя

Проблема

Когда пользователь явно выбирает язык, это отражает его предпочтения и должно иметь приоритет над автоматическим определением по заголовкам браузера или геолокации. Без сохранения этот выбор теряется при закрытии браузера или завершении сессии. При следующем визите приложение запускается заново, и пользователю снова приходится выбирать язык. Такое повторение показывает, что приложение не уважает предпочтения пользователя, создаёт неудобства и снижает доверие.

Задача состоит из двух частей: зафиксировать явный выбор пользователя в момент выбора и получить этот выбор при последующих посещениях до запуска любой автоматической логики определения языка. Если сохранённое предпочтение не проверяется на раннем этапе обработки запроса, пользователя могут перенаправить на основе настроек браузера, а не его явного выбора, что сводит на нет саму идею выбора.

Решение

Сохраняйте выбранный пользователем язык в постоянной cookie, когда он явно выбирает язык. При следующих визитах на главную страницу приложения проверяйте наличие этой cookie до попытки определить язык на основе настроек браузера. Если найдена корректная сохранённая локаль, перенаправьте пользователя на корневой путь этой локали, чтобы его предпочтение сразу учитывалось.

Такой подход разделяет явный выбор пользователя и автоматическое определение. Cookie выступает в роли устойчивого сигнала намерения, который сохраняется даже после перезапуска браузера и имеет приоритет над временными сигналами, такими как заголовок Accept-Language. Проверяя cookie на сервере при первом запросе, вы обеспечиваете перенаправление до рендеринга страницы, что создаёт бесшовный пользовательский опыт.

Шаги

Когда пользователь выбирает язык, сохраняйте его выбор в cookie, чтобы он сохранялся между сессиями.

export function setLocalePreference(locale: string) {
  const maxAge = 60 * 60 * 24 * 365;
  document.cookie = `NEXT_LOCALE=${locale}; path=/; max-age=${maxAge}; SameSite=Lax`;
}

Эта функция записывает cookie с именем NEXT_LOCALE и выбранной локалью, срок действия — один год. path=/ гарантирует доступность cookie во всём приложении, а SameSite=Lax обеспечивает разумную защиту от CSRF и позволяет отправлять cookie при навигации на верхнем уровне.

2. Вызывайте helper при выборе языка пользователем

Внедрите helper в компонент переключения языка, чтобы предпочтение сохранялось сразу после выбора.

import { useRouter } from "next/router";
import { setLocalePreference } from "@/lib/locale";

export default function LanguageSwitcher() {
  const router = useRouter();
  const { locales, locale: currentLocale } = router;

  const handleLocaleChange = (newLocale: string) => {
    setLocalePreference(newLocale);
    router.push(router.pathname, router.asPath, { locale: newLocale });
  };

  return (
    <select
      value={currentLocale}
      onChange={(e) => handleLocaleChange(e.target.value)}
    >
      {locales?.map((loc) => (
        <option key={loc} value={loc}>
          {loc.toUpperCase()}
        </option>
      ))}
    </select>
  );
}

Когда пользователь меняет выбор, cookie устанавливается, и роутер переходит на ту же страницу на новом языке. Cookie будет доступен во всех последующих запросах.

3. Проверьте сохранённые предпочтения на корневой странице

В getServerSideProps корневой страницы прочитайте cookie и выполните редирект на сохранённую локаль, если она существует и валидна.

import { GetServerSideProps } from "next";

export const getServerSideProps: GetServerSideProps = async (context) => {
  const storedLocale = context.req.cookies.NEXT_LOCALE;
  const { locales, defaultLocale } = context;

  if (
    storedLocale &&
    locales?.includes(storedLocale) &&
    storedLocale !== defaultLocale
  ) {
    return {
      redirect: {
        destination: `/${storedLocale}`,
        permanent: false,
      },
    };
  }

  return {
    redirect: {
      destination: `/${defaultLocale}`,
      permanent: false,
    },
  };
};

export default function RootPage() {
  return null;
}

Этот код проверяет, существует ли cookie NEXT_LOCALE и содержит ли она допустимую локаль из списка, настроенного в приложении. Если сохранённая локаль не является дефолтной, пользователь будет перенаправлен на корень этой локали. В противном случае — на дефолтную локаль. Редирект происходит на сервере до рендера, чтобы пользователь сразу попадал на нужную локаль.

4. Настройте маршрутизацию локалей в Next.js

Убедитесь, что в next.config.js указаны поддерживаемые локали, чтобы логика редиректа могла валидировать сохранённые предпочтения.

module.exports = {
  i18n: {
    locales: ["en", "fr", "de", "es"],
    defaultLocale: "en",
  },
};

Эта конфигурация включает встроенную маршрутизацию i18n в Next.js и делает locales и defaultLocale доступными в getServerSideProps. Логика корневой страницы использует эти значения для проверки сохранённой cookie и построения правильного адреса для перенаправления.