cereallarceny / cra-ssr

[DEPRECATED] Server-side rendering with create-react-app, React Router v4, Helmet, Redux, and Thunk
484 stars 118 forks source link

styled-components? #8

Closed jlarmstrongiv closed 6 years ago

jlarmstrongiv commented 6 years ago

Hi,

I just finished reading your tutorial on Medium; thank you. It’s a big help! Here’s a newbie question:

I am wondering how to integrate cra-ssr with the styled-components package. I was reading their documentation, but I just wasn’t sure where in the hierarchy their component should go:

// current renderToString
  <Loadable.Capture report={m => modules.push(m)}>
    <Provider store={store}>
      <StaticRouter location={req.url} context={context}>
        <Frontload isServer>
          <App />
        </Frontload>
      </StaticRouter>
    </Provider>
  </Loadable.Capture>

// their renderToString
  <StyleSheetManager sheet={sheet.instance}>
    <YourApp />
  </StyleSheetManager>

const styleTags = sheet.getStyleTags() // or sheet.getStyleElement()

I’m also not entirely sure if their wrapper component injects the style tags into the head on its own. It looks like express is still responsible for passing those to the page.

There’s another method (article) which takes those style tags into account:

  const sheet = new ServerStyleSheet(); // <-- creating out stylesheet

  const body = renderToString(sheet.collectStyles(<App />)); // <-- collecting styles
  const styles = sheet.getStyleTags(); // <-- getting all the tags from the sheet

  const title = 'Server side Rendering with Styled Components';

  res.send(
    Html({
      body,
      styles, // <-- passing the styles to our Html template
      title
    })
  );

To add that to your current setup, I would assume that styles would be passed into the page HOC and react-helmet would be configured to add the tags to the head?

aleksandr-senichev commented 6 years ago
Just wrap your <App/> component

<Loadable.Capture report={m => modules.push(m)}>
            <Provider store={store}>
              <StaticRouter location={req.url} context={context}>
                <Frontload isServer>
                  <StyleSheetManager sheet={sheet.instance}>
                    <App />
                  </StyleSheetManager>
                </Frontload>
              </StaticRouter>
            </Provider>
          </Loadable.Capture>
jlarmstrongiv commented 6 years ago

Wish it were that easy! Inside server/loader.js, a few edits need to be made.

First, styled-components needs to import it’s ServerStyleSheet at the top of the file:

import { ServerStyleSheet } from 'styled-components'

Second, the frontloadServerRender function needs to return and destructure both the routeMarkup and the styleTags:

      frontloadServerRender(() => {
        const sheet = new ServerStyleSheet();
        const routeMarkup = renderToString(sheet.collectStyles(
          <Loadable.Capture report={m => modules.push(m)}>
            <Provider store={store}>
              <StaticRouter location={req.url} context={context}>
                <Frontload isServer>
                  <App />
                </Frontload>
              </StaticRouter>
            </Provider>
          </Loadable.Capture>
        ))
        const styleTags = sheet.getStyleTags();
        return { routeMarkup, styleTags };
      }).then(({ routeMarkup, styleTags }) => {

Third, styleTags needs to be added to the injectHTML function:

          const html = injectHTML(htmlData, {
            …
            state: JSON.stringify(store.getState()).replace(/</g, '\\u003c'),
            styleTags
          });

Fourth, styleTags needs to be destructured from the injectHTML function and included in the with the meta tags.

  const injectHTML = (data, { html, title, meta, body, scripts, state, styleTags }) => {
    data = data.replace('<html>', `<html ${html}>`);
    data = data.replace(/<title>.*?<\/title>/g, title);
    data = data.replace('</head>', `${styleTags}${meta}</head>`);

Hopefully this’ll help anyone else who comes along 🙂 feel free to make suggestions