A community member asked on Discord why their Redux code wasn't working. After looking at their site for a bit I noticed they were also using gatsby-plugin-styled-components
. Both their redux plugin & styled components implemented in their gatsby-ssr.js
the API replaceRenderer
鈥攚hich there can only be one of.
We should when loading plugins check for cases like this and print out a warning in the console that things won't work as expected and suggest workarounds e.g. in this case, pull the styled-component SSR code into their custom redux code.
After combining the two replaceRenderer files, (removing the styled-components plugin , adding the code to the redux ssr code) ... I ended up with this file
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
import { Provider } from 'react-redux'
import React from 'react'
import initStore from './src/shared/store/initRedux'
import { renderToString } from 'react-dom/server'
exports.replaceRenderer = ({
bodyComponent,
replaceBodyHTMLString,
setHeadComponents
}) => {
const sheet = new ServerStyleSheet()
const store = initStore()
const app = () => (
<Provider store={store}>
<StyleSheetManager sheet={sheet.instance}>
{bodyComponent}
</StyleSheetManager>
</Provider>
)
const body = renderToString(app)
replaceBodyHTMLString(body)
setHeadComponents([sheet.getStyleElement()])
return
}
by all means it looks like it should have worked, but instead I got anywhere between 12-24 errors along the lines of:
component---src-layouts-index-js-36c5da770483ba68dacf.js Failed to load resource: the server responded with a status of 404 (Not Found)
component---src-pages-index-js-87d1c17ad13dae91a1d2.js Failed to load resource: the server responded with a status of 404 (Not Found)
path---index-a0e39f21c11f6a62c5ab.js Failed to load resource: the server responded with a status of 404 (Not Found)
component---src-layouts-index-js-36c5da770483ba68dacf.js Failed to load resource: the server responded with a status of 404 (Not Found)
component---src-pages-index-js-87d1c17ad13dae91a1d2.js Failed to load resource: the server responded with a status of 404 (Not Found)
path---index-a0e39f21c11f6a62c5ab.js Failed to load resource: the server responded with a status of 404 (Not Found)
component---src-pages-index-js-87d1c17ad13dae91a1d2.js Failed to load resource: the server responded with a status of 404 (Not Found)
path---index-a0e39f21c11f6a62c5ab.js Failed to load resource: the server responded with a status of 404 (Not Found)
component---src-layouts-index-js-36c5da770483ba68dacf.js Failed to load resource: the server responded with a status of 404 (Not Found)
app-47a21a6fc641308aca59.js:4142 bundle loading error true
app-47a21a6fc641308aca59.js:933 Loading the component for / failed
app-47a21a6fc641308aca59.js:537 bundle loading error true
app-47a21a6fc641308aca59.js:940 Loading the JSON for / failed
app-47a21a6fc641308aca59.js:600 bundle loading error true
app-47a21a6fc641308aca59.js:948 Loading the Layout for / failed
component---src-pages-index-js-87d1c17ad13dae91a1d2.js Failed to load resource: the server responded with a status of 404 (Not Found)
path---index-a0e39f21c11f6a62c5ab.js Failed to load resource: the server responded with a status of 404 (Not Found)
component---src-layouts-index-js-36c5da770483ba68dacf.js Failed to load resource: the server responded with a status of 404 (Not Found)
Running gatsby build:
Minified React error #46; visit http://facebook.github.io/react /docs/error-decoder.html?invariant=46 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
- render-page.js:4936 reactProdInvariant
render-page.js:4936:16
- render-page.js:19988 renderToString
render-page.js:19988:122
- render-page.js:42261 Object.exports.replaceRenderer
render-page.js:42261:42
- render-page.js:40928
render-page.js:40928:39
- render-page.js:40926 module.exports
render-page.js:40926:26
- render-page.js:176 module.exports
render-page.js:176:31
Whoops, this appears to work only in development mode:
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
import { Provider } from 'react-redux'
import React from 'react'
import initStore from './src/shared/store/initRedux'
import { renderToString } from 'react-dom/server'
exports.replaceRenderer = ({
bodyComponent,
replaceBodyHTMLString,
setHeadComponents
}) => {
const sheet = new ServerStyleSheet()
const store = initStore()
const app = () => (
<Provider store={store}>
<StyleSheetManager sheet={sheet.instance}>
{bodyComponent}
</StyleSheetManager>
</Provider>
)
const body = renderToString(app)
replaceBodyHTMLString(body)
setHeadComponents([sheet.getStyleElement()])
}
You will get react minified error 46, which is "renderToString: you must pass a valid React Element"
so I got rid of the consts and can confirm this is working in both prod / dev
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
import { Provider } from 'react-redux'
import React from 'react'
import initStore from './src/shared/store/initRedux'
import { renderToString } from 'react-dom/server'
exports.replaceRenderer = ({
bodyComponent,
replaceBodyHTMLString,
setHeadComponents
}) => {
const sheet = new ServerStyleSheet()
const store = initStore()
replaceBodyHTMLString(
renderToString(
<Provider store={store}>
<StyleSheetManager sheet={sheet.instance}>
{bodyComponent}
</StyleSheetManager>
</Provider>
)
)
setHeadComponents([sheet.getStyleElement()])
}
@akadop Thanks for the workaround! It saved me tons of time!
Thank's a lot @akadop, I didn't understand why it wasn't working on SSR... You're the real mvp 馃憡
This was done in #3889
@akadop It's really strange how removing those constants makes this work. Any specific explanation for it.
@prajapati-parth
i presume it's because setting it as a const is making it a function that returns a react element, and the renderToString
function does not take a function as a valid arg
@prajapati-parth
just checked renderToString()
export function renderToString(element: ReactElement<any>): string;
this is flow typed, so passing in an arg that isn't a ReactElement<any>
will throw an error. A function returning the component will not meet the criteria.
this would probably work though:
const app = (
<Provider store={store}>
<StyleSheetManager sheet={sheet.instance}>
{bodyComponent}
</StyleSheetManager>
</Provider>
)
const body = renderToString(app)
replaceBodyHTMLString(body)
@akadop
That makes sense. Thanks for digging it out.
We actually had got it wrong in the first place itself. I think since renderToString
expects an element and since app
in your example (https://github.com/gatsbyjs/gatsby/issues/2005#issuecomment-326787567) can be treated as a stateless functional component, replacing
const body = renderToString(app)
with
const body = renderToString(<app />)
would also work.
The modified example would look like
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
import { Provider } from 'react-redux'
import React from 'react'
import initStore from './src/shared/store/initRedux'
import { renderToString } from 'react-dom/server'
exports.replaceRenderer = ({
bodyComponent,
replaceBodyHTMLString,
setHeadComponents
}) => {
const sheet = new ServerStyleSheet()
const store = initStore()
const app = () => (
<Provider store={store}>
<StyleSheetManager sheet={sheet.instance}>
{bodyComponent}
</StyleSheetManager>
</Provider>
)
const body = renderToString(<app />)
replaceBodyHTMLString(body)
setHeadComponents([sheet.getStyleElement()])
}
yup, that works too! im not sure about performance implications though, since your wrapping it in another react component (even though it's an SFC.. same thing happens internally)
Most helpful comment
Whoops, this appears to work only in development mode:
You will get react minified error 46, which is "renderToString: you must pass a valid React Element"
so I got rid of the consts and can confirm this is working in both prod / dev