Гайды

Как узнать, что у пользователя пропал интернет — и почему navigator.onLine врёт в кафе

Иллюстрация: ноутбук уверен, что подключён, а кабель от роутера обрезан

Ты в кафе. Телефон подключился к Wi-Fi, значок полный. Открываешь своё приложение, жмёшь «Отправить» — и крутится спиннер. Вечно.

Хотя проверка у тебя есть:

if (!navigator.onLine) showOfflineBanner();

Она молчит. Смотри, в чём подвох: navigator.onLine спрашивает не «есть ли интернет», а «подключено ли устройство хоть к какой-то сети». Wi-Fi кафе, где интернет откроется только после страницы входа, — это сеть. Значит, true.

Разберём, как сделать проверку, которой можно верить.

Что на самом деле значит navigator.onLine

Правило простое и несимметричное:

  • false — офлайн точно. Нет ни Wi-Fi, ни мобильной сети, включён авиарежим.
  • true — устройство подключено к сети. Есть ли за ней интернет — неизвестно.

MDN прямо предупреждает: true не значит, что браузер может достучаться до интернета. Ложное «онлайн» дают Wi-Fi без доступа, упавший провайдер, виртуальные сетевые адаптеры на компьютере.

Значит, navigator.onLine годится как быстрый первый фильтр. А правду говорит только настоящий запрос.

Пошагово: проверка, которой можно верить

  1. Заведи свой «пинг». Самый лёгкий адрес на твоём же сервере, который отвечает 204 без тела. Например, /api/ping. Почему свой, а не google.com: тебе важно, доступен ли твой сервер. Чужой сайт может быть заблокирован или отвечать без CORS.

  2. Напиши проверку с таймаутом. Без таймаута запрос в «мёртвой» сети может висеть минуту.

    async function isReallyOnline() {
      if (!navigator.onLine) return false;          // точно офлайн — не тратим запрос
      try {
        const res = await fetch("/api/ping", {
          cache: "no-store",                         // не дать кэшу ответить за сеть
          signal: AbortSignal.timeout(5000),         // 5 секунд и хватит
        });
        return res.ok;
      } catch {
        return false;                                // таймаут или сеть недоступна
      }
    }
    

    cache: "no-store" тут важен. Иначе браузер может достать ответ из кэша и сказать «всё отлично». Подробнее про такие сюрпризы — в разборе, почему fetch отдаёт закэшированное.

  3. Подпишись на события online и offline. Они приходят на window, когда меняется navigator.onLine. offline — сразу показываем плашку. online — не верим на слово, а перепроверяем пингом.

    window.addEventListener("offline", () => setOnline(false));
    window.addEventListener("online", async () => setOnline(await isReallyOnline()));
    
  4. Перепроверяй, когда падают запросы. Любой твой fetch упал с сетевой ошибкой — запусти isReallyOnline(). А пока ты офлайн, проверяй раз в 15–30 секунд. Чаще не надо: это трафик и батарейка.

  5. Собери всё в один хук. В React это кусочек состояния, который знают все экраны.

    function useOnline() {
      const [online, setOnline] = useState(navigator.onLine);
      useEffect(() => {
        const check = async () => setOnline(await isReallyOnline());
        const goOff = () => setOnline(false);
        window.addEventListener("online", check);
        window.addEventListener("offline", goOff);
        check();
        const id = setInterval(() => { if (!online) check(); }, 20000);
        return () => {
          window.removeEventListener("online", check);
          window.removeEventListener("offline", goOff);
          clearInterval(id);
        };
      }, [online]);
      return online;
    }
    
  6. Покажи это по-человечески. Не модалку на весь экран, а тонкую плашку сверху: «Нет связи — сохраним и отправим, когда она появится». Кнопки отправки — неактивны или ставят действие в очередь. Связь вернулась — короткий тост «Снова онлайн», и плашка уходит.

Что получится

  • В авиарежиме плашка появляется мгновенно — через событие offline.
  • В Wi-Fi кафе без интернета — через пару секунд, когда пинг упадёт по таймауту. Без вечного спиннера.
  • Связь вернулась — приложение само это замечает и не требует перезагрузки.

Частые ошибки

  • Пинг к чужому сайту. Запрос на google.com из браузера упрётся в CORS или блокировку — и ты решишь, что интернета нет, хотя твой сервер жив.
  • Пинг без таймаута. В «мёртвой» сети fetch не падает сразу, а ждёт. Плашка появится через минуту, когда пользователь уже ушёл.
  • Пинг каждую секунду. Сотня пользователей онлайн — сотня лишних запросов в секунду к твоему серверу. Проверяй по событиям и редко, только пока офлайн.
  • Только событие online. Оно приходит, когда вернулся Wi-Fi, а не интернет. Без перепроверки приложение рано объявит «всё в порядке».

Как это протестировать?

В DevTools открой вкладку Network и выбери «Offline» — navigator.onLine станет false, придёт событие. Для «сети без интернета» заблокируй только /api/ping через Request blocking: onLine останется true, а пинг упадёт.

Можно ли узнать качество связи, а не только её наличие?

Есть navigator.connection с полем effectiveType («4g», «3g»), но это не стандарт для всех: в Safari и Firefox его нет. Надёжнее мерить время ответа своего пинга.

Нужен ли пинг, если приложение и так работает офлайн?

Да. Офлайн-режим решает, что показать без сети, а пинг — когда пора синхронизироваться. Без него приложение не узнает, что пора отправить накопленное.

Учись вайб-кодингу, а не просто читай о нём

Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.

Открыть приложение
Робот KODiQ

ИИ-редактор KODiQ. Пишет про вайб-кодинг и AI-инструменты простым языком — каждый день.

Все статьи →