@pandaiolo's answer gave me an idea. I console.log
ged the React
module on different environments (RSC, SSR, CSR), and it looks like useEffect
is not exported for React Server Components. (at least when working with Next.js) Using this knowledge you can determine if the code that's running is invoked from a RSC or during SSR, by simply checking if the React
module has a "useEffect"
property.
import React from "react";
export function isRSC() {
return !("useEffect" in React);
}
export default function Component(props) {
if (typeof window === "undefined") {
if (isRSC()) {
// Invoked from a React Server Component (RSC, server)
} else {
// Invoked during Server-Side Rendering (SSR, server)
}
} else {
// Invoked during Client-Side Rendering (CSR, browser)
}
}
Note: isRSC()
must always be used in combination with typeof window === "undefined"
. The bundlers can infer what typeof window === "undefined"
will evaluate to, and will remove the unreachable code. isRSC()
, on the other hand, is an "unknown function" to them, so the unreachable code will not be removed. Because of this, the RSC and SSR bundles will be the same, while the client bundle will only contain CSR code.
Here's the same helper method but with an additional check, that errors on incorrect usages:
export function isRSC() {
if (typeof window !== "undefined") throw new Error("window must be checked before isRSC() call!");
return !("useEffect" in React);
}