Data Fetching & Caching
fetchWithCache, prefetchCache, and framework adapters for reactive data fetching.
If you're coming from TanStack Query:
fetchWithCachereturns{ 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"instrategy.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. Thefetch/core,cache/tags, andcache/invalidatemodules are generated only when tag invalidation is enabled. Without it, the SW still intercepts requests and applies caching strategies — you just can't usefetchWithCache()orprefetchCache()programmatically.
Preconditions
- Swoff initialized (
swoff initdone) - Service worker registered and controlling the page
- Caching strategies configured (see Caching Strategy guide)
features.tagInvalidation.enabled: trueinswoff.config.json
Status
Off by default (gated behind features.tagInvalidation.enabled: true). When enabled, the generated SW applies these default patterns:
| Pattern | Strategy | Behavior |
|---|---|---|
/api/* | network-first | Try network, fall back to cache |
/static/* | cache-first | Serve from cache on hit, fetch from network on miss |
The SW handles all fetch events — no manual route registration needed.
Generated files
| File | What it does | Import in your code? |
|---|---|---|
swoff/fetch/core.ts | fetchWithCache(), prefetchCache() | Yes — main API |
swoff/cache/invalidate.ts | invalidateByTag(), invalidateByTags() | Yes, for manual invalidation |
swoff/config.ts | API_BASE — base URL for relative fetch paths | Edit if your API is on another origin |
No-bundler projects (no-bundler, HTMX) do not generate
fetch/core.tsorcache/invalidate.ts. Instead, theswoff-api.bundle.jsIIFE provideswindow.swoff.fetchWithCache()and related APIs. Use nativefetch()withX-SW-*headers to control caching per request (see below), or use thewindow.swoffAPI 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 rapidfetchWithCachecalls 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:
| Event | Detail shape | Purpose |
|---|---|---|
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.
Related
- Library Comparison — Swoff vs TanStack Query / SWR / Apollo Client