remix-run / remix

Build Better Websites. Create modern, resilient user experiences with web fundamentals.
https://remix.run
MIT License
29.99k stars 2.53k forks source link

[Bug]: Errors with Styled Components SSR #1032

Closed numso closed 1 year ago

numso commented 2 years ago

Which Remix packages are impacted?

What version of Remix are you using?

1.0.6

What version of Node are you using? Minimum supported version is 14.

14

Steps to Reproduce

https://codesandbox.io/s/remix-styled-bug-repro-5sv6v

  1. Create a new remix project
  2. Follow the steps detailed in the docs under styling: css-in-js

Expected Behavior

The client should hydrate the styles from styled-component without errors.

Actual Behavior

The style tag and all the style rules flash onto the screen as plain text before the javascript kicks in and removes them, applying the correct styles. Some content is also duplicated on the page (in the codesandbox above it is the footer). We also see an error in the console: Expected server HTML to contain a matching <link> in <head>.

Looks like the docs have you run const styles = sheet.getStyleTags() which returns a string. When react renders this string it html encodes it so you end up with a string in your head instead of a style tag.

Switching that to const styles = sheet.getStyleElement() causes the page to server render the correct styles. But as soon as js kicks in it removes those styles and throws this in the console: Warning: Did not expect server HTML to contain a <style> in <head>

If you instead gather up the styles and markup in a single render, and then inject the styles as a string: markup.replace('</head>', styles + '</head>') things seems to work better but it still throws this warning in the console: Warning: Prop 'className' did not match. Server: "sc-fFeiMQ chKvQT" Client: "sc-gsDKAQ fVbgCo"

alexkuz commented 2 years ago

I've made an example how it could be done (also avoiding double rendering; didn't think into it too much though, probably not an optimal solution), but the last issue with non-matching classes requires something similar to babel-plugin-styled-components, I think.

https://gist.github.com/alexkuz/d91c9ce1380f4b528ebf262ad0e4f959

joshcawthorne commented 2 years ago

Running into similar problems. Hopefully a solution can be found soon, as styled component support is a must for me!

AIEPhoenix commented 2 years ago

add babel-plugin-styled-components as dev dependency. add customer babel.config.js like blow module.exports = { presets: [ "@remix-run/core/babel", ], plugins: [ [ "babel-plugin-styled-components", { "ssr": true } ] ] }; this work for me

joshcawthorne commented 2 years ago

@AIEPhoenix I'm still seeing FOUC even with this added as a dev dependency. Is this something you don't see?

juanpasolano commented 2 years ago

I am getting this from a fresh remix install, not CSS or anything added to it.

uwuru commented 2 years ago

@AIEPhoenix Your suggestion has zero effect for me. Looking through the source and then finding this closed issue for implementing being able to do this seems to backs up it is not implemented? Issue #36

Ultimately it is what is needed though so that class names stay consist across the server and client.

https://styled-components.com/docs/advanced#tooling-setup

In order to reliably perform server side rendering and have the client side bundle pick up without issues, you'll need to use our babel plugin. It prevents checksum mismatches by adding a deterministic ID to each styled component.

I'm sure enabling us to use/configure Babel would help a lot of people with a lot of issues πŸ˜„

I know Remix do not want to "lock in" to things (so they can swap out esbuild later if needed etc) but there's so many tools we can't use as things stand now πŸ˜“ (#717)

I appreciate and like their take too with how they recommend to do styling (and I have really had a good go at doing it that way!) but at work I work day in day out with styled-components so that's where my mind and workflow is most efficient.

numso commented 2 years ago

The styled-components example that was recently added to the dev branch worked well for me: https://github.com/remix-run/remix/tree/1576615c0bdb6222bcddf0ffefe19d2335169299/examples/styled-components

The hydration warning is still present. It looks like the plan is for styled-components to support SSR without needing the babel-plugin. That's being tracked here: https://github.com/styled-components/styled-components/issues/3660

Elijah-trillionz commented 2 years ago

Still no solution for this? @numso I tried following the example you shared but I still get the same error. and besides, it's no different from what I already have in mine.

And I think before they work on styled-components supporting SSR without the babel-plugin they should work on making the babel-plugin work in remix.

Because in next.js there it's just an easy setup with .babelrc, but it's not the case in remix. I don't know if it's already possible in remix some other way, but I have searched all around but no solution.

And the error I get is something like Warning: Prop 'className' did not match. Server: "sc-fFeiMQ chKvQT" Client: "sc-gsDKAQ fVbgCo"

alexkuz commented 2 years ago

I have a somewhat working solution for hydration issue, although I'm not ready to make it public yet. It monkey-patches Remix's esbuild to add a custom plugin which then uses tree-sitter (a fast code parser that supports TS) to parse the source and add component config, same way as styled-components/macro does. For now it only supports styled.tag and styled(...) (which are the most common usecases I believe). Gonna test it more and share it later.

Vashiru commented 2 years ago

The styled-components example that was recently added to the dev branch worked well for me: https://github.com/remix-run/remix/tree/1576615c0bdb6222bcddf0ffefe19d2335169299/examples/styled-components

The hydration warning is still present. It looks like the plan is for styled-components to support SSR without needing the babel-plugin. That's being tracked here: styled-components/styled-components#3660

So we're currently running into an issue with this solution. It works, until we hit a 404 page. The 404 page will be styled, but once we navigate away, our CSS will not be loaded anymore.

Edit: Bit of searching leads me here: https://github.com/mui/material-ui/issues/30436

prxg22 commented 2 years ago

I have a somewhat working solution for hydration issue, although I'm not ready to make it public yet. It monkey-patches Remix's esbuild to add a custom plugin which then uses tree-sitter (a fast code parser that supports TS) to parse the source and add component config, same way as styled-components/macro does. For now it only supports styled.tag and styled(...) (which are the most common usecases I believe). Gonna test it more and share it later.

Any news on that? I'm still having issues to make styled-components work correctly without the babel-plugin and I'm almost sure that the componentId issue is causing it. If I have any styled-component with a chunk of css that is not rendered on the server due some react state and on the client this state is changed, it seems styled-components recalculate some componentIds and recreate the CSS classes for those specific components, leaving its children with the original classes that came from the server. This is happening on a private repo, but I'll try to reproduce it and add an example here.

alexkuz commented 2 years ago

@prxg22 I could publish it this weekend I think, it works for us so far. It would be helpful to look at your example to see if it would help in your case.

prxg22 commented 2 years ago

@alexkuz it happens that digging more I found that what I described only happens with components from our ui-component lib and only if we extend them on styled-components like:

const Nav = styled.nav(({ isMenuOpen }) => `
  ${Button} {
      // some css
   }

   ${isMenuOpen && css`
      ${Button} {
          // after menu opens Nav gets a new CSS class, Button doesn't
       }
   `}
`)

if we create a new styled-component (eg. styled(Button)(({isMenuOpen}) => ...)) and generate on the server as the remix example, it works perfectly.

alexkuz commented 2 years ago

Interesting. I tried to recreate your example and it still doesn't work with my plugin, but if it's written like this, it works fine:

const Nav = styled.nav`
  ${Button} {
      // some css
   }

   ${p => p.isMenuOpen && css`
      ${Button} {
          // after menu opens Nav gets a new CSS class, Button doesn't
       }
   `}
`;
prxg22 commented 2 years ago

I fixed my problem by building our component lib using babel-plugin-styled-components adding the ssr and namespace options

alexkuz commented 2 years ago

@prxg22 just realized there was css missing in your example, that's why it didn't work for me:

const Nav = styled.nav(({ isMenuOpen }) => css`
...
Elijah-trillionz commented 2 years ago

@prxg22 do you mind sharing with us how you fixed it with babel-plugin-styled-components

prxg22 commented 2 years ago

Sorry, @alexkuz, my bad πŸ˜…

@Elijah-trillionz as I said, it was not a fix on the remix app. We are using an ui component lib, which is built using babel. I just add the following line to its babel.config.json:

module.exports = {
 ...,
 plugins: [
    ...,
    ['babel-plugin-styled-components', { ssr: true, namespace: 'some_namespace' }],
  ],
}
Elijah-trillionz commented 2 years ago

@prxg22 ok thanks for the clarification

alexkuz commented 2 years ago

I've published my plugin, try it out:

https://github.com/lukalabs/lukalabs-npm/tree/main/packages/esbuild-inject-plugin https://github.com/lukalabs/lukalabs-npm/tree/main/packages/esbuild-styled-components

If you have any issues please let me know.

@prxg22 fyi

Elijah-trillionz commented 2 years ago

@alexkuz I am having issues installing esbuild-styled-components. It keeps throwing npm errors. It seems to fail when it's about to install node-gyp. Should I paste the error logs here or create an issue on the package repo?

STAYWICKED commented 2 years ago

Chat me on Whatsapp+2347026260392

On Mon, May 9, 2022, 7:39 PM Elijah Trillionz @.***> wrote:

@alexkuz https://github.com/alexkuz I am having issues installing esbuild-styled-components https://github.com/lukalabs/lukalabs-npm/tree/main/packages/esbuild-styled-components. It keeps throwing npm errors. Should I paste the error logs here or create an issue on the package repo?

β€” Reply to this email directly, view it on GitHub https://github.com/remix-run/remix/issues/1032#issuecomment-1121445294, or unsubscribe https://github.com/notifications/unsubscribe-auth/AZCCBUQKWRXF7LF2ABU7CMDVJFLVJANCNFSM5J72OP6Q . You are receiving this because you are subscribed to this thread.Message ID: @.***>

alexkuz commented 2 years ago

@Elijah-trillionz you may need to upgrade you Node. Try to find a similar issue on https://github.com/tree-sitter/tree-sitter, that's probably where it comes from.

slavik0329 commented 2 years ago

@alexkuz Gets rid of errors on dev and production, thanks!

maurerbot commented 2 years ago

I can't get any of this to work :(

markdalgleish commented 1 year ago

I've opened a PR to the Remix examples repo that addresses hydration errors: https://github.com/remix-run/examples/pull/311.

As others mentioned, the trick is to run your styled components through the Babel CLI first using babel-plugin-styled-components and then importing the generated JS into your Remix app. It's a bit clunky but this is similar to how many other tools are currently supported in Remix (e.g. Sass).