Speculative Data Fetching in React Using <link rel="preload"> and Service Workers

Modern SPAs often suffer from the time gap between HTML render and React hydration, especially when data fetching kicks in after the app loads. What if you could speculatively preload app state before hydration — like how browsers preload fonts or images? Using a combination of ``, Service Workers, and a custom React hook, you can preload data before React even boots, dramatically improving Time to Interactive (TTI) for initial render. Let’s build it. Step 1: Preload API Data in the HTML Head Before your React app initializes, you can hint to the browser to preload key JSON data by injecting this tag into your server-rendered HTML: This doesn’t execute a fetch directly — it just hints the browser to fetch the resource early and cache it in the preload cache. Step 2: Intercept Preload with a Service Worker To capture this speculative fetch and make it available to React before the app asks for it, install a Service Worker that caches preload fetches. Inside sw.js: self.addEventListener("fetch", (event) => { if (event.request.url.includes("/api/dashboard")) { event.respondWith( caches.open("preloaded-api").then(async (cache) => { const match = await cache.match(event.request); if (match) return match; const response = await fetch(event.request); cache.put(event.request, response.clone()); return response; }) ); } }); This will store the preload response in a named cache, so your React app can pull from it instantly. Step 3: Install the Service Worker on First Load Somewhere near the top of your React app (or in index.tsx): if ("serviceWorker" in navigator) { navigator.serviceWorker.register("/sw.js").then(() => { console.log("SW registered"); }); } This ensures your Service Worker is available for future navigations — and will intercept preload requests. Step 4: Create a React Hook to Load from Cache First This hook will load preloaded data from the Service Worker cache, falling back to a network fetch if needed. export function usePreloadedData(url) { const [data, setData] = React.useState(null); React.useEffect(() => { let active = true; async function fetchData() { const cache = await caches.open("preloaded-api"); const cached = await cache.match(url); if (cached) { const json = await cached.json(); if (active) setData(json); } else { const res = await fetch(url); const json = await res.json(); if (active) setData(json); } } fetchData(); return () => { active = false }; }, [url]); return data; } Now, in your component: const dashboardData = usePreloadedData("/api/dashboard"); Boom — React renders with preloaded data immediately, avoiding the wait for hydration + fetch. Step 5: Optional – Preload Multiple Endpoints Just add more `` tags in your HTML head for other critical endpoints, and update your Service Worker accordingly. Then in the Service Worker: if (event.request.url.includes("/api/")) { // same logic, cache any API preload } ✅ Pros:

May 1, 2025 - 03:45
 0
Speculative Data Fetching in React Using <link rel="preload"> and Service Workers

Modern SPAs often suffer from the time gap between HTML render and React hydration, especially when data fetching kicks in after the app loads. What if you could speculatively preload app state before hydration — like how browsers preload fonts or images?

Using a combination of ``, Service Workers, and a custom React hook, you can preload data before React even boots, dramatically improving Time to Interactive (TTI) for initial render.

Let’s build it.

Step 1: Preload API Data in the HTML Head

Before your React app initializes, you can hint to the browser to preload key JSON data by injecting this tag into your server-rendered HTML:

This doesn’t execute a fetch directly — it just hints the browser to fetch the resource early and cache it in the preload cache.

Step 2: Intercept Preload with a Service Worker

To capture this speculative fetch and make it available to React before the app asks for it, install a Service Worker that caches preload fetches.

Inside sw.js:

self.addEventListener("fetch", (event) => {
  if (event.request.url.includes("/api/dashboard")) {
    event.respondWith(
      caches.open("preloaded-api").then(async (cache) => {
        const match = await cache.match(event.request);
        if (match) return match;

        const response = await fetch(event.request);
        cache.put(event.request, response.clone());
        return response;
      })
    );
  }
});

This will store the preload response in a named cache, so your React app can pull from it instantly.

Step 3: Install the Service Worker on First Load

Somewhere near the top of your React app (or in index.tsx):

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("/sw.js").then(() => {
    console.log("SW registered");
  });
}

This ensures your Service Worker is available for future navigations — and will intercept preload requests.

Step 4: Create a React Hook to Load from Cache First

This hook will load preloaded data from the Service Worker cache, falling back to a network fetch if needed.

export function usePreloadedData(url) {
  const [data, setData] = React.useState(null);

  React.useEffect(() => {
    let active = true;

    async function fetchData() {
      const cache = await caches.open("preloaded-api");
      const cached = await cache.match(url);

      if (cached) {
        const json = await cached.json();
        if (active) setData(json);
      } else {
        const res = await fetch(url);
        const json = await res.json();
        if (active) setData(json);
      }
    }

    fetchData();
    return () => { active = false };
  }, [url]);

  return data;
}

Now, in your component:

const dashboardData = usePreloadedData("/api/dashboard");

Boom — React renders with preloaded data immediately, avoiding the wait for hydration + fetch.

Step 5: Optional – Preload Multiple Endpoints

Just add more `` tags in your HTML head for other critical endpoints, and update your Service Worker accordingly.



Then in the Service Worker:

if (event.request.url.includes("/api/")) {
  // same logic, cache any API preload
}

Pros: