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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server Side Rendering #124

Closed
geelen opened this issue Oct 20, 2016 · 37 comments
Closed

Server Side Rendering #124

geelen opened this issue Oct 20, 2016 · 37 comments
Milestone

Comments

@geelen
Copy link
Member

geelen commented Oct 20, 2016

Thought I'd open an issue to kick off the discussion. It's already possible because of the way we build on top of Glamor, but we haven't exposed it as an API. But basically, if you did this:

import styleSheet from 'styled-components/lib/models/StyleSheet'

/* before each render */
styleSheet.flush()

/* after each render */
styleSheet.rules().map(rule => rule.cssText).join('\n')

Then you should get the chunk of CSS you need for each request. Can someone with a server-rendered setup take a look and confirm this works, and maybe show how they'd be invoking it? I'd be happy enough to export something like:

import { serverStylesheet } from 'styled-components'

/* before each render */
serverStylesheet.reset()

/* after each render */
serverStylesheet.getCSS()

What do people think?

@pheuter
Copy link

pheuter commented Oct 22, 2016

I'll gladly help test this out! We currently avoid using styled components for most styles because of the unclear path to support SSR, would be great to move the inline styles into styled components if we can generate the CSS on the server.

Here is what I have setup so far:

import styleSheet from 'styled-components/lib/models/StyleSheet';

styleSheet.flush();

let markup = renderToString(
  <Root store={store} Router={ServerRouter} routerProps={{ location, context }} />
);

console.log(styleSheet.rules());

Unfortunately, I'm getting back an empty array []. I can confirm that one of the components nested inside Root is wrapped by styled and generates a className when the client loads.

@diegohaz
Copy link
Member

I'm currently implementing SSR on a project and I managed to make this work without styleSheet.flush() (if I call this, the result is the same as @pheuter), only the styles from injectGlobal weren't included.

