How to Optimize SWR in React

This article provides a practical guide on how to optimize the SWR (Stale-While-Revalidate) data-fetching library in React applications. You will learn how to fine-tune revalidation settings, leverage optimistic updates, implement request deduplication, and prefetch data to significantly improve your application’s performance and user experience.

1. Adjust Default Revalidation Settings

By default, SWR aggressively revalidates data to ensure it is always up to date. However, features like revalidating on window focus or network reconnection can cause unnecessary API requests. You can disable or customize these settings globally using SWRConfig or locally inside individual useSWR hooks.

// Disable automatic revalidation on focus and reconnect
const { data } = useSWR('/api/user', fetcher, {
  revalidateOnFocus: false,
  revalidateOnReconnect: false,
  refreshInterval: 0, // Disable polling unless explicitly needed
});

2. Leverage Deduping Intervals

If multiple components on the same page request the same API endpoint simultaneously, SWR automatically dedupes these requests. By default, the dedupingInterval is set to 2000ms (2 seconds). If your data does not change frequently, you can increase this interval to prevent redundant network requests.

const { data } = useSWR('/api/static-data', fetcher, {
  dedupingInterval: 60000, // Dedupes requests for 1 minute
});

3. Enable keepPreviousData for Pagination and Filtering

When implementing pagination, search, or filtering, updating the SWR key usually triggers a loading state, causing the UI to flicker. Enabling keepPreviousData keeps the old data on the screen while the new data is being fetched, ensuring a seamless user transition.

const [page, setPage] = useState(1);
const { data } = useSWR(`/api/items?page=${page}`, fetcher, {
  keepPreviousData: true,
});

4. Implement Optimistic Updates with Mutate

Instead of waiting for a server response to update the UI after a mutation (like adding a comment or liking a post), you can use SWR’s mutate function to perform optimistic updates. This immediately updates the local cache and UI, then validates the change with the server in the background.

const { mutate } = useSWRConfig();

const handleAddTodo = async (newTodo) => {
  const options = {
    optimisticData: [...data, newTodo],
    rollbackOnError: true,
    populateCache: true,
    revalidate: false,
  };
  
  await mutate('/api/todos', updateTodoOnServer(newTodo), options);
};

5. Prefetch Data Before Rendering

To eliminate loading times entirely, you can prefetch data before a component renders. SWR provides a preload API that initiates the fetch request early in the application lifecycle, such as when a user hovers over a link or button.

import { preload } from 'swr';

// Trigger preloading when a user hovers over a navigation link
const prefetchUserData = () => {
  preload('/api/user', fetcher);
};

return (
  <button onMouseEnter={prefetchUserData} onClick={navigateToProfile}>
    View Profile
  </button>
);

6. Use a Custom Cache Provider for Persistence

SWR stores its cache in memory by default, meaning data is lost upon a page refresh. You can optimize load times on subsequent visits by syncing SWR’s cache with localStorage or sessionStorage using a custom cache provider.

function localStorageProvider() {
  const map = new Map(JSON.parse(localStorage.getItem('app-cache') || '[]'));
  
  window.addEventListener('beforeunload', () => {
    const appCache = JSON.stringify(Array.from(map.entries()));
    localStorage.setItem('app-cache', appCache);
  });
  
  return map;
}

// Wrap your app with SWRConfig
<SWRConfig value={{ provider: localStorageProvider }}>
  <App />
</SWRConfig>