To get around the issue of using client side components in server side pages I decided to build a client side wrapper to nest these components within on server side pages. So then I can make all my API calls and fetch my data on the server and pass it down to the components.
Is this a good convention? the code is below:
ClientSideWrapper:
"use client";
interface ClientWrapperProps {
children: React.ReactNode;
}
export const ClientWrapper = ({ children }: ClientWrapperProps) => {
return <>{children}</>;
};
page.tsx
export default async function ResourcesPage({
params,
}: {
params: { slug: string };
}) {
const resources = await getResources();
console.log("Resources", JSON.stringify(resources));
const tags = await getContentfulTags();
const productTags = filterAndMapTags(tags, "Product:");
const industryTags = filterAndMapTags(tags, "Industry");
const applicationTags = filterAndMapTags(tags, "Application");
const resourceTypeTags = filterAndMapTags(tags, "ResourceType");
const resourcesByType: { [key: string]: TransformedResource[] } = {};
resourceTypes.forEach((resourceType) => {
resourcesByType[resourceType] = resources.filter(
(item) => item.resourceType === resourceType
);
});
const accordionData = [
{
title: "Drop-in Specs",
data: resourcesByType["Drop-in Specs"],
results: resourcesByType["Drop-in Specs"].length,
},
{
title: "Installation Manuals",
data: resourcesByType["Installation Manuals"],
results: resourcesByType["Installation Manuals"].length,
},
{
title: "Brochures",
data: resourcesByType["Brochures"],
results: resourcesByType["Brochures"].length,
},
{
title: "Declarations of Perfromance",
data: resourcesByType["Declarations of Performance"],
results: resourcesByType["Declarations of Performance"].length,
},
{
title: "Application Sheets",
data: resourcesByType["Application Sheets"],
results: resourcesByType["Application Sheets"].length,
},
{
title: "Product Sheets",
data: resourcesByType["Product Sheets"],
results: resourcesByType["Product Sheets"].length,
},
{
title: "Technical Notes",
data: resourcesByType["Technical Notes"],
results: resourcesByType["Technical Notes"].length,
},
{
title: "DOTs/MTOs",
data: resourcesByType["DOTs/MTOs"],
results: resourcesByType["DOTs/MTOs"].length,
},
{
title: "Tools",
data: resourcesByType["Tools"],
results: resourcesByType["Tools"].length,
},
];
const dropdownItems = [
{ title: "Product", data: productTags },
{ title: "Resource Type", data: resourceTypeTags },
{ title: "Industry", data: industryTags },
{ title: "Application", data: applicationTags },
];
return (
<div className="overflow-hidden px-6 pb-6 pt-3 md:px-3 sm:px-2 xs:px-0">
<StaticBreadCrumb
mappableProps={fakeObjectForBreadCrumb}
headerImage={false}
/>
<h1 className="mb-4 mt-3 sm:mb-3">Resources</h1>
<div className="flex flex-col">
<div className="w-100 relative flex flex-row justify-start border-b-[1px] border-softGrey pb-3 md:flex-col md:pb-4">
<h4 className="mr-1 text-mdLg text-softGrey ">Filter</h4>
<ClientWrapper>
<div className="relative">
<div className="no-scrollbar absolute ml-3 flex w-[100%] flex-row md:left-[0px] md:ml-[0px] md:mt-1 smMd:mt-[-25px] smMd:block">
<div className="flex flex-row smMd:invisible">
{dropdownItems.map((item, index) => (
<div key={index} className="z-50 ml-3 md:ml-[0px] md:mr-1">
<Dropdown title={item.title}>
<ResourceDropDown data={item.data} />
</Dropdown>
</div>
))}
</div>
<div className="lg:invisible md:invisible smMd:visible">
<MobileResourceDropDown dropdownItems={dropdownItems}>
<ResourceDropDown data={productTags} />
</MobileResourceDropDown>
</div>
</div>
</div>
</ClientWrapper>
</div>
<div className=" mt-3 flex flex-col px-3 smMd:px-2 sm:px-0">
{accordionData.map((item, index) => {
return (
<ClientWrapper key={index}>
<Accordion
title={item.title}
results={item.results}
key={index}
>
<div className={`${index === 1 ? "mt-3" : "mt-1"}`}>
<AccordionResource accordionData={item.data} />
</div>
</Accordion>
</ClientWrapper>
);
})}
</div>
</div>
</div>
);
}
Yes, that seems to be the recommended pattern for nesting server components inside client components, as importing server components inside client components is unsupported:
Recommended Pattern: Passing Server Components to Client Components as Props
Instead, when designing Client Components you can use React props to mark "slots" for Server Components.
The Server Component will be rendered on the server, and when the Client Component is rendered on the client, the "slot" will be filled in with the rendered result of the Server Component.
A common pattern is to use the React
children
prop to create the "slot". We can refactor<ExampleClientComponent>
to accept a genericchildren
prop and move the import and explicit nesting of<ExampleClientComponent>
up to a parent component.
This is exactly what is happening in your code:
ResourcesPage
is rendered on the server, including ClientWrapper
's children
props, but not ClientWrapper
s themselves.
Once on the client, ClientWrapper
s are rendered, with the children
prop containing the rendered result of the Server Component already.