SwoffSwoff

Data Fetching & Caching

fetchWithCache, prefetchCache, and framework adapters for reactive data fetching.

If you're coming from TanStack Query: fetchWithCache returns { response, fromCache } instead of { data, isLoading }. Caching happens in the Service Worker — cached responses survive hard navigation, tab close, and full page reload. No provider wrapper needed. Prefer TanStack Query's data caching? Set its routes to "network-only" in strategy.patterns — Swoff still handles offline navigation and all other assets. See the Library Comparison.

This is the first guide that introduces a swoff API. Caching Strategy and Navigation Caching (guides 2–3) require no imports — they work purely through swoff.config.json. Data fetching is where you start interacting with the cache programmatically.

Requires features.tagInvalidation.enabled: true. The fetch/core, cache/tags, and cache/invalidate modules are generated only when tag invalidation is enabled. Without it, the SW still intercepts requests and applies caching strategies — you just can't use fetchWithCache() or prefetchCache() programmatically.

Preconditions

  • Swoff initialized (swoff init done)
  • Service worker registered and controlling the page
  • Caching strategies configured (see Caching Strategy guide)
  • features.tagInvalidation.enabled: true in swoff.config.json

Status

Off by default (gated behind features.tagInvalidation.enabled: true). When enabled, the generated SW applies these default patterns:

PatternStrategyBehavior
/api/*network-firstTry network, fall back to cache
/static/*cache-firstServe from cache on hit, fetch from network on miss

The SW handles all fetch events — no manual route registration needed.

Generated files

FileWhat it doesImport in your code?
swoff/fetch/core.tsfetchWithCache(), prefetchCache()Yes — main API
swoff/cache/invalidate.tsinvalidateByTag(), invalidateByTags()Yes, for manual invalidation
swoff/config.tsAPI_BASE — base URL for relative fetch pathsEdit if your API is on another origin

No-bundler projects (no-bundler, HTMX) do not generate fetch/core.ts or cache/invalidate.ts. Instead, the swoff-api.bundle.js IIFE provides window.swoff.fetchWithCache() and related APIs. Use native fetch() with X-SW-* headers to control caching per request (see below), or use the window.swoff API for programmatic cache access. The SW still intercepts all requests and applies your configured strategy.

Usage

fetchWithCache is a thin wrapper around the native fetch() API. It returns the same { response, fromCache } shape regardless of your framework.

Plain JavaScript (works everywhere)

import { fetchWithCache, prefetchCache } from "./swoff/fetch/core";

// Fetch — cached in SW, survives hard nav
const { response, fromCache } = await fetchWithCache("/api/notes");
const notes = await response.json();

// fromCache === true means the SW served a cached response.
// What happens next depends on the strategy configured for this URL
// (see the Caching Strategies guide).

// Prefetch — fire-and-forget cache warm
prefetchCache("/api/notes/123");

From React (generated hooks)

If your framework is React-based, the CLI generates reactive hooks into swoff/adapters/:

import { useSwoffFetch } from "./swoff/adapters/useSwoffFetch";

function NotesList() {
  const { data, isLoading, error, fromCache } = useSwoffFetch("/api/notes");
  if (isLoading) return <p>Loading...</p>;
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

See the React framework page for the full adapter list, or the Blueprint to create adapters for any framework.

From HTMX (header-based)

HTMX requests are regular HTTP fetches — the SW intercepts them automatically. Control caching strategy via hx-headers:

<!-- Default strategy applies from swoff.config.json -->
<button hx-get="/api/notes">Load Notes</button>

<!-- Override strategy for this request -->
<button hx-get="/api/notes" hx-headers='{"X-SW-Strategy": "network-first"}'>
  Fresh Notes
</button>

From any framework (native fetch with headers)

No swoff import needed for basic caching — the SW intercepts every fetch call automatically. Use X-SW-* headers for per-request control:

// Plain fetch — SW applies strategy from config
const res = await fetch("/api/notes");

// With strategy override via header
const res = await fetch("/api/notes", {
  headers: { "X-SW-Strategy": "stale-while-revalidate" },
});

From PHP / Laravel / Django (server-rendered pages)

Include the auto-initializing bundles in your template, then use native fetch() or the window.swoff API:

{{-- Laravel Blade --}}
<script src="{{ asset('swoff/client-injector.bundle.js') }}"></script>
<script src="{{ asset('swoff/swoff-api.bundle.js') }}"></script>
<script>
// Use the window.swoff API for programmatic caching
const { response, fromCache } = await swoff.fetchWithCache("/api/notes");
const notes = await response.json();

// SW intercepts all fetch requests — caching works automatically
const res = await fetch("/api/notes");

// For per-request control, use X-SW-* headers:
const fresh = await fetch("/api/notes", {
  headers: { "X-SW-Strategy": "network-first" }
});

// Mark a request as a mutation via X-SW-Type header:
const created = await fetch("/api/notes", {
  method: "POST",
  body: JSON.stringify({ title: "New" }),
  headers: { "X-SW-Type": "mutation" }
});
</script>

Customize

No generated files to edit for basic setup. All caching behavior is configured through swoff.config.json. See the Caching Strategies guide for all strategy options, patterns, and config reference.

Config

{
  "features": {
    "requestBatchWindowMs": 50
  }
}
  • requestBatchWindowMs — debounce window in milliseconds for coalescing rapid fetchWithCache calls to the same URL. Default: 50. Prevents duplicate in-flight requests during rapid re-renders.

Caching strategy configuration (default strategy, timeout, patterns, per-route overrides, cache eviction, cache key normalization, reactive defaults) is covered in the Caching Strategies guide.

Framework adapters

This section has two adapters:

useSwoffFetch

The primary data-fetching adapter. Exposes reactive request state: { data, isLoading, error, fromCache, refetch }.

Events listened to:

EventDetail shapePurpose
cache-invalidated{ tags }Cache entries invalidated — refetch if your URL's tags intersect

If your framework already has these adapters (useSwoffFetch, useSwoffPrefetch — React, Vue, Svelte), the CLI generates them into swoff/adapters/. To create adapters for any other framework, see the Blueprint for the event reference and adapter patterns.

On this page