How to Update React Server Components

React Server Components (RSC) represent a paradigm shift in web development by rendering components on the server to improve performance and SEO. Because Server Components run exclusively on the server, they cannot use client-side state hooks like useState or useEffect to trigger visual updates. Instead, updating a Server Component requires requesting a new render from the server and merging the new UI payload into the existing client-side page. This article explains the primary methods used to update React Server Components, including Server Actions, router refreshing, and URL parameter mutations.

1. Using Server Actions and Data Revalidation

The most common and efficient way to update a Server Component is by using Server Actions combined with data revalidation. When a user interacts with a Client Component (like clicking a button or submitting a form), they trigger a Server Action that mutates data on the server. Once the mutation is complete, you tell React to revalidate the cache, which forces the Server Component to re-render with the fresh data.

In frameworks like Next.js, this is achieved using revalidatePath or revalidateTag.

// app/actions.js (Server Action)
'use server'

import { revalidatePath } from 'next/cache';

export async function updateTodoItem(todoId, completed) {
  // 1. Mutate the database
  await db.todo.update({ where: { id: todoId }, data: { completed } });

  // 2. Revalidate the path containing the Server Component
  revalidatePath('/todos');
}

When revalidatePath('/todos') is called, the server re-renders the Server Components associated with that route and sends the updated UI back to the browser, updating the screen seamlessly without a full page reload.

2. Triggering a Router Refresh

If you need to update a Server Component without a direct data mutation action—such as after a specific client-side event—you can explicitly tell the router to refresh the current route.

In Next.js, you can use the useRouter hook from next/navigation inside a Client Component. Calling router.refresh() requests a new payload for the current route from the server.

// app/components/RefreshButton.js (Client Component)
'use client'

import { useRouter } from 'next/navigation';

export default function RefreshButton() {
  const router = useRouter();

  return (
    <button onClick={() => router.refresh()}>
      Sync Data
    </button>
  );
}

When router.refresh() is executed: * The server re-renders the Server Components. * The client receives the updated payload. * The browser retains the client-side state (such as input values or scroll position) while updating the DOM with the new server-rendered content.

3. Changing URL and Query Parameters

Because Server Components have access to route parameters and search queries (URL query parameters), changing the URL is an effective way to trigger a re-render. When the URL changes, the server processes the new request parameters and sends the updated Server Component output.

For example, a pagination component or a search input can update the Server Component by pushing a new query string to the router:

// app/components/SearchInput.js (Client Component)
'use client'

import { useRouter, usePathname } from 'next/navigation';

export default function SearchInput() {
  const router = useRouter();
  const pathname = usePathname();

  function handleSearch(term) {
    const params = new URLSearchParams();
    if (term) {
      params.set('query', term);
    } else {
      params.delete('query');
    }
    router.push(`${pathname}?${params.toString()}`);
  }

  return (
    <input 
      type="text" 
      onChange={(e) => handleSearch(e.target.value)} 
      placeholder="Search products..." 
    />
  );
}

The parent Server Component reads the searchParams from its props and fetches new data based on the updated search query, rendering the fresh results automatically.