API Reference

withFlux()

A Next.js config wrapper that generates your service worker at build time and injects the hydration-race-fix script into every page — the one line that connects Flux's IDB layer to offline navigation and asset caching.

Prerequisites
  • @tsworldtech/flux-next installed
  • A storagePrefix that matches the one passed to createFlux() — see api/create-flux
1

Basic usage

Wrap your existing Next.js config with withFlux() — it returns a modified config, so it composes with any other config wrappers you already use.
typescript
1// next.config.js
2const { withFlux } = require('@tsworldtech/flux-next')
3
4/** @type {import('next').NextConfig} */
5const nextConfig = {
6 // your existing config
7}
8
9module.exports = withFlux(nextConfig, {
10 precacheRoutes: ['/', '/pricing', '/blog'],
11 offlineShell: '/offline',
12 storagePrefix: 'myapp_',
13 dynamicRoutePrefixes: ['/blog/', '/products/'],
14})
2

Config reference

precacheRoutes, offlineShell, and storagePrefix are the fields you'll set on nearly every project. storagePrefix specifically must match whatever you pass to createFlux(), since the SW and the engine need to agree on the same IDB namespace.
typescript
1interface GenerateSwConfig {
2 precacheRoutes: string[] // routes cached ahead of time, not just on first visit
3 offlineShell: string // fallback route served when nothing else matches
4 storagePrefix: string // must match the storagePrefix passed to createFlux()
5 syncTags?: string[] // Background Sync tags registered while a tab is hidden
6 cacheVersion?: string // bump to force old SW caches to be dropped
7 outDir?: string // where sw.js is written — defaults to /public
8 dynamicRoutePrefixes: string[] // see step 4 — enables offline dynamic route rendering
9}
3

What happens at build time

Two things happen automatically — you never hand-write or edit the generated service worker file yourself.
typescript
1// withFlux() wraps your Next.js config and, at build time:
2// 1. Writes sw.js to /public (or outDir) using the SW template, with
3// your config values injected as PRECACHE_ROUTES, CACHE_VERSION,
4// IDB_NAME, OFFLINE_SHELL, SYNC_TAGS, EXTERNAL_CACHE_ORIGINS,
5// and DYNAMIC_ROUTE_PREFIXES
6// 2. Injects an inline script into <head> that sets
7// window.FLUX_SW_READY = false, resolving it once
8// navigator.serviceWorker.ready fires
9
10// You never write or touch sw.js yourself — it's fully generated.
4

The SW vs App Router hydration race fix

This is the specific problem withFlux() exists to solve: without a signal for "the service worker is actually controlling this page yet," the App Router can render before the SW is ready to serve cached assets, causing a flash of unstyled or unhydrated content on repeat visits.
typescript
1// The inline script withFlux() injects, conceptually:
2window.FLUX_SW_READY = new Promise((resolve) => {
3 if (!('serviceWorker' in navigator)) return resolve(true)
4 navigator.serviceWorker.ready.then(() => resolve(true))
5})
6
7// HydrationGate waits on BOTH flux.hydrated and window.FLUX_SW_READY.
8// On a repeat visit the SW is already active — this resolves in
9// well under 10ms. On a first visit, the 2000ms hard timeout in
10// HydrationGate takes over and the app renders unhydrated.
Pairs directly with HydrationGate

window.FLUX_SW_READY only matters because HydrationGate reads it. If you're not using HydrationGate, this script still runs but nothing consumes it by default.

5

dynamicRoutePrefixes

This is what makes a dynamic route like /blog/[slug] work fully offline, even for a specific slug the user has never visited before — as long as the homepage (or any page) has been visited once.
typescript
1dynamicRoutePrefixes: ['/blog/', '/products/']
2
3// A hard navigation to /blog/my-post while fully offline, having
4// never visited that exact URL before:
5// 1. Misses PAGE_CACHE (never fetched this specific page)
6// 2. Misses the network (offline)
7// 3. pathname matches a prefix in DYNAMIC_ROUTE_PREFIXES
8// 4. SW serves the cached root app shell ('/') AT THE REAL URL —
9// the address bar stays on /blog/my-post
10// 5. Next.js's client router mounts the matching route component
11// 6. Flux hydrates the actual post data from IDB
12
13// Without a matching prefix, step 3 falls through to offlineShell instead.
6

What the generated service worker does

You don't write any of this — it's worth knowing what's running, since it's the layer between a navigation and whatever withFlux() configured for you.
typescript
1// What the generated sw.js actually does at runtime:
2
3// handleNavigation — cache-first with background update. On a miss for
4// both cache and network, checks DYNAMIC_ROUTE_PREFIXES before falling
5// back to the offline shell.
6
7// handleStatic — cache-first for every /_next/static/ path and static
8// file extension.
9
10// FLUX_WARM_CHUNKS listener — receives the assets[] array from a
11// bootstrap() response and fetches/caches any chunks not already stored.
12
13// SKIP_WAITING listener — forces the new SW to activate immediately
14// when you post that message, instead of waiting for all tabs to close.

For the hook that consumes window.FLUX_SW_READY, see api/hydrate. For how assets[] reaches the FLUX_WARM_CHUNKS listener in the first place, see api/bootstrap. Building with Vite or Remix instead of Next.js? See the Vite plugin covered under the flux-react package docs — same SW template, no App Router specifics.