Once I finish it I will post the complete code and a demo here. (I'm trying to make the app work completely without javascript enabled on client).

@mxstbr
Copy link
Member

mxstbr commented Oct 24, 2016

only the styles from injectGlobal weren't included.

Huh, that's interesting. I think I might accidentally use another styleSheet for that? /cc @geelen

@diegohaz
Copy link
Member

Demo: https://arc.diegohaz.com (disable javascript, even the form works).
Code: https://github.com/diegohaz/arc/blob/universal-redux/src/server.js#L74

It doesn't work with injectGlobal (I removed).

The serverStylesheet.getCSS() API would be nice. 👍

@lucasavila00
Copy link

lucasavila00 commented Oct 29, 2016

I noticed something.
I'm using Grid from 'grid-styled' and using styleSheet.rules().map(rule => rule.cssText).join('\n') seems to go just one level deep. It never rendered code of Grid, but rendered my code that used media queries.

code is like this:

const Wrapper = styled(Grid)`
  position: fixed;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 40px;
  z-index: 0;
  top: 90px;
  z-index: 1;
  width: 100%;
  left: 0px;
  @media screen and (min-width: 52em){
    left: 16.66%;
  }
  @media screen and (min-width: 64em){
    left: 25%;
  }
`

Invoked as <Wrapper xs={1} sm={1} md={2/3} lg={1/2}>{children}</Wrapper>

css gets rendered as

.iMsaJZ { 
  position: fixed;
  display: -webkit-box;display:flex;display:-webkit-flex;display:-ms-flexbox;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  -moz-justify-content: center;
  -webkit-justify-content: center;
  justify-content: center;
  -webkit-box-align: center;
  -ms-flex-align: center;
  -webkit-align-items: center;
  align-items: center;
  height: 40px;
  z-index: 0;
  top: 90px;
  z-index: 1;
  width: 100%;
  left: 0px;
 }
@media screen and (min-width: 52em){
  .iMsaJZ { 
    left: 16.66%;
  }
}
@media screen and (min-width: 64em){
  .iMsaJZ { 
    left: 25%;
  }
}

The element receives the correct classname on SSR (class="iMsaJZ hqknNf"), wich are two classes, one for my code(iMsaJZ) and one for Grid's(hqknNf).
But there is no mention to the classname designed to Grid in the initial server response.
When I turn JS on then Grid's code appears to the browser.

.hqknNf { 
  -webkit-box-sizing: border-box; 
  box-sizing: border-box;
  display: inline-block;
  vertical-align: top;
  padding-left: 0px;
  padding-right: 0px;
  width: 100%;
  width: 100%;
 }
@media screen and (min-width: 40em) {
  .hqknNf { 
    width: 100%;
  }
}
@media screen and (min-width: 52em) {
  .hqknNf { 
    width: 66.66666666666666%;
  }
}
@media screen and (min-width: 64em) {
  .hqknNf { 
    width: 50%;
  }
}

Inlining code solves the problem and ssr can be used but it breaks composition :(
I hope I made myself clear :)

Also to notice that while profiling on my dev pc (windows 10) I got 0 ~ 1ms latency calling styleSheet.rules().map(rule => rule.cssText).join('\n')
That's nice!

@gbozee
Copy link

gbozee commented Nov 2, 2016

Based on @pheuter test, I get the same result,(the class is injected in the string markup) but not calling stylesheet.flush() before calling styleSheet.rules() produces TypeError: Cannot read property 'cssRules' of undefined

@aesopwolf
Copy link

aesopwolf commented Nov 3, 2016

Ah, this is so great. I was able to get it working with gatsby in no time

  render () {
    const styles = styleSheet.rules().map(rule => rule.cssText).join('\n');

    return (
      <html lang="en">
        <head>
          <style>
            {styles}
          </style>
        </head>
        <body>
          <div id="react-mount" dangerouslySetInnerHTML={{ __html: this.props.body }} />
          <script src={prefixLink(`/bundle.js?t=${BUILD_TIME}`)} />
        </body>
      </html>
    )
  }

I'm all for exposing a simpler api via serverStylesheet.getCSS()

@kitten
Copy link
Member

kitten commented Nov 9, 2016

@gbozee I ran into that problem. Turns out when (f.e.) webpack 2 is used, it loads the es bundle, due to the package.json js:next/module fields.

Until there is an API for the stylesheet that we can use, you should work around it using webpack's resolve.alias to load the lib folder instead.

MoOx added a commit to MoOx/phenomic that referenced this issue Nov 16, 2016
…box.

Now if you use [Glamor](https://github.com/threepointone/glamor/) to
write your style, static rendering will take that into account and will
prerender styles for you. Nothing to setup. It’s even injecting glamor
ids if you want to rehydrate on startup. See [glamor server
documentation](https://github.com/threepointone/glamor/blob/master/docs/
server.md) to setup hydratation (you will need to handle this yourself
in your ``scripts/phenomic.browser.js``.
Since [styled-components](https://styled-components.com/) [use Glamor
under the
hood](styled-components/styled-components#124)
, this will work for this library as well.

Ref #864
MoOx added a commit to MoOx/phenomic that referenced this issue Nov 16, 2016
…box.

Now if you use [Glamor](https://github.com/threepointone/glamor/) to
write your style, static rendering will take that into account and will
prerender styles for you. Nothing to setup. It’s even injecting glamor
ids if you want to rehydrate on startup. See [glamor server
documentation](https://github.com/threepointone/glamor/blob/master/docs/
server.md) to setup hydratation (you will need to handle this yourself
in your ``scripts/phenomic.browser.js``.
Since [styled-components](https://styled-components.com/) [use Glamor
under the
hood](styled-components/styled-components#124)
, this will work for this library as well.

Ref #864
@diegohaz
Copy link
Member

diegohaz commented Nov 16, 2016

I think I might accidentally use another styleSheet for that?

@mxstbr I'm interested in solving this (injectGlobal). Can you give me a way?

@MoOx
Copy link

MoOx commented Nov 17, 2016

Please get in touch when somebody start to handle this. I would like to get this to work out of the box in Phenomic (I just integrated Glamor and Aphrodite rendering and would like to cover all populars CSS in JS solutions).
I was thinking glamor integration will be enough but @philpl just told me it won't work :(

@chiefjester
Copy link
Member

@aesopwolf did you get the same problem as @diegohaz of not having styles included from injectGlobal calls?

I think its a general consensus that this is working out of the box. The only minor caveat is styles from injectGlobal isn't included. But fixing that is trivial I guess?

MoOx added a commit to MoOx/phenomic that referenced this issue Nov 17, 2016
@aesopwolf
Copy link

@thisguychris I just ran a quick test with injectGlobal and I didn't get the same problem. My document head included a single style tag with everything expected.

Input:

const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;

injectGlobal`
  body {
    background: red;
  }
`;

Output:

<style>
    .kZQsGZ {
        font-size: 1.5em;
        text-align: center;
        color: palevioletred;
    }
    body {
        background: red;
    }
</style>

@geelen
Copy link
Member Author

geelen commented Nov 18, 2016

Yeah the implementation for injectGlobal uses the same instance of StyleSheet under the hood so what @aesopwolf is getting looks right..

@chiefjester
Copy link
Member

@geelen so do we get a green light for the exposed api? aka serverStylesheet.getCSS()?

Also, @aesopwolf would you mind giving me a test repo of your SSR to test my PR with?

@mxstbr
Copy link
Member

mxstbr commented Nov 18, 2016

I would love an exposed API, would love for you to look into that @thisguychris!

@diegohaz
Copy link
Member

You guys are right, injectGlobal is working. It was a problem in my code.
I was using it inside componentDidMount (which doesn't run in ssr).

Sorry for the mess.

@diegohaz
Copy link
Member

Now this repo is working with styled-components, injectGlobal, SSR and everything: https://github.com/diegohaz/arc/tree/fullstack

@thisguychris

@tomazy
Copy link

tomazy commented Nov 18, 2016

It would be great if .flush() worked. The problem may be somewhere here:
https://github.com/styled-components/styled-components/blob/master/src/vendor/glamor/sheet.js#L88

     this.sheet  = {
        cssRules: [],
        insertRule: rule => {
          const serverRule = { cssText: rule }
          this.sheet.cssRules.push(serverRule)              
          return {serverRule, appendRule: (newCss => serverRule.cssText += newCss)}  
        }
      }

flush() on server does this:

this.sheet.cssRules = []

So after flushing appendRule will be appending text to a dangling serverRule that is not in this.sheet.cssRules anymore.

EDIT:
ComponentStyle holds the ref to the dangling serverRule here:
https://github.com/styled-components/styled-components/blob/master/src/models/ComponentStyle.js#L44

chiefjester added a commit to chiefjester/styled-components that referenced this issue Nov 18, 2016
- fixes styled-components#124
- remap flush to reset()
- convenience function getCSS() for SSR
chiefjester added a commit to chiefjester/styled-components that referenced this issue Nov 18, 2016
- reference issue styled-components#124 and the PR itself
@dignifiedquire
Copy link

I am trying to use hedron, but it seems the styles from it are not present. But my custom styles are rendered fine. Any ideas why this is happening/how to work around?

chiefjester added a commit to chiefjester/styled-components that referenced this issue Nov 23, 2016
- fixes styled-components#124
- remap flush to reset()
- convenience function getCSS() for SSR

update CHANGELOG.md

- reference issue styled-components#124 and the PR itself

removed prototype

changed to ServerStyleSheet as Pascal Case

- as per [@diegohaz](https://github.com/diegohaz) recommendation

remove mutation and capitlization in export

renamed serverStyle to styleSheet and refactor

- now just extending StyleSheet as pointed out by geeleen
- renamed flush as reset
- added convenience function to call rules.map...
@clempat
Copy link

clempat commented Nov 29, 2016

@jorilallo I had quite similar issue. In my case I solved it by adding 'styled-components/lib/models/StyleSheet' in externals in webpack 2.

externals: {
  'styled-components/lib/models/StyleSheet': 'commonjs styled-components/lib/models/StyleSheet',
}

it was not there because I was adding only the node_modules root folder to externals.

@codepunkt
Copy link
Member

codepunkt commented Dec 4, 2016

I'm not sure why this is so hard using styled-components. It's already possible with glamor, which it seems this is based on, right? Can't we just simply use the underlying glamor API or parts of it and re-expose?

@geelen Concatenating the resulting cssText is fine, but it should also be rehydrated on server-side to prevent the client from injecting the styles again.

@chiefjester
Copy link
Member

@code-punkt see #214 :)

@SachaG
Copy link
Contributor

SachaG commented Dec 30, 2016

@aesopwolf thanks for the code snippet, worked with Gatsby for me too. But I think it should be <style dangerouslySetInnerHTML={{ __html: styles }} />, otherwise special characters will get escaped out.

@eXon
Copy link

eXon commented Dec 30, 2016

@tomazy You can always remove your SSR style block once the client-side rendering is done. That way, your page will look great while the app is loading and you will not get duplicate styles.

ReactDOM.render(<App />, document.getElementById('app'));
document.getElementById('ssr-style').remove();

@codepunkt
Copy link
Member

@eXon yes, but styled-components could also just stop injecting it on the client-side again so you don't have to hack stuff like that together ;-)

See #214 for more discussion about this.

@teonik
Copy link

teonik commented Dec 31, 2016

@tomazy I used something similar to remove the server rendered critical-css block when react and styled-components kicked in on the client side.
The problem with this approach is that it will also remove any styles you injected with injectGlobals,Those styles, if rendered on the server, will end up in the ssr block.
It is also feels like a dirty hack compared to the rest of the experience offered by styled-components 😛

Of course there are simpler ways to stick some global css in the pre-rendered page's head section but the point is that it would be nice to have proper support for it.

chiefjester added a commit to chiefjester/styled-components that referenced this issue Jan 10, 2017
- fixes styled-components#124
- remap flush to reset()
- convenience function getCSS() for SSR

update CHANGELOG.md

- reference issue styled-components#124 and the PR itself

removed prototype

changed to ServerStyleSheet as Pascal Case

- as per [@diegohaz](https://github.com/diegohaz) recommendation

remove mutation and capitlization in export

renamed serverStyle to styleSheet and refactor

- now just extending StyleSheet as pointed out by geeleen
- renamed flush as reset
- added convenience function to call rules.map...
@mxstbr mxstbr modified the milestone: v2.0 Jan 17, 2017
@kristojorg
Copy link
Contributor

PSA: I was seeing TypeError: Cannot read property 'cssRules' of undefined when using this line of code:

const styles = styleSheet.rules().map(rule => rule.cssText).join('\n');

The fix: If you are seeing this as well, make sure you are rendering at least one styled component in your app.

For me, I was just installing styled-components for the first time, and hadn't used one yet, and was therefore seeing this error.

@kristojorg
Copy link
Contributor

I have submitted a PR to fix this issue here. Hope this helps some!

@karthikiyengar
Copy link

I am trying to use hedron, but it seems the styles from it are not present. But my custom styles are rendered fine. Any ideas why this is happening/how to work around?

@dignifiedquire - Could you please tell me how you worked around this? I'm having the exact same issue with hedron.
@diegohaz - Using the universal-redux branch for this, is there anything that I should look into particularly?

@kristojorg
Copy link
Contributor

@karthikiyengar Hedron is using styled-components which I believe means you need to import both your own server markup and theirs.

@karthikiyengar
Copy link

@kristojorg - Thanks for your answer.

Please disregard, my vendor assets weren't being generated properly, leading to the issue.

@mxstbr
Copy link
Member

mxstbr commented Apr 1, 2017

This discussion has now been moved to #386.

@mxstbr mxstbr closed this as completed Apr 1, 2017
romandev0318 pushed a commit to romandev0318/phenomic that referenced this issue Dec 20, 2023
…box.

Now if you use [Glamor](https://github.com/threepointone/glamor/) to
write your style, static rendering will take that into account and will
prerender styles for you. Nothing to setup. It’s even injecting glamor
ids if you want to rehydrate on startup. See [glamor server
documentation](https://github.com/threepointone/glamor/blob/master/docs/
server.md) to setup hydratation (you will need to handle this yourself
in your ``scripts/phenomic.browser.js``.
Since [styled-components](https://styled-components.com/) [use Glamor
under the
hood](styled-components/styled-components#124)
, this will work for this library as well.

Ref #864
romandev0318 pushed a commit to romandev0318/phenomic that referenced this issue Dec 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests