javascriptreactjsnext.jsreact-server-components

Detect React Server Component


Is there a way to determine whether a file or function is being executed as part of a React Server Component rendering, or a classic client component rendering ("use client";)?


Solution

  • Edit: rsc-env package

    Turns out there's already a package that does this, that also supports tree-shaking unreachable code in the server bundle. https://github.com/LorisSigrist/rsc-env

    It's more reliable, since its behavior is defined by conditional exports intended to be used by React, instead of by some missing export that may be re-added in the future versions.

    import { rsc } from "rsc-env";
    
    export default function Component(props) {
      if (rsc) { /* Invoked from a React Server Component (RSC) */ }
      else { /* Invoked during SSR or CSR */ }
    }
    

    Original answer (suboptimal)

    @pandaiolo's answer gave me an idea. I console.logged 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);
    }