navigator.onLineはtrueでもオンライン状態とは限らない

navigator.onLineはtrueでもオンライン状態とは限らない

navigator.onLineは「ブラウザがネットワークインターフェースに接続されているか」を示すだけで、実際に目的のサーバーに到達できるかは保証しません

MDNのnavigator.onLineの説明文にも「true値は必ずインターネットにアクセスできると考えることはできません。仮想イーサネットアダプタを持つ仮想化ソフトウェアを実行しているコンピューターでは常に「接続中」になるなど、誤検出になる可能性があります。」と記載されています。

Navigator: onLine プロパティ - Web API | MDN

単独で使うと誤判定(接続あり扱いでも実際にはAPIに繋がらない等)が起き、ユーザーに誤ったUXを与える可能性があります。

実務ではnavigator.onLineをトリガーにして、fetchで実際の到達性チェックと組み合わせて使うのが正解です。

実際の到達性チェックと組み合わせた関数

実際の到達性チェックと組み合わせた関数は以下のようなコードになります。

setTimeoutとcontroller.abort()を併用して、3秒以内に接続できなければ「不通」とみなして接続を切断します。

JavaScript
async function isActuallyOnline(checkUrl = location.href, timeout = 3000) {
  if (!navigator.onLine) return false
  
  const controller = new AbortController()
  const id = setTimeout(() => controller.abort(), timeout)

  try {
    const res = await fetch(checkUrl, { method: 'HEAD', signal: controller.signal })
    return res.ok
  } catch (e) {
    return false
  } finally {
    clearTimeout(id)
  }
}

console.log(await isActuallyOnline())
// インターネットにアクセスできなければfalse

毎秒確認したい場合はsetIntervalを使用します。

JavaScript
async function checkOnline() {
  console.log(await isActuallyOnline())
}

setInterval(checkOnline, 1000)

Reactの場合はこのようになります。

App.tsx
import { useEffect, useState } from 'react'

async function isActuallyOnline(checkUrl = location.href, timeout = 3000) {
  if (!navigator.onLine) return false

  const controller = new AbortController()
  const id = setTimeout(() => controller.abort(), timeout)

  try {
    const res = await fetch(checkUrl, { method: 'HEAD', signal: controller.signal })
    return res.ok
  } catch {
    return false
  } finally {
    clearTimeout(id)
  }
}

export default function App() {
  const [online, setOnline] = useState<boolean | null>(null)

  useEffect(() => {
    const check = async () => {
      const result = await isActuallyOnline()
      setOnline(result)
    }
    check()
    const intervalId = setInterval(check, 1000)
    
    return () => clearInterval(intervalId)
  }, [])

  return (
    <div>
      <h1>Reactのオンライン判定サンプル</h1>
      {online === null && <p>チェック中...</p>}
      {online === true && <p>✅ オンラインです</p>}
      {online === false && <p>❌ オフラインです</p>}
    </div>
  )
}

Reactのオンライン判定サンプル