Как узнать, что у пользователя пропал интернет — и почему 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 годится как быстрый первый фильтр. А правду говорит только настоящий запрос.
Пошагово: проверка, которой можно верить
-
Заведи свой «пинг». Самый лёгкий адрес на твоём же сервере, который отвечает
204без тела. Например,/api/ping. Почему свой, а не google.com: тебе важно, доступен ли твой сервер. Чужой сайт может быть заблокирован или отвечать без CORS. -
Напиши проверку с таймаутом. Без таймаута запрос в «мёртвой» сети может висеть минуту.
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 отдаёт закэшированное. -
Подпишись на события
onlineиoffline. Они приходят наwindow, когда меняетсяnavigator.onLine.offline— сразу показываем плашку.online— не верим на слово, а перепроверяем пингом.window.addEventListener("offline", () => setOnline(false)); window.addEventListener("online", async () => setOnline(await isReallyOnline())); -
Перепроверяй, когда падают запросы. Любой твой
fetchупал с сетевой ошибкой — запустиisReallyOnline(). А пока ты офлайн, проверяй раз в 15–30 секунд. Чаще не надо: это трафик и батарейка. -
Собери всё в один хук. В 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; } -
Покажи это по-человечески. Не модалку на весь экран, а тонкую плашку сверху: «Нет связи — сохраним и отправим, когда она появится». Кнопки отправки — неактивны или ставят действие в очередь. Связь вернулась — короткий тост «Снова онлайн», и плашка уходит.
Что получится
- В авиарежиме плашка появляется мгновенно — через событие
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 его нет. Надёжнее мерить время ответа своего пинга.
Нужен ли пинг, если приложение и так работает офлайн?
Да. Офлайн-режим решает, что показать без сети, а пинг — когда пора синхронизироваться. Без него приложение не узнает, что пора отправить накопленное.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.




