Search code examples
cssnext.jschakra-ui

How to handle unsupported CSS properties in Next.js or use @supports directives in Chakra-UI


I am working with a React component that uses different opacity values according to whether there is support for the CSS backdrop-filter directive:

background={(() => {
  const opacity = isBackdropFilterSupported() ? 0.75 : 0.98
  return (
    `linear-gradient(
      180deg, rgba(76, 63, 143, ${opacity}) 62.76%,
      rgba(184, 169, 255, ${opacity}) 100%
    )`
  )
})()}

The issue is that the site is generated server-side using Next.js. CSS.supports('backdrop-filter', 'blur(1px)') returns false on the server, so the value is always false regardless of the client properties.

One solution would be to use CSS like:

.drawer {
  --opacity: 0.75;
  background: linear-gradient(
    180deg, rgba(76, 63, 143, var(--opacity)) 62.76%,
    rgba(184, 169, 255, var(--opacity)) 100%
  );
}
@supports not (backdrop-filter: blur(1px)) {
  .drawer { --opacity: 0.98; }
}

This should be interpreted by the client and avoid the server-side rendering issue, but I've found no indication as to how to integrate such a style into Chakra-UI which this is build on.


Solution

  • I didn't mention it in my original post, but I was getting an error like: Prop id did not match. Server: "toggle--gxfg3t7xwo" Client: "toggle--ki0j10p2l".

    It turns out this means that the DOM generated by the browser doesn't match the DOM generated by Next.js. When this happens Next.js gives up on trying to rehydrate the document which is why I was getting the server-rendered values.

    The solution was to use a hook to determine when the component was mounted (which only happens on the client). That hook looks like:

    export const useMounted = () => {
      // https://www.joshwcomeau.com/react/the-perils-of-rehydration/
      const [hasMounted, setHasMounted] = React.useState(false);
      React.useEffect(() => {
        setHasMounted(true);
      }, []);
      return hasMounted;
    };
    

    The opacity determination then became:

    const hasMounted = useMounted()
    ⋮
    const opacity = hasMounted && isBackdropFilterSupported() ? 0.75 : 0.98