I'm using Next.JS 13.4.19 with the new app
folder to enable React Server Components, and running into issues trying to use client sub-components (i.e. <ClientComponent.SubComponent \>
) inside a server component.
ClientTest.jsx
is a Client component with sub-component attached (pretend this has some state or other reason to be client component)
'use client'
export default function ClientTest() {
return <div>ClientTest</div>
}
ClientTest.Item = function ClientTestItem() {
return <div>ClientTest.Item</div>
}
page.jsx
is a React Server Component (allows access to db or file system)
import ClientTest from './ClientTest'
export default function Page() {
// ClientTest.Item is undefined, and the following line errors with:
// Unsupported Server Component type: undefined
return (
<ClientTest.Item />
);
}
it seems the main component <ClientTest />
works fine, but the attached <ClientTest.Item />
is undefined :(
https://codesandbox.io/p/sandbox/currying-grass-wsvtn4?file=/app/page.tsx
Possible workaround would be to simply avoid sub-components in the server compoonent by making another wrapper client component (importing ClientTest.Item in a client component works fine) but I would greatly prefer if this could be fixed through some magic webpack configuration? or maybe it's a bug in next.js?
For the record; Importing server sub-components in server components works fine, as does importing client sub-components in client components.
Edit:
I am working on a component library which uses the sub-component pattern quite a bit, so it makes a big difference to me if this is a "working as intended" part-of-the-design of server components, or a "current implementation limitation"/bug.
If it is a permanent design limitation we might need to rethink our entire naming scheme and architecture to avoid terrible DX, but I haven't found anything conclusive about this in the react docs.
A colleague of mine found another (slightly hacky) workaround that seems to make Next.js 14.2+ happy; simply wrap the root client component as a server component. We made a utility function for this since we have a few of them :)
utils/wrapClientComponent.js
(no 'use client' in this file)
export default function wrapClientComponent(ClientComponent) {
return forwardRef((props, ref) => {
return <ClientComponent {...props} ref={ref} />;
});
}
This way we don't have to resort to Root
naming (no 'use client' in this file):
components/ClientTest/index.js
export default Object.assign(wrapClientComponent(ClientTest), {
Item: ClientTestItem
});
And the client components are available for use in server components as you'd expect.
components/ClientTest/ClientTest.js
'use client'
export default function ClientTest() {
return <div>ClientTest</div>
}
components/ClientTest/ClientTest.Item.js
'use client'
export default function ClientTestItem() {
return <div>ClientTest.Item</div>
}