reactjswebpacknext.jsnext.js13react-server-components

How can I use a react client sub-component inside a React Server Component?


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.


Solution

  • 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>
    }