Command Palette

Search for a command to run...

Настройка определения страны

В этом руководстве вы узнаете, как витрина подбирает регион продаж для нового посетителя, как включить определение по IP через ip-api и как добавить собственный провайдер определения.

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

Как выбирается провайдер

считывает активный провайдер из переменной окружения и переключается по нему:

src/lib/geolocation/detect.ts
1export const resolveGeolocationProvider = (): GeolocationProvider => {
2 switch (true) {
3 case isIpApi(geolocationProvider):
4 return ipApiProvider
5 default:
6 return platformProvider
7 }
8}

читает , а сверяет значение со строкой , оба определены в том же файле. Любое другое значение, включая незаданную переменную, проваливается в , который читает geo-заголовки, уже выставленные вашим хостингом.

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

src/middleware.ts
const applyCountry = await resolveCountry(request, regionMap)

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

Внимание: 

В задано , но не является кодом страны, поэтому ни один регион из seed-данных под него не подходит. Остаётся последнее звено цепочки, первый регион из ответа бэкенда, а это произвольная страна. Укажите реальный код ISO 3166-1 alpha-2 в нижнем регистре, например или , чтобы при неудачном определении посетитель попадал туда, куда вы решили.

Использование geo-заголовков хостинга

Провайдер по умолчанию не требует настройки и не делает сетевых запросов. Он читает страну, которую ваша платформа уже определила, в таком порядке:

ИсточникПлатформа
Cloudflare Workers
Vercel
Cloudflare
Google App Engine
Fastly
, nginx GeoIP или собственный прокси

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

На Vercel и Cloudflare этого достаточно. Заголовок бесплатен, приходит мгновенно и точнее любого сторонного сервиса, поэтому там оставьте незаданной.

Включение провайдера ip-api

Развёртываниям без geo-заголовков, а также локальной разработке, нужен настоящий поиск по IP. Задайте провайдер в :

apps/storefront/.env.local
1GEOLOCATION_PROVIDER=ip-api
2GEOLOCATION_PROVIDER_API_KEY=

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

Провайдер сначала проверяет заголовки хостинга и обращается к ip-api только если ни один из них не принёс страну, поэтому на Vercel его включение ничего не стоит. Удачный поиск по IP посетителя кэшируется на час, а одновременные запросы по одному IP склеиваются в один вызов. Провайдер также соблюдает лимиты ip-api: он читает заголовки ответа и и перестаёт обращаться к сервису на секунд, когда лимит исчерпан или пришёл ответ . Именно это не даёт получить бан на час при высокой нагрузке.

Внимание: 

ip-api работает и без ключа, но его бесплатный эндпоинт доступен только по HTTP, разрешает 45 запросов в минуту, а условия использования запрещают коммерческое применение, приводя в качестве примера недопустимого «местную валюту или ближайший магазин для посетителей интернет-магазина». Магазин делает с результатом ровно это, поэтому боевому магазину нужен платный ключ. С заданным ключом провайдер обращается к по HTTPS, и больше ничего менять не нужно.

Как адрес посетителя определяет способ поиска

читает сначала , затем , , , и , и приводит найденное к каноническому виду: убирает порт, убирает скобки IPv6 и разворачивает IPv4-mapped адрес IPv6, так что распознаётся как петлевой адрес . От того, что получилось, зависит одно из трёх поведений:

Адрес клиентаПоведение
Публичный адресПоиск по собственному IP посетителя, кэш на час, таймаут 1,5 с
Петлевой или из приватного диапазонаПосетитель находится в сети сервера, поэтому ip-api вызывается без IP и определяет исходящий адрес самого сервера. Без кэша, таймаут 5 с
Ни в одном заголовке адреса нетСтрана не определяется, работает цепочка резервных вариантов

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

Последняя строка намеренно не делает предположений. Определение по исходящему адресу сервера для посетителя с неизвестным IP привязало бы всех таких посетителей к стране датацентра. Если развёртывание не определяет страну совсем, значит стоящий перед ним прокси не передаёт .

Проверка того, как определился регион

Задайте , и middleware начнёт добавлять к каждому ответу то, что он решил. Флаг работает в любом режиме, включая production-сборку:

apps/storefront/.env.local
GEOLOCATION_DEBUG=true
ЗаголовокЗначение
, или одна из причин
Что вернул провайдер, если ничего не определил
, или , когда ответила cookie
Адрес посетителя, или
Страна, которая применилась

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

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

Добавление собственного провайдера

Каждый провайдер реализует один и тот же контракт из двух полей:

src/lib/geolocation/types.ts
1export type GeolocationContext = {
2 headers: Headers
3 cf?: { country?: string | null }
4}
5
6export type GeolocationProvider = {
7 name: string
8 lookup: (context: GeolocationContext) => Promise<string | null>
9}

возвращает код ISO 3166-1 alpha-2 в нижнем регистре или , если ничего не определил. Поле нужно не для вида: оно попадает в ключ кэша, поэтому два провайдера никогда не читают результаты друг друга.

Чтобы добавить провайдер для другого сервиса или для локальной базы вроде MaxMind GeoLite2:

  1. Создайте файл в , который экспортирует . Вызовите в нём сначала из , если хотите оставить приоритет за бесплатным заголовком, и оберните сетевой запрос в из , чтобы получить часовой кэш и склейку одновременных запросов.
  2. Добавьте для него в переключателе выше и небольшой хелпер рядом с .
  3. Укажите в тот id, который проверяют ваш и хелпер.

Ориентироваться лучше на : это более короткий пример, 50 строк вообще без сетевых запросов. А показывает, что добавляет настоящий поиск: таймаут, соблюдение лимитов и ветвление по адресу клиента.

Материалы

Изменено 26 августа 2026 г.·Редактировать страницу