vercel / next.js

The React Framework
https://nextjs.org
MIT License
125.83k stars 26.85k forks source link

styled-components with Babel plugin results in className mismatch on Next.js 5 #3706

Closed exogen closed 5 years ago

exogen commented 6 years ago

Expected Behavior

styled-components should not result in the className mismatch warning from React.

Current Behavior

Warning: Prop `className` did not match. Server: "Header-johx6c-0 bHIeEO" Client: "Header___default-johx6c-0 gGUBlx"

I'm exporting the header component like so:

export default styled(Header)`
  text-align: center;
`;

Notice that styled-components uses the name Header on the server and Header__default on the client. This doesn't happen with Next.js 4, so I assume it has something to do with the way modules are built with the new Universal Webpack setup.

Steps to Reproduce (for bugs)

  1. Use styled-components with next v5.
  2. Use babel-plugin-styled-components with the default displayName setting.
  3. Render a component exported like: export default styled(Component)

(It doesn't matter what the Babel plugin's ssr setting is, nor if the server stylesheet is set up in _document.js. Same thing in every combination.)

Your Environment

Tech Version
next v5.0.0
node v8.9.4
OS macOS
browser Chrome
FiberJW commented 6 years ago

bump.

It only happens in dev for me though.

spencersmb commented 6 years ago

Tim knows this and said its on their list of things todo very soon!

r0b- commented 6 years ago

I got the same problem with a simple h1 component:

const Headline = styled.h1`...`; 

Warning: Prop `className` did not match. Server: "pages__Headline-te1id5-0 bqAPoZ" Client: "pages__Headline-te1id5-0 cIpCqj"

Also only for dev...

(next@5.0.1-canary.16, styled-components@3.2.3)

timneutkens commented 6 years ago

Tim knows this and said its on their list of things todo very soon!

I haven't said anything like this at any point 🤔

Maybe @kitten knows what's going on.

kitten commented 6 years ago

I suppose this is a side effect of using ES module exports for the client and CJS for the server, correct?

I thought we used to have a fix for this in place but apparently we don’t. I’m not sure how safe it’d be to filter out component names that are exposed as “default” but that’d be a solution.

Please note that I would not recommend the “export default styled.div” pattern. I’d guess this is what causes this.

As the result you’d get when applying the above to arrow functions, similarly for our Babel plugin this would mean that your StyledComponent technically doesn’t have a name. So simply writing “const X = ...; export default X” might help here if I’m not mistaken. Even if for “Header” you’d name that variable “Header” or “Container”, for debugging in general this would be more explicit.

spencersmb commented 6 years ago

Finally got a fix for this issue. You need to specify for each environment how babel handles styled-components:

this is my .babelrc file, I have other stuff going on in here but look specifically at "Development". Adding the development under env should fix the error completely.

{
  "env": {
    "test": {
      "presets": [
        [
          "env",
          {
            "modules": "commonjs"
          }
        ],
        "next/babel"
      ]
    },
    "development": {
      "plugins": [
        [
          "styled-components",
          {
            "ssr": true,
            "displayName": true
          }
        ]
      ],
      "presets": "next/babel"
    },
    "production": {
      "presets": "next/babel"
    },
    "server": {
      "presets": [
        [
          "env",
          {
            "modules": "commonjs"
          }
        ],
        "next/babel"
      ]
    }
  },
  "plugins": [
    [
      "styled-components",
      {
        "ssr": true,
        "displayName": true
      }
    ],
    [
      "inline-react-svg"
    ],
    [
      "transform-flow-strip-types"
    ]
  ],
  "retainLines": true
}
kitten commented 6 years ago

@spencersmb alright, this indicates that the recommended next.js usage of the plugin is out of date. We've got a note on why this matters in our docs https://www.styled-components.com/docs/tooling#usage

Quote:

The plugin call order in your .babelrc file matters. If you're using the env property in your babel configuration, then putting this plugin into the plugins array won't suffice. Instead it needs to be put into each env's plugins array to maintain it being executed first. See this for more information.

With this issue linked there: https://github.com/styled-components/babel-plugin-styled-components/issues/78

The next.js example needs an update then, if I'm not mistaken? :smile:

timneutkens commented 6 years ago

@kitten makes sense. The example is actually fine since it doesn't use "env" maybe this should be documented in the example readme though 🤔

https://github.com/zeit/next.js/blob/canary/examples/with-styled-components/

https://github.com/zeit/next.js/blob/canary/examples/with-styled-components/.babelrc

r0b- commented 6 years ago

Hmmm, I don't use different envs in my .baberc (actually it looks like the one Tim referenced) and I don't export the styled component. I just use it in the same file:

const Test = styled.div`
 color: red;
`;

export default () => (
    <>
        ...
            <Test>XXX</Test>
        ...
    </>
);

Strangely enough it happens only on my index page and not on every page load...

iwarner commented 6 years ago

Hiya, I am also getting Warning: Prop className did not match. Server: "sc-ifAKCX jWHVsk" Client: "sc-bdVaJa ebJbtP"

I was npm link components into my nextJS project and this was causing the issue I think. When I bring the component into the project without linking it works, I still get the render flash though.

Also the issue seems to be on a full refresh - when I edit the page the element renders correctly, on full refresh I get the error above again

Hope this helps to dive a little deeper

Babelrc

{
  "plugins": [
    [ "styled-components", { "ssr": true, "displayName": true, "preprocess": false } ],
  ],
  "presets": ["next/babel"]
}

I use style inline method

    const Button = styled.button`
      border: none;
      border-radius: 5rem;
      color: white;
      cursor: pointer;
      display: inline-block;
      font-size: 1rem;
    `
timneutkens commented 5 years ago

I'm assuming this doesn't happen anymore with latest styled-components.

jsardev commented 5 years ago

@timneutkens Actually it still happens. Is it just me or do you guys also still have this warning popping up?

I'm using next@7.02 and styled-components@4.1.1.

revskill10 commented 5 years ago

Any update on this issue ? Seems annoying to everyone using in dev mode.

exogen commented 5 years ago

What about the Babel plugin version @sarneeh @revskill10? I just tried to make a quick repro with the latest version of next, styled-components, and babel-plugin-styled-components and it looks fixed to me. __default isn't appended to the computed name of the exported component anymore.

revskill10 commented 5 years ago

@exogen I'm using "babel-plugin-macros": "^2.4.2", , with antd and always got this warning. The magic is Why client-side still generates their own IDs , because as i see, the only place to generate IDs is in _document.js, which only happens on server side though.

brizental commented 5 years ago

These are my versions:

next 7.0.2 babel-plugin-styled-components 1.1.5 styled-components 4.1.3

...and I'm still having this issue intermittently. Any suggestions?

doitlikepruett commented 5 years ago

I found this to work. Make sure you have babel-plugin-styled-components in your dependencies. The following can go in .babelrc or in package.json


"babel": {
  "env": {
    "development": {
      "presets": ["next/babel"],
      "plugins": [
        ["styled-components", {
          "ssr": true,
          "displayName": true
        }]
      ]
    },
    "production": {
      "presets": ["next/babel"],
      "plugins": [
        ["styled-components", {
          "ssr": true,
          "displayName": true
        }]
      ]
    },
    "test": {
      "presets": [
        ["next/babel", {
          "preset-env": {
            "modules": "commonjs"
          }
        }]
      ],
      "plugins": [
        ["styled-components", {
          "ssr": true,
          "displayName": true
        }]
      ]
    }
  }
}
matteoferigo commented 5 years ago

Issue also occurs with react-jss, where no babel plugin is required. When the server starts everything works fine, then …randomly… if I refresh the page or hot-reloading does it, client rendered className (example: Comp-el-0-1-3) is higher of server rendered className (example: Comp-el-0-1-1). 🤷‍♂️ next: 8.0.3 react-jss: 8.6.1 @babel/core: 7.3.3

izziaraffaele commented 5 years ago

For me everything is fine in production but I get a warning for className mismatch in development. I quickly solved the issue by disabling stylesheets server-side rendering in development.

Here is my _document

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      const initialProps = await Document.getInitialProps(ctx);

      // prevent stylesheets server-side rendering 
      // in case we are not in production
      if (!process.env.NODE_ENV !== 'production') {
        return initialProps;
      }

      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: App => props => sheet.collectStyles(<App {...props} />),
        });

      return {
        ...initialProps,
        styles: (
          <React.Fragment>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </React.Fragment>
        ),
      };
    } finally {
      sheet.seal();
    }
  }
}

Then you just to have to edit your babel config to disable the ssr option in development (note that if you're using a .babelrc file to manage babel's config you'll have to convert it to a .babelrc.js file)

const dev = process.env.NODE_ENV !== 'production';

module.exports = {
  presets: ['next/babel'],
  plugins: [
    ['styled-components', { ssr: !dev, displayName: dev }],
  ],
};