Skip to content

Instantly share code, notes, and snippets.

@jorgeadev
Created December 26, 2025 03:43
Show Gist options
  • Select an option

  • Save jorgeadev/286a828737015bb586081798ea9f96ac to your computer and use it in GitHub Desktop.

Select an option

Save jorgeadev/286a828737015bb586081798ea9f96ac to your computer and use it in GitHub Desktop.

Reflecting Database Changes in the UI - Next.js Best Practices

There are several ways to reflect database changes in the UI in Next.js. Here are the correct approaches:

1. revalidatePath() - Best for Server Actions ✅ (Recommended)

Use this inside your server action to invalidate the cache for a specific path:

"use server";

import { revalidatePath } from "next/cache";

export const archiveArticleById = async (id: string) => {
  // ...existing code...
  
  const result = await prisma.post.update({
    where: { id },
    data: { status: "ARCHIVED" }
  });

  // Revalidate the dashboard page to show updated data
  revalidatePath("/dashboard");
  revalidatePath("/blog");
  
  return { success: true, data: result };
};

2. router.refresh() - For Client Components

Use this when you need to refresh from the client side:

"use client";

import { useRouter } from "next/navigation";

export const ArchiveArticleAction = ({ postId }: ArchiveArticleActionProps) => {
  const router = useRouter();

  const handleArchive = async () => {
    const result = await archiveArticleById(postId);
    
    if (result.success) {
      router.refresh(); // ✅ Refreshes current route's data
    }
  };
  
  return <DropdownMenuItem onClick={handleArchive}>Archive</DropdownMenuItem>;
};

3. Combine Both (Most Reliable) ✅ (Best Practice)

"use server";

import { revalidatePath } from "next/cache";

export const archiveArticleById = async (id: string) => {
  // ...database update...
  
  revalidatePath("/dashboard");
  revalidatePath("/blog");
  
  return { success: true };
};
"use client";

export const ArchiveArticleAction = ({ postId }: ArchiveArticleActionProps) => {
  const router = useRouter();

  const handleArchive = async () => {
    const result = await archiveArticleById(postId);
    
    if (result.success) {
      toast.success("Post archived");
      router.refresh(); // Extra insurance
    }
  };
};

4. Optimistic Updates (Advanced)

For instant UI feedback before server response:

"use client";

import { useOptimistic } from "react";

export const ArticlesList = ({ posts }: { posts: Post[] }) => {
  const [optimisticPosts, updateOptimisticPosts] = useOptimistic(
    posts,
    (state, archivedId: string) => 
      state.filter(post => post.id !== archivedId)
  );

  const handleArchive = async (id: string) => {
    updateOptimisticPosts(id); // Immediate UI update
    await archiveArticleById(id); // Server update
  };

  return optimisticPosts.map(post => <PostRow post={post} />);
};

For Your Case - Recommended Solution:

export const archiveArticleById = async (id: string): Promise<ServerActionReturn<Post>> => {
  // ...existing validation...
  
  const result = await prisma.post.update({
    where: { id },
    data: { status: "ARCHIVED" }
  });

  revalidatePath("/dashboard/articles");
  revalidatePath("/dashboard");
  
  return { success: true, data: result };
};
"use client";

import { toast } from "@/components/wrapper/toast-wrapper";

export const ArchiveArticleAction = ({ postId }: ArchiveArticleActionProps) => {
  const handleArchive = async () => {
    const result = await archiveArticleById(postId);
    
    if (result.success) {
      toast.success("Post archived successfully");
      // No need for router.refresh() if using revalidatePath
    }
  };

  return <DropdownMenuItem onClick={handleArchive}>Archive</DropdownMenuItem>;
};

Key Points:

  • revalidatePath() is server-side cache invalidation
  • router.refresh() is client-side re-fetch
  • ✅ Use both for maximum reliability
  • revalidatePath() alone is usually sufficient
  • ✅ Always revalidate all affected paths
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment