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

Comments

@geelen
Copy link
Member

@geelen 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 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

@diegohaz diegohaz commented Oct 24, 2016

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 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

@diegohaz diegohaz commented Oct 24, 2016

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 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 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 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 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 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 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 :(

@thisguychris
Copy link
Member

@thisguychris thisguychris commented Nov 17, 2016

@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
We will wait for official API
styled-components/styled-components#124
@aesopwolf
Copy link

@aesopwolf aesopwolf commented Nov 17, 2016

@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 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..

@thisguychris
Copy link
Member

@thisguychris thisguychris commented Nov 18, 2016

@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 mxstbr commented Nov 18, 2016

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

@diegohaz
Copy link
Member

@diegohaz diegohaz commented Nov 18, 2016

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

@diegohaz diegohaz commented Nov 18, 2016

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

@thisguychris

@tomazy
Copy link

@tomazy 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

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

@dignifiedquire dignifiedquire commented Nov 23, 2016

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?

thisguychris added a commit to thisguychris/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 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 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.

@thisguychris
Copy link
Member

@thisguychris thisguychris commented Dec 4, 2016

@code-punkt see #214 :)

@SachaG
Copy link
Contributor

@SachaG 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 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

@codepunkt codepunkt commented Dec 30, 2016

@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 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.

thisguychris added a commit to thisguychris/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

@kristojorg kristojorg commented Jan 19, 2017

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

@kristojorg kristojorg commented Jan 20, 2017

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

@karthikiyengar
Copy link

@karthikiyengar karthikiyengar commented Mar 23, 2017

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

@kristojorg kristojorg commented Mar 24, 2017

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

@karthikiyengar
Copy link

@karthikiyengar karthikiyengar commented Mar 24, 2017

@kristojorg - Thanks for your answer.

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

@mxstbr
Copy link
Member

@mxstbr mxstbr commented Apr 1, 2017

This discussion has now been moved to #386.

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

Successfully merging a pull request may close this issue.

None yet