Back to Blog
Lesson 26 of the Next.js: Build Full-Stack Apps with the App Router course
Next.jsAugust 13, 20264 min read

Optimistic Updates in Next.js: Improving Perceived Performance

Learn how to use React's useOptimistic hook in Next.js to provide instant UI feedback, masking network latency and making your full-stack blog feel lightning-fast.

Next.jsReactPerformanceUXWeb Development
Scrabble tiles spelling 'UPDATE' on wooden surface, symbolizing progress and change.

Previously in this course, we covered revalidating data with revalidatePath to keep our database and UI in sync. While that ensures data consistency, it still forces the user to wait for the server to process the request before the screen updates.

In this lesson, we’ll move beyond that wait time by implementing optimistic updates. This technique allows us to update the UI immediately, as if the request already succeeded, and then revert (or confirm) the UI state once the server response arrives.

What are Optimistic Updates?

An optimistic update is a pattern where the frontend "guesses" the successful result of an action before the server confirms it.

Think about how a "Like" button works on a social media app. When you tap the heart, the icon turns red instantly. The app doesn't wait for a round-trip to the database to tell it that the "like" was saved. If the network request happens to fail, the app quietly reverts the UI to its original state. This creates a "snappy" experience that masks real-world network latency.

In React, the useOptimistic hook is designed specifically for this purpose. It manages a temporary state that is derived from your current state but can be "overridden" while an action is in progress.

Implementing useOptimistic in a Blog Comment Section

Let’s apply this to our ongoing project by making the "Add Comment" form feel instantaneous.

When a user submits a comment, we want to show it in the list immediately, even before the database confirms it. Here is how we implement this using a Client Component:

TSX
CE9178">'use client';

import { useOptimistic } from CE9178">'react';
import { addComment } from CE9178">'./actions';

export default function CommentSection({ initialComments }) {
  // 1. Initialize the optimistic state
  const [optimisticComments, addOptimisticComment] = useOptimistic(
    initialComments,
    (state, newComment) => [...state, { text: newComment, pending: true }]
  );

  async function action(formData: FormData) {
    const text = formData.get(CE9178">'text') as string;
    
    // 2. Update UI immediately
    addOptimisticComment(text);
    
    // 3. Perform the actual server operation
    await addComment(text);
  }

  return (
    <form action={action}>
      <input name="text" required />
      <button type="submit">Post Comment</button>
      
      <ul>
        {optimisticComments.map((comment, i) => (
          <li key={i} style={{ opacity: comment.pending ? 0.7 : 1 }}>
            {comment.text}
          </li>
        ))}
      </ul>
    </form>
  );
}

How it works under the hood

  1. The Hook: useOptimistic takes your current server-rendered state (initialComments) and a reducer function.
  2. The Trigger: When addOptimisticComment is called, React instantly re-renders the component using the value returned by your reducer function.
  3. Synchronization: Once the await addComment(text) server action completes, Next.js will re-fetch the data (typically via revalidatePath) and update the initialComments prop. React then automatically swaps out your temporary "optimistic" state for the real data from the server.

Hands-on Exercise

  1. Open your comment form component.
  2. Wrap your existing comment list state in useOptimistic.
  3. Ensure your reducer function correctly appends the new comment object to the existing array.
  4. Add a visual indicator (like a grayed-out opacity, as shown in the code above) to distinguish "pending" comments from "confirmed" ones.
  5. Test by opening your browser's Network tab and setting the throttling to "Slow 3G" to observe the optimistic update in action.

Common Pitfalls

  • Forgetting to Revalidate: If you don't call revalidatePath after your Server Action, the UI will stay in the "optimistic" state forever because the parent component will never receive the updated "real" data.
  • Over-optimizing: Don't use useOptimistic for everything. It works best for simple UI updates like liking, starring, or adding small items. If an action is critical (e.g., changing a password or processing a payment), you should wait for the server response to avoid user confusion.
  • State Drift: Ensure your optimistic state structure matches the data coming back from the server, or React might struggle to reconcile the transition between the two.

FAQ

Q: Does useOptimistic handle error rollbacks automatically? A: Yes. If the server action throws an error, the useOptimistic state will naturally revert to the latest initialState provided by the server.

Q: Can I use this with complex data structures? A: Absolutely. While our example used an array of comments, you can store any data type in the optimistic state.

Q: How does this relate to performance? A: It doesn't change the actual speed of the server, but it significantly improves perceived performance, which is often more important for user retention.

Recap

We’ve learned that optimistic updates allow us to bypass network latency by rendering the expected outcome of an action immediately. By using useOptimistic, we keep our UI responsive while the server handles the heavy lifting in the background. This pattern is essential for building professional, high-performance web applications that feel native to the user.

Up next: We will secure our application by learning how to manage secrets with Environment Variables.

Similar Posts