Data Preloading
Overview
The Data Preloading module provides utilities and middleware to fetch and cache data before it is actually needed by a React component. Its primary goal is to reduce loading delays and avoid cascading fetch waterfalls by preemptively populating the cache with data, enabling components to render immediately with cached data or to enter suspense states with minimal delay.
This module is crucial in scenarios where data required by a component can be anticipated and fetched ahead of time, such as during server-side rendering or early client-side initialization. By decoupling data fetching from component mounting, Data Preloading improves perceived performance and user experience.
Core Concepts
Preloading vs Normal Fetching
Normal Fetching: A component calls a data fetching hook (e.g.,
useSWR) during render, triggering a fetch if data is not cached or stale.Preloading: Data is fetched before the component mounts, and the result cached. When the component mounts and calls
useSWRwith the same key, it immediately finds the cached data and avoids refetching or loading delays.
Cache Coordination
Preloading involves a dedicated cache (PRELOAD) separate from the main cache to track prefetch promises keyed by serialized request keys. When the component later calls useSWR, the middleware checks this PRELOAD cache and serves the preloaded promise to avoid duplicate requests.
How Preloading Works
Preload Utility Function
The main function preload accepts a key and a fetcher function. It serializes the key to a stable string and checks if a preload request for this key already exists in the preload cache.
If a preload request exists, it returns the cached promise to avoid duplicate network calls.
Otherwise, it calls the fetcher function with the serialized argument, stores the resulting promise in the preload cache, and returns it.
This behavior ensures idempotent preloading: multiple calls to preload with the same key before the component uses the data will share the same fetch promise.
export const preload = <Data, SWRKey, Fetcher>(
key_: SWRKey,
fetcher: Fetcher
): ReturnType<Fetcher> => {
const [key, fnArg] = serialize(key_)
const [, , , PRELOAD] = SWRGlobalState.get(cache) as GlobalState
if (PRELOAD[key]) return PRELOAD[key]
const req = fetcher(fnArg) as ReturnType<Fetcher>
PRELOAD[key] = req
return req
}
Preload Middleware
To integrate preloading with the normal SWR lifecycle, a middleware is provided. This middleware wraps the fetcher function used by useSWR:
When the SWR hook is called, the middleware serializes the key and checks if there is a preload promise in the
PRELOADcache.If found, it deletes the preload record (to avoid reuse) and returns the preloaded promise instead of calling the fetcher again.
If no preload promise exists, it calls the original fetcher as usual.
This ensures that preloaded data is consumed by the first hook that uses it, and subsequent hooks will either reuse cached data or fetch fresh data.
export const middleware: Middleware =
useSWRNext => (key_, fetcher_, config) => {
const fetcher =
fetcher_ &&
((...args: any[]) => {
const [key] = serialize(key_)
const [, , , PRELOAD] = SWRGlobalState.get(cache) as GlobalState
if (key.startsWith(INFINITE_PREFIX)) {
return fetcher_(...args)
}
const req = PRELOAD[key]
if (isUndefined(req)) return fetcher_(...args)
delete PRELOAD[key]
return req
})
return useSWRNext(key_, fetcher, config)
}
Interaction with Other Modules
Core SWR Hook (
useSWR):
The preload middleware wraps the fetcher insideuseSWR. When components mount and invokeuseSWRwith keys that were preloaded, the middleware supplies the preloaded promise, ensuring immediate data availability or fast suspense resolution.Global Cache and State (
cache,SWRGlobalState):
The preload cache (PRELOAD) is stored inside the global SWR state alongside other caches. This shared global state allows the preload utility and middleware to coordinate fetch promises and cache entries.Serialization Utility (
serialize):
Keys passed to preload and SWR are serialized consistently to stable strings to ensure cache key identity matches across preloading and hook usage.Infinite Loading (
useSWRInfinite):
The middleware explicitly bypasses preload logic for keys that start with the infinite loading prefix, delegating preload handling to the infinite loading module that manages its own cache for pages.
Important Design Patterns and Concepts
Idempotent Preloading:
Repeated calls topreloadwith the same key share the same fetch promise and do not trigger redundant fetches.Cache Separation and Consumption:
Preloading uses a dedicated cache for promises (PRELOAD) separate from the main data cache. Once consumed by a hook, preload entries are deleted to avoid stale usage or duplication.Middleware Composition:
The preload logic is implemented as middleware, allowing transparent integration into existing SWR hooks without modifying their core logic. This supports flexible extension and composition with other middleware.Key Serialization:
Consistent serialization of keys ensures that complex keys (functions, arrays) are correctly and consistently mapped to string cache keys.Bypass for Infinite Loading:
Infinite loading keys are excluded from preload middleware to avoid conflicts, since infinite loading manages pagination and caching differently.
Summary of Key Files and Their Roles
File | Role |
|---|---|
| Implements the |
Mermaid Diagram: Preloading Workflow Sequence
This sequence diagram visualizes the typical workflow of preloading data and its consumption by a component using the SWR hook with preload middleware.
sequenceDiagram
participant Preloader as Preload Utility
participant Cache as PRELOAD Cache
participant Component as React Component
participant SWRHook as useSWR Hook with Preload Middleware
participant Fetcher as Fetcher Function
Preloader->>Cache: Check if preload promise exists for key
alt preload promise exists
Cache-->>Preloader: Return existing preload promise
else
Preloader->>Fetcher: Call fetcher with serialized key arg
Fetcher-->>Preloader: Return fetch promise
Preloader->>Cache: Store preload promise by key
end
Component->>SWRHook: Call useSWR with key and fetcher
SWRHook->>Cache: Check for preload promise by key
alt preload promise exists
Cache-->>SWRHook: Return preload promise and delete from cache
else
SWRHook->>Fetcher: Call fetcher normally
end
SWRHook-->>Component: Return data or promise
Summary
The Data Preloading module enables anticipatory data fetching by caching fetch promises before component mount and transparently supplying them during component data fetching. Using a dedicated preload cache and middleware integration, it reduces loading delays and improves rendering performance without disrupting the SWR hook’s normal lifecycle or infinite loading mechanisms. This design supports efficient, idempotent preloading with clean separation of concerns and extensibility.