blocks-react-renderer icon indicating copy to clipboard operation
blocks-react-renderer copied to clipboard

[bug]: Cannot pass custom components in server-side components in NextJS

Open wzrdx opened this issue 1 year ago • 10 comments

What version of @strapi/blocks-react-renderer are you using?

"@strapi/blocks-react-renderer": "^1.0.1",
"react": "^18.3.1"

What's Wrong?

I am trying to use the BlocksRenderer inside a server component in my NextJS app. I want to pass a custom component for links.

<BlocksRenderer
    content={article.attributes.Content}
    blocks={{
        link: ({ children, url }) => (
            <Link href={url} target="_blank">
                {children}
            </Link>
        ),
    }}
/>

I get the following error:

⨯ Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server". Or maybe you meant to call this function rather than return it.
  {link: function link}
         ^^^^^^^^^^^^^
    at stringify (<anonymous>)

To Reproduce

Create a NextJS app using the /app router and create a new page (page.tsx).

Render the BlocksRendered inside of it and try to pass a custom component.

Expected Behaviour

The page should render without errors.

wzrdx avatar Aug 26 '24 10:08 wzrdx

I'm having the same issue

castulo avatar Sep 07 '24 19:09 castulo

I created a client component for rendering block content.

example: https://strapi.io/blog/integrating-strapi-s-new-rich-text-block-editor-with-next-js-a-step-by-step-guide

import Image from "next/image";

import {
  BlocksRenderer,
  type BlocksContent,
} from "@strapi/blocks-react-renderer";

export default function BlockRendererClient({
  content,
}: {
  readonly content: BlocksContent;
}) {
  if (!content) return null;
  return (
    <BlocksRenderer
      content={content}
      blocks={{
        image: ({ image }) => {
          console.log(image);
          return (
            <Image
              src={image.url}
              width={image.width}
              height={image.height}
              alt={image.alternativeText || ""}
            />
          );
        },
      }}
    />
  );
}

05August avatar Sep 18 '24 07:09 05August

I created a client component for rendering block content.

example: https://strapi.io/blog/integrating-strapi-s-new-rich-text-block-editor-with-next-js-a-step-by-step-guide

import Image from "next/image";

import {
  BlocksRenderer,
  type BlocksContent,
} from "@strapi/blocks-react-renderer";

export default function BlockRendererClient({
  content,
}: {
  readonly content: BlocksContent;
}) {
  if (!content) return null;
  return (
    <BlocksRenderer
      content={content}
      blocks={{
        image: ({ image }) => {
          console.log(image);
          return (
            <Image
              src={image.url}
              width={image.width}
              height={image.height}
              alt={image.alternativeText || ""}
            />
          );
        },
      }}
    />
  );
}

this while a solution that works, doesnt help where the desire is ISR/SSG for example when generating a blog page that shouldnt need to have the html rendered on the client, I havent been able to find another solution so will probably have to use the MD editor instead.

NaughtyDog6000 avatar Dec 17 '24 09:12 NaughtyDog6000

Also having the same issue. Any update or recommendation from the Strapi team?

It's nice to have a client side option, but for most real use cases, you want this content to be already rendered server side for SEO...

lmammino avatar May 06 '25 10:05 lmammino

Is there any movement here?

nathanaelphilip avatar Jun 02 '25 19:06 nathanaelphilip

I'm having the same issue :c

carloslibardo avatar Jun 03 '25 13:06 carloslibardo

The way I could workaround this issue and still keep using SSR on Blog Post is by wrapping the BlockRenderer and using prose class.

Image

https://github.com/tailwindlabs/tailwindcss-typography?tab=readme-ov-file#element-modifiers

carloslibardo avatar Jun 04 '25 10:06 carloslibardo

The way I could workaround this issue and still keep using SSR on Blog Post is by wrapping the BlockRenderer and using prose class.

Image https://github.com/tailwindlabs/tailwindcss-typography?tab=readme-ov-file#element-modifiers

This is completely unrelated to the issue.

mcer12 avatar Jul 20 '25 14:07 mcer12

My solution was:

  1. create a new client side component for the modified element. (in my case a custom image with a link to enlarge)
  2. assign the component instead of using a new anonymous function. Honestly think this should be the suggested way to do this.
  3. profit

I wasn't able to extract the prop types from the library, so I just copied them in a new type.

So the component looks like this:

"use client";

import Link from "next/link";
import Image from "next/image";

interface ImageProps {
  image: {
    name: string;
    alternativeText?: string | null;
    url: string;
    caption?: string | null;
    width: number;
    height: number;
    formats?: Record<string, unknown>;
    hash: string;
    ext: string;
    mime: string;
    size: number;
    previewUrl?: string | null;
    provider: string;
    provider_metadata?: unknown | null;
    createdAt: string;
    updatedAt: string;
  };
  children?: React.ReactNode;
};

export function CustomImage({ image, children }: ImageProps) {
  return (
    <Link href={image.url} className="photoswipe" data-pswp-width={image.width} data-pswp-height={image.height} target="_blank">
      <Image src={image.url} alt="" width={864} height={648} />
      {children}
    </Link>
  );
}

And the block renderer looks like this

<BlocksRenderer content={post.text} blocks={{ image: CustomImage, }} />

Another option would probably be to make the whole parent template (template with <BlocksRenderer> component) client side. But I didn't try that nor do I think it would be a good solution.

mcer12 avatar Jul 20 '25 15:07 mcer12

Issue has already been discussed there: https://github.com/strapi/blocks-react-renderer/issues/10

=> BlocksRenderer uses React Context and is (logically) annotated with 'use client' directive. That said, it makes it impossible to be rendered server-side (And in the case of that issue, error passing function from server component to this client component makes sense).

The fix would be to remove Context, 'use client' and any usage of hook if any, making server component compatible.

What's your thoughts on it @strapi team? I understand props drilling is annoying, but the drawback of not being able to use SSR seems worse

tretail avatar Oct 31 '25 17:10 tretail