Best Practices
Production recommendations for configuring, debugging, and operating resource-fallback.
Rule configuration
Align match with base / publicPath
| Build tool | Align match with |
|---|---|
| Vite | base |
| Webpack | output.publicPath |
If the first resource URL does not match match, the runtime never enters retry/fallback.
urls order is fallback order
Recommended chain:
Primary CDN → Backup CDN → Self-hosted static origin → Same-origin '/'The last entry is usually '/' (relative origin) to avoid hitting a broken CDN again.
{
match: 'https://cdn.example.com/',
urls: [
'https://cdn-backup.example.com/',
'https://static.mysite.com/',
'/', // origin — always last
],
}Use trailing slashes on CDN prefixes
Prefix URLs should end with / (e.g. https://cdn.example.com/). The runtime uses joinAssetPrefix to avoid malformed paths like ...prod + js/foo.js → ...prodjs/foo.js.
Per-rule retry and circuit
Override per rule when different asset classes need different policies:
rules: [
{
match: 'https://cdn.example.com/',
urls: ['https://cdn-backup.example.com/', '/'],
retry: { max: 2, baseDelay: 300 },
circuit: { threshold: 3, cooldown: 30000 },
},
],
defaults: {
retry: { max: 2 },
circuit: { threshold: 5, cooldown: 30000, shareAcrossTabs: true },
},Keep retry.max between 1–3. Excessive retries increase user wait time.
CDN prefix notes
- Same artifact on all CDNs — required for
sri: 'keep'/'strict' - CORS headers on fonts — fallback font origins need
Access-Control-Allow-Originfor cross-origin@font-face - preconnect — leave
injectPreconnect: true(default) to reduce DNS + TLS latency on fallback hosts
Debugging tips
Enable debug logging
localStorage.__RF_DEBUG__ = '1';
location.reload();Or set debug: true in config (always logs — use sparingly in production).
Verify in the right environment
| Environment | Dynamic import fallback |
|---|---|
Vite dev | ✗ Not supported |
Vite preview / production | ✓ |
| Webpack production | ✓ |
Network panel checklist
- First request to primary CDN fails
- Retries on same host (with
__rf=on module scripts) - Fallback to next URL in
urls - Final success or
rf:error
Hybrid SW debugging
- Use
localhost,127.0.0.1, or HTTPS — not LAN IP over HTTP - Clear old SW + caches after rebuild
- Verify
navigator.serviceWorker.controller?.scriptURLmatches current build
Monitoring
Filter rf:error by reason:
window.addEventListener('rf:error', (e) => {
if (e.detail.reason === 'no-match') return; // expected for third-party scripts
analytics.track('resource_fallback_exhausted', e.detail);
});Track fallback chains:
window.addEventListener('rf:fallback', (e) => {
analytics.track('resource_fallback_switch', {
from: e.detail.from,
to: e.detail.to,
});
});See Runtime Events for full API.
Entry and lazy-route fallback UI
- Entry bundle — add
rf:errorlistener inindex.htmlbefore app scripts - Lazy routes — wrap
React.lazy/ async components with ErrorBoundary - Do not auto-reload on
rf:error— the library intentionally leaves recovery to the application
Sync script limitations
When a classic (non-module) <script> fails:
- Browser fires
erroronly — already-executed code is irreversible - The plugin replaces the DOM node and reloads, but re-execution may cause side effects if globals were partially mounted
- When all URLs are exhausted, only
rf:errorfires — no automaticlocation.reload()
Hybrid SW does not take over scripts and does not guarantee strict ordering for synchronous classic scripts. Strong ordering requires a future opt-in ScriptSequencer capability.