responsive-rsc

Client-side caching for React Server Components

The Problem

React Server Components can only be updated by changing their props. In Next.js, this typically means updating the URL search params and passing the new values to the component.

Lets look at the below setup where we have a filter component - with values A, B, C. Its currently set to value A. When user changes the value to B, it updates the "value" search parameter of the page.

import { Filter, ComponentSkeleton } from "./client";

async function Component(props) {
  await waitSeconds(2);
  return <div> {props.value} </div>;
}

export default async function Page(props) {
  const searchParams = await props.searchParams;
  const value = searchParams.value || "A";
  await waitSeconds(1);

  return (
    <div>
      <Filter value={value} />
      <Suspense fallback={<ComponentSkeleton />}>
        <Component value={value} />
      </Suspense>
    </div>
  );
}

In the demo, the page component has an artificial delay of 1 second, and the server component has an artificial delay of 2 seconds. The filter buttons use useTransition to show a spinner while updating search parameters

/example

A

Click "B", wait for it to load, then click "A" again. Even though "A" was already loaded, you have to wait for the server again. Even if we use caching in the server component with something like unstable_cache, a request to the server will still be made, the page delay will still be present.

With client-side data fetching libraries like React Query, previously fetched data would show instantly from local cache. we need something like a local cache for Server Components to avoid making additional requests to the server

The Solution

responsive-rsc brings client-side caching to Server Components. When you request search parameters for which we already have cached results, the cached Server Components are rendered instantly without a server request

/example

A

Click A, B, C, then click A again - it loads instantly from local cache - No server request is made and UI updates are instant!

API

Wrap your page with ResponsiveSearchParamsProvider and replace Suspense with ResponsiveSuspense for the server components that rely on search params. Specify which search params the component uses with the searchParamsUsed prop.

import {
  ResponsiveSearchParamsProvider,
  ResponsiveSuspense,
} from "responsive-rsc";
import { Filter } from "./client";

export default async function Page(props) {
  const searchParams = await props.searchParams;

  return (
    <ResponsiveSearchParamsProvider
      value={{ value: searchParams.value }}
    >
      <Filter />
      <ResponsiveSuspense
        searchParamsUsed={["value"]}
        fallback={<SlowComponentSkeleton />}
      >
        <SlowComponent value={searchParams.value} />
      </ResponsiveSuspense>
    </ResponsiveSearchParamsProvider>
  );
}

In client components (like the filter buttons), use useResponsiveSearchParams to read the search parameters and useSetResponsiveSearchParams to update them.

"use client";

import {
  useResponsiveSearchParams,
  useSetResponsiveSearchParams,
} from "responsive-rsc";

export function Filter() {
  const { value } = useResponsiveSearchParams();
  const setResponsiveSearchParams =
    useSetResponsiveSearchParams();

  function handleUpdate(newValue: string) {
    setResponsiveSearchParams((v) => ({
      ...v,
      value: newValue,
    }));
  }

  return <div> ... </div>;
}
© 2026/Manan Tank