[bug]: Cannot pass custom components in server-side components in NextJS
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.
I'm having the same issue
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 || ""}
/>
);
},
}}
/>
);
}
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.
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...
Is there any movement here?
I'm having the same issue :c
The way I could workaround this issue and still keep using SSR on Blog Post is by wrapping the BlockRenderer and using prose class.
https://github.com/tailwindlabs/tailwindcss-typography?tab=readme-ov-file#element-modifiers
The way I could workaround this issue and still keep using SSR on Blog Post is by wrapping the BlockRenderer and using prose class.
https://github.com/tailwindlabs/tailwindcss-typography?tab=readme-ov-file#element-modifiers
This is completely unrelated to the issue.
My solution was:
- create a new client side component for the modified element. (in my case a custom image with a link to enlarge)
- assign the component instead of using a new anonymous function. Honestly think this should be the suggested way to do this.
- 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.
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
https://github.com/tailwindlabs/tailwindcss-typography?tab=readme-ov-file#element-modifiers