Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hydration errors when using @emotion/react within a design system library inside nextjs > 13.x (old page router) #3153

Open
glomotion opened this issue Jan 24, 2024 · 5 comments

Comments

@glomotion
Copy link

glomotion commented Jan 24, 2024

Hey there! 馃憢
So I maintain a design system library (@biom3/react), which I have built atop of @emotion/react (which is an amazing library, thank you kindly for maintaining it all these years!! 馃檹).

Current behavior:

For the longest time, our library has been working flawlessly inside of nextjs (which is one of it's primary use-cases).

Lately I have noticed that with newer builds of nextjs (not using the new app router yet mind you), many of our simplest DS components (eg <Box /> which essentially just renders a <div> under the hood using { jsx } from '@emotion/react') cause nextjs hydration errors - when they never used to.

What's got me stumped, is inside the <Box /> component src, i'm logging out the css object that's getting applied to the basic <div> (as a css object) - its identical when logged out inside the nextjs server console, and inside the JS console. I don't understand why emotion is creating these styles twice, as 2 seperate style statements.

Is there some sort of configuration thing that I am missing here?

I noticed that this nextjs docs page mentions that emotion compatibility is being worked on right now, but i understood that to mean with regard to the new app router - not the old page style router. Is that correct?

To reproduce:

Simply run this (very simple) stackblitz: https://stackblitz.com/edit/stackblitz-starters-xgouws?description=The%20React%20framework%20for%20production&file=pages%2Findex.tsx&title=Next.js%20Starter

note the hydration error:

Warning: Prop `className` did not match. Server: " Box css-lazpp8-O" Client: " Box css-8ty08s-V"

Expected behavior:

There should be no hydration errors.

Environment information:

  • react version: 18.2.0
  • @emotion/react version: 11.11.3

The source for the simple <Box /> component looks like:

export function Box<RC extends ReactElement | undefined = undefined>({
  testId,
  sx = {},
  children,
  rc = <div />,
  domRef,
  className,
  ...domProps
}: BoxWithRCAndDomProps<RC>) {
  const css = useConvertSxToEmotionStyles(sx);
  console.log('@@@@@2 BOX converted sx => css obj', css);
  return cloneElementWithCssProp(rc, {
    ...domProps,
    ...(testId ? { 'data-testid': testId } : {}),
    ...(domRef ? { ref: domRef } : {}),
    className: `${className ?? ''} Box`,
    css,
    children,
  });
}

then the cloneElementWithCssProp function looks like:

import { jsx } from '@emotion/react';

export const cloneElementWithCssProp = (
  element: ReactElementWithRef,
  props: Record<string, unknown>,
) => {
  // EMOTION_TYPE handles non-React elements (native JSX/HTML nodes)
  const clonedElement =
    // eslint-disable-next-line no-underscore-dangle
    element.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__ || element.type;

  const clonedProps = {
    key: element.key,
    ref: element.ref,
    ...element.props,
    ...props,
  };

  if (props.css || element.props.css) {
    clonedProps.css = [element.props.css, props.css];
  }

  return jsx(clonedElement, clonedProps);
};

I wonder, could you help me understand what I might be able to do to resolve these hydration errors?

@glomotion
Copy link
Author

FWIW - I have also tested not dynamically creating the css inside of the Box component source, and instead simply applying a static css object of:

{  width: "200px", height: "200px", background: "gold" }

But this still creates a hydration error. So this leads me to believe that somehow there is a misconfiguration issue within emotion (either when i compile the DS library, or when i consume it inside of the demo stackblitz nextjs application)

@glomotion
Copy link
Author

Have also tried to setup an emotion cache - in order to avoid hydration errors, with no luck. :(
https://stackblitz.com/edit/stackblitz-starters-mdzn9s?description=The%20React%20framework%20for%20production&file=pages%2F_app.tsx&title=Next.js%20Starter

@glomotion
Copy link
Author

Could be related: #2926

@glomotion
Copy link
Author

glomotion commented Jan 28, 2024

How interesting. So I may have tracked down what is causing this issue. The @biom3/react package is built using TSUP, and minified using terser. Disabling minification (and hence terser) - clears up the issue.

FWIW - I've worked out that It's the mangle configuration of terser, which causes the problem.
With the following configuration, @emotion/react seems to build correctly, and the hydration errors go away. 馃帀

import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['.src/index.ts'],
  sourcemap: true,
  clean: true,
  format: ['cjs', 'esm'],
  minify: 'terser',
  terserOptions: {
    compress: true,
    mangle: false,
    format: {
      comments: false,
    },
  },
});

So it would seem that terser's mangle is doing something that does not quite agree with how @emotion/react works internally. 馃

@glomotion
Copy link
Author

Happy for you guys to close this issue out, if this is expected RE terser / mangle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant