Detect server/client component

Anyone know of a way to detect at runtime whether a component is running as a server component or as a client component? I'm building a custom Cloudflare Image component which has additional functionality when running on the server, and ideally I'd like to be able to import one component into a client or a server component and have it use the server component "version" when imported into a server component. I don't care too much about having to write <CloudflareImageClient> or <CloudflareImageServer>, except that I might have a component which is sometimes a server component and sometimes a client component depending on what imports it. Here's an example, where <Hero> may run as either a server component or a client component.
export const Hero: FC = () => {
// When Hero is imported into a server component, I'd like this to use <CloudflareImageServer>.
// When Hero is imported into a client component, I'd like this to use <CloudflareImageClient>.
return <CloudflareImage></CloudflareImage>
}
export const Hero: FC = () => {
// When Hero is imported into a server component, I'd like this to use <CloudflareImageServer>.
// When Hero is imported into a client component, I'd like this to use <CloudflareImageClient>.
return <CloudflareImage></CloudflareImage>
}
// home/page.tsx
export const Page: NextPage = () => {
<Hero></Hero>
}
// home/page.tsx
export const Page: NextPage = () => {
<Hero></Hero>
}
// components/some-client-component.tsx
'use client'

export const SomeClientComponent: FC = () => {
<Hero></Hero>
}
// components/some-client-component.tsx
'use client'

export const SomeClientComponent: FC = () => {
<Hero></Hero>
}
I don't think it's sufficient to check typeof window === undefined, since that'll be true for client components when they initially run on the server. I'm also open to hearing that this is fundamentally impossible based on how importing client and server components works in Next.js. It feels like that's probably the case.
12 Replies
_ankri
_ankri16mo ago
I am sorry that I don't have an answer to your question, but I was curious and tried it myself. My idea was to "hack" my way around it by declaring Hero as a ErrorBoundary. My thought was to render ServerComponent when the ErrorBoundary catches something, otherwise a ClientComponent. But Class components are Client Components and need a "use client" .
bakdaddy
bakdaddy16mo ago
I feel like there you might find your answer https://nextjs.org/docs/getting-started/react-essentials If I understand correctly you want to have more features if it's server component? You can nest server components inside client components and have always more features
whatplan
whatplan16mo ago
ideally I'd like to be able to import one component into a client or a server component and have it use the server component "version" when imported into a server component.
this is almost certainly not possible due to that fact that both client+server components are initially render on the server, and I would say thats a good thing client and server components are fundamentally different, having access to very different capabilities by design, if you have a component who's behavior changes depending on if its a server or client component you actually just have 2 components, a client and server version and they should be made separate I get its not as clean, but its much more clear to someone using the component what is actually getting run and where and with what functionality
mattddean
mattddean16mo ago
@whatplan (Rustular Devrel) I agree that it could be a footgun to have a component that behaves differently depending on what imports it, but I don’t see any other way to accomplish this type of functionality. I’m not suggesting any black magic; <CloudflareImage> would be a wrapper that asks react if it’s running as a client or server component, and render either a separate <CloudflareImageClient> or <CloudflareImageServer> component depending. I can write this kind of code right now, but only at the “use client” boundary, not any deeper in the tree. It doesn’t feel at odds with the model to me, as it’s a common pattern to write components that are either client or server components depending on who imports them, like my <Hero> example.
whatplan
whatplan16mo ago
I’m not suggesting any black magic; <CloudflareImage> would be a wrapper that asks react if it’s running as a client or server component, and render either a separate <CloudflareImageClient> or <CloudflareImageServer> component depending.
Im confused with what is different between CloudflareImageClient and CloudflareImageServer, Does the server version use async/await or server only code? Does the client version use hooks or dom/window stuff?
mattddean
mattddean16mo ago
The server version uses async/await, then renders the client version. The client version uses a custom loader, passing a function to next/image, so it’s a client component.
whatplan
whatplan16mo ago
Having two versions seems like the correct thing to do here (and the only way to be honest again dont think its possible to determine client/server). Could you rephrase your perspective on why wouldn't just using CloudflareImageClient and CloudflareImageServer where you wouldve used CloudflareImage work? I guess I just don't understand the usecase since the server version ends up rendering the client version anyway. I assume its doing some kind of ssr-like data fetching but then in my opinion thats just further reason to make them seperate components, as they have distinctly different functionality
mattddean
mattddean16mo ago
The problem is the middleman Hero component. Sometimes it’ll be a server component, and other times it’ll be a client component. It all depends whether it’s rendered in a client or server component. But within the Hero component, I can’t know whether I’m in the server tree or the client tree.
whatplan
whatplan16mo ago
then you should have 2 versions of hero or pass the CloudflareImage as a prop
mattddean
mattddean16mo ago
Yeah, this is a solution, but it gets less realistic the further into the tree your are. The spirit of the “use client” directive is that we only have to care about the server/client boundary once, not at every component all the way down the tree.
whatplan
whatplan16mo ago
exactly, you declare "use client" once and from then on your in client world your trying to do potentially server stuff (async/await) in client world- no can do because again back to the crux of the issue you cant determine this at runtime the whole point of "use client" is the bundler knows at build time what components are server and what arent
mattddean
mattddean16mo ago
The bundler knows at build time whether Hero is a server or client component. It may be a server component in some places and a client in others. In either case, the bundler infers its type based on where it’s imported I actually think that all that would be required to make this work is if the bundler returned null when importing a server component into a client component. Then we could do ‘return <CloudflareImageServer /> ?? <CloudflareImageClient />’
Want results from more Discord servers?
Add your server