There are several ways to reflect database changes in the UI in Next.js. Here are the correct approaches:
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 };
};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>;
};"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
}
};
};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} />);
};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