Next.js 14: Hydration Error | Warning: Only plain objects can be passed to Client Components from Server Components

I’m developing an app with Next.js 14, TypeScript, Mongoose & MongoDB.

I fetched users from my database and rendered them in cards with some of their information, like tags displayed in badges.

I also used the Link component to make the cards and the badges clickable.

Here is some of my relevant code:

  • “componentscardsUserCard.tsx”:
import Link from "next/link";
import Image from "next/image";

import { getTopInteractedTags } from "@/lib/actions/tag.action";
import { Badge } from "../ui/badge";
import RenderTag from "../shared/RenderTag";

interface Props {
  user: {
    _id: string;
    clerkId: string;
    picture: string;
    name: string;
    username: string;
    // orgId?: string;
  };
}

const UserCard = async ({ user }: Props) => {
  const interactedTags = await getTopInteractedTags({ userId: user._id });

  return (
    <Link
      href={`/profile/${user.clerkId}`}
      className="shadow-light100_darknone w-full max-xs:min-w-full xs:w-[260px]"
    >
      <article className="background-light900_dark200 light-border flex w-full flex-col items-center justify-center rounded-2xl border p-8">
        <Image
          src={user.picture}
          alt="image profil utilisateur"
          width={100}
          height={100}
          className="rounded-full"
        />

        <div className="mt-4 text-center">
          <h3 className="h3-bold text-dark200_light900 line-clamp-1">
            {user.name}
          </h3>
          <p className="body-regular text-dark500_light500 mt-2">
            @{user.username}
          </p>
          {/* {user.orgId && (
            <p className="body-regular text-dark500_light500 mt-2">
              Org ID: {user.orgId}
            </p>
          )} */}
        </div>

        <div className="mt-5">
          {interactedTags.length > 0 ? (
            <div className="flex items-center gap-2">
              {interactedTags.map((tag) => (
                <RenderTag key={tag._id} _id={tag._id} name={tag.name} />
              ))}
            </div>
          ) : (
            <Badge>Pas encore d’étiquettes</Badge>
          )}
        </div>
      </article>
    </Link>
  );
};

export default UserCard;
  • “componentssharedRenderTag.tsx”:
import React from "react";
import Link from "next/link";

import { Badge } from "@/components/ui/badge";

interface Props {
  _id: string;
  name: string;
  totalQuestions?: number;
  showCount?: boolean;
}

const RenderTag = ({ _id, name, totalQuestions, showCount }: Props) => {
  return (
    <Link href={`/etiquettes/${_id}`} className="flex justify-between gap-2">
      <Badge className="subtle-medium background-light800_dark300 text-light400_light500 rounded-md border-none px-4 py-2 uppercase">
        {name}
      </Badge>

      {showCount && (
        <p className="small-medium text-dark500_light700">{totalQuestions}</p>
      )}
    </Link>
  );
};

export default RenderTag;

However, I get this error:

Error: Hydration failed because the initial UI does not match what was
rendered on the server. See more info here:
https://nextjs.org/docs/messages/react-hydration-error

Expected server HTML to contain a matching in .

I tried many solutions, like replacing the article with a div or passing the suppressHydrationWarning property to the related elements to remove this warning.
However, these solutions didn’t help me fix this issue.

Therefore, I came out with another alternative which is using useRouter instead of Link for the navigation.

Here is my code:

  • “componentssharedRenderTag.tsx”:
"use client";

import { useRouter } from "next/navigation";
import { Badge } from "../ui/badge";
import { Button } from "../ui/button";
interface Props {
  _id: string;
  name: string;
  totalQuestions?: number;
  showCount?: boolean;
}

const RenderTag = ({ _id, name, totalQuestions, showCount }: Props) => {
  const router = useRouter();
  return (
    <Button
      className="flex justify-between gap-2"
      onClick={() => router.push(`/tags/${_id}`)}
    >
      <Badge className="subtle-medium background-light800_dark300 text-light400_light500 rounded-md border-none px-4 py-2 uppercase">
        {name}
      </Badge>
      {showCount && (
        <p className="small-medium text-dark500_light700">{totalQuestions}</p>
      )}
    </Button>
  );
};

export default RenderTag;

Now, the hydration error disappears, but I get this warning in my terminal:

Warning: Only plain objects can be passed to Client Components from
Server Components. Objects with toJSON methods are not supported.
Convert it manually to a simple value before passing it to props.
<… _id={{buffer: …}} name=”next.js”>

Where this error is coming from, especially this part <... _id={{buffer: ...}} name="next.js">?

I guess it is related to my database “tags” collection whis has the following data:

{"_id":{"$oid":"66a476d397749b79a8140e72"},"__v":{"$numberInt":"0"},"createdOn":{"$date":{"$numberLong":"1722054355742"}},"followers":[],"name":"next.js","questions":[]}
{"_id":{"$oid":"66a5164197749b79a8a3258b"},"__v":{"$numberInt":"0"},"createdOn":{"$date":{"$numberLong":"1722095169919"}},"followers":[],"name":"React","questions":[]}

But, why I get this error only when I use useRouter?