Search code examples
cachingnext.jsnext.js13

NextJS 13: Invalidating sidebar server component in chat app


Full minimal reproducable repo is here.

This is a chat app with these routes:

  • /chat
    • layout.tsx - server component that houses Sidebar which is also a server component
  • /chat/[id] - client component

Whenever I update the data in my Input component, the sidebar doesn't update even with all of the commands below:

router.refresh();
router.push("/chat/" + chatId);

I even have a special route to invalidate the cache on the server side which calls revalidatePath("/chat", "layout") but the sidebar still doesn't update.

However, it's weird because in my Sidebar server component, the shows that it updates but the UI just doesn't update for some reason.

Below are some of the key code snippets, but the full reproducible code is in the repo.

// ./app/chat/layout.tsx

import React from 'react'
import { Sidebar } from "./_sidebar"

export default async function Layout({children}: {children: React.ReactNode}) {
  return (
    <div className="relative w-screen min-h-screen flex justify-start">
      <Sidebar />
      {children}
    </div>
  )
}
// ./app/chat/_sidebar
import { getChats } from "@/actions";
import Link from "next/link";

export async function Sidebar() {
  const chats = await getChats();
  console.log("Sidebar.chats.latest", chats.at(-1))

  return (
    <div className="flex flex-col h-full px-6 min-h-screen">
      <h2 className="font-bold underline">Sidebar</h2>
        <Link href="/chat">New Chat</Link>
        {chats.map((chat, index) => (
          <p className="whitespace-nowrap" key={index}>{chat.message.slice(0,20)}</p>
         ))}
    </div>
  );
}
// ./app/chat/_input.tsx

"use client";
import { nanoid } from "nanoid";
import { useRouter } from "next/navigation";
import React from "react";

export function Input() {
  const router = useRouter();
  const inputRef = React.useRef<any>();

  return (
    <div className="absolute bottom-1/2 flex flex-col h-28">
      <div className="flex flex-col space-y-8">
        <label className="text-white">Input Field Below:</label>
        <textarea ref={inputRef} className="text-black w-96 h-48" />
        <button
          className="bg-green-900 px-6 py-3"
          onClick={async () => {
            const value = inputRef?.current.value;
            const chatId = nanoid();
            await fetch("/api/chats/update", {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
              },
              body: JSON.stringify({ id: chatId, message: value }),
            }).then(res => res.json());

            await fetch(`/api/revalidate?path=/chat&type=layout`, {cache: "no-store"});
            router.refresh();
            router.push("/chat/" + chatId);
         }}
        >
          Submit
        </button>
      </div>
    </div>
  );
}

Solution

  • I found a workaround: just swap the router.push and router.refresh rows. And maybe it doesn't need to call revalidatePath.

    The code below is an example.

    <button
        className="bg-green-900 px-6 py-3"
        onClick={async () => {
            const value = inputRef?.current.value;
            const chatId = nanoid();
            await fetch("/api/chats/update", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify({ id: chatId, message: value }),
            }).then(res => res.json());
            router.push("/chat/" + chatId);
            router.refresh();
        }}
    >
        Submit
    </button>