How to add offline support in React

Offline support enables React applications to work without internet connectivity by caching resources and data locally. As the creator of CoreUI with 12 years of React development experience, I’ve built offline-first React applications serving millions of users globally.

The most reliable approach combines service workers for asset caching with local storage or IndexedDB for data persistence.

Setup Service Worker

First, ensure you have a service worker configured (see how to add service workers in React).

Update public/service-worker.js for offline caching:

import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'
import { registerRoute } from 'workbox-routing'
import { NetworkFirst, CacheFirst, StaleWhileRevalidate } from 'workbox-strategies'

precacheAndRoute(self.__WB_MANIFEST)

// Cache HTML pages
registerRoute(
  ({ request }) => request.mode === 'navigate',
  new NetworkFirst({
    cacheName: 'pages',
    networkTimeoutSeconds: 3
  })
)

// Cache API responses
registerRoute(
  ({ url }) => url.pathname.startsWith('/api/'),
  new NetworkFirst({
    cacheName: 'api-cache',
    networkTimeoutSeconds: 5
  })
)

// Cache static assets
registerRoute(
  ({ request }) => ['style', 'script', 'image'].includes(request.destination),
  new CacheFirst({
    cacheName: 'assets'
  })
)

Create Offline Hook

Create src/hooks/useOffline.js:

import { useState, useEffect } from 'react'

export const useOffline = () => {
  const [isOffline, setIsOffline] = useState(!navigator.onLine)

  useEffect(() => {
    const handleOnline = () => setIsOffline(false)
    const handleOffline = () => setIsOffline(true)

    window.addEventListener('online', handleOnline)
    window.addEventListener('offline', handleOffline)

    return () => {
      window.removeEventListener('online', handleOnline)
      window.removeEventListener('offline', handleOffline)
    }
  }, [])

  return isOffline
}

Create Offline Storage Service

Create src/services/offlineStorage.js:

const STORAGE_KEY = 'offline_data'

export const offlineStorage = {
  save: (key, data) => {
    const store = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}')
    store[key] = {
      data,
      timestamp: Date.now()
    }
    localStorage.setItem(STORAGE_KEY, JSON.stringify(store))
  },

  get: (key) => {
    const store = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}')
    return store[key]?.data || null
  },

  remove: (key) => {
    const store = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}')
    delete store[key]
    localStorage.setItem(STORAGE_KEY, JSON.stringify(store))
  },

  clear: () => {
    localStorage.removeItem(STORAGE_KEY)
  }
}

Create Offline-Aware Fetch

Create src/utils/offlineFetch.js:

import { offlineStorage } from '../services/offlineStorage'

export const offlineFetch = async (url, options = {}) => {
  try {
    const response = await fetch(url, options)
    const data = await response.json()

    offlineStorage.save(url, data)
    return data
  } catch (error) {
    if (!navigator.onLine) {
      const cachedData = offlineStorage.get(url)
      if (cachedData) {
        return cachedData
      }
    }
    throw error
  }
}

Use in Component

import React, { useState, useEffect } from 'react'
import { useOffline } from './hooks/useOffline'
import { offlineFetch } from './utils/offlineFetch'

function App() {
  const [data, setData] = useState([])
  const [error, setError] = useState(null)
  const isOffline = useOffline()

  useEffect(() => {
    const fetchData = async () => {
      try {
        const result = await offlineFetch('/api/data')
        setData(result)
      } catch (err) {
        setError('Failed to load data')
      }
    }

    fetchData()
  }, [])

  return (
    <div>
      {isOffline && (
        <div className="offline-banner">
          You are offline. Showing cached data.
        </div>
      )}

      {error && <div className="error">{error}</div>}

      <ul>
        {data.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  )
}

export default App

Best Practice Note

This is the same offline strategy we use in CoreUI’s React PWA templates. The combination of service workers for asset caching and local storage for data ensures your app works seamlessly offline. For larger datasets, consider using IndexedDB instead of localStorage.

For production applications, consider using CoreUI’s React Admin Template which includes pre-built offline support, background sync, and conflict resolution.

For complete PWA functionality, you might also want to learn how to add push notifications in React to re-engage users when they come back online.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author