React guide

Offline-First Search in React with altor-vec

Use altor-vec to add offline-first search to your React app — entirely in the browser, with no server, no API keys, and zero per-query cost. Build search that works without a network connection — cache the vector index in IndexedDB and serve search entirely from browser storage, enabling PWAs and offline-first apps to maintain full search capability offline.

Install: npm install altor-vec @xenova/transformers

Implementation

Works with Vite, CRA, or any React 18+ setup. Uses useState + useRef for the engine.

// useOfflineSearch.ts — IndexedDB-backed offline search in React
import { useState, useEffect, useRef } from 'react';
import init, { WasmSearchEngine } from 'altor-vec';

const DB_NAME = 'search-db', STORE = 'index';

async function saveToIDB(data: string) {
  return new Promise((res, rej) => {
    const req = indexedDB.open(DB_NAME, 1);
    req.onupgradeneeded = () => req.result.createObjectStore(STORE);
    req.onsuccess = () => {
      req.result.transaction(STORE, 'readwrite').objectStore(STORE).put(data, 'v1')
        .onsuccess = () => res();
    };
    req.onerror = () => rej(req.error);
  });
}

async function loadFromIDB(): Promise {
  return new Promise((res) => {
    const req = indexedDB.open(DB_NAME, 1);
    req.onupgradeneeded = () => req.result.createObjectStore(STORE);
    req.onsuccess = () => {
      const tx = req.result.transaction(STORE, 'readonly');
      const get = tx.objectStore(STORE).get('v1');
      get.onsuccess = () => res(get.result ?? null);
      get.onerror = () => res(null);
    };
    req.onerror = () => res(null);
  });
}

export function useOfflineSearch(indexUrl: string) {
  const engine = useRef(null);
  const [ready, setReady] = useState(false);
  const [offline, setOffline] = useState(!navigator.onLine);

  useEffect(() => {
    const on = () => setOffline(false);
    const off = () => setOffline(true);
    window.addEventListener('online', on);
    window.addEventListener('offline', off);
    return () => { window.removeEventListener('online', on); window.removeEventListener('offline', off); };
  }, []);

  useEffect(() => {
    (async () => {
      await init();
      // Try IndexedDB cache first
      const cached = await loadFromIDB();
      if (cached) {
        engine.current = WasmSearchEngine.from_json(cached);
        setReady(true);
        return;
      }
      // Fall back to network fetch
      if (!navigator.onLine) return; // Offline with no cache — can't search
      const resp = await fetch(indexUrl);
      const json = await resp.text();
      engine.current = WasmSearchEngine.from_json(json);
      await saveToIDB(json); // Cache for next time
      setReady(true);
    })();
  }, [indexUrl]);

  function search(queryEmbedding: Float32Array, k = 5) {
    if (!engine.current) return [];
    return JSON.parse(engine.current.search(queryEmbedding, k));
  }

  return { search, ready, offline };
}

Performance

Load from IndexedDB: ~50–200ms. Search: <1ms. Zero network dependency after first load. Measured on M2 MacBook Pro, Chrome 124. Mobile is typically 2–4× slower — test on target devices before deploying.

Index sizeDimensionsQuery p50Memory
1,000 vectors384~0.1ms~2MB
10,000 vectors384~0.4ms~17MB
50,000 vectors384~0.9ms~85MB

When this approach works best

Limitations

Frequently asked questions

How do I detect when the user is offline and fall back to cached search?

Use navigator.onLine and the 'online'/'offline' window events. When offline, load the index from IndexedDB. When online, optionally fetch a fresher index. In a service worker, cache the index file with a cache-first strategy.

What is the maximum index size I can store in IndexedDB?

IndexedDB storage limits are browser- and device-dependent: Chrome allows up to ~60% of available disk space, Firefox up to 50%. In practice, a 50K-document index at 384 dimensions (~85MB JSON) is the practical upper limit for reliable cross-device support.

How do I use altor-vec in a service worker for offline search?

altor-vec WASM runs in service workers. Import altor-vec in your service worker, cache the index JSON with a cache-first strategy, and respond to search fetch events by loading the cached index and running engine.search() in the service worker context.

Related resources

framework

use case

reference