The way gatsby-plugin-styled-components currently implements SSR allows for only one style sheet per pathname.
import React from 'react'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
const sheetByPathname = new Map()
exports.wrapRootElement = ({ element, pathname }) => {
const sheet = new ServerStyleSheet()
sheetByPathname.set(pathname, sheet)
return <StyleSheetManager sheet={sheet.instance}>{element}</StyleSheetManager>
}
exports.onRenderBody = ({ setHeadComponents, pathname }) => {
const sheet = sheetByPathname.get(pathname)
if (sheet) {
setHeadComponents([sheet.getStyleElement()])
sheetByPathname.delete(pathname)
}
}
This is a problem if you're trying to implement a dark mode without applying extra CSS classes to your components. In my case, dark mode is implemented by styled-components' ThemeProvider taking a theme function. This function receives the current color mode from a hook in gatsby-ssr's wrapRootElement:
import React from 'react'
import { useDarkMode } from 'hooks'
import { ThemeProvider } from 'styled-components'
import theme from 'utils/theme'
function Theme({ children }) {
const [darkMode] = useDarkMode()
return <ThemeProvider theme={theme(darkMode)}>{children}</ThemeProvider>
}
export const wrapRootElement = ({ element }) => <Theme>{element}</Theme>
Even though the hook persists the current color mode to local storage, this implementation leads to the light mode briefly flashing when hard reloading the page (cmd + shift + R) since the SSR stylesheet doesn't know about different styles for the dark theme.
To solve this I was thinking about how to override gatsby-plugin-styled-components's default behavior and server-render two stylesheets per pathname in order to serve the correct one depending on whether or not darkMode was found in local storage. If anyone can offer some advice on how to do that or if there might be a more elegant solution, that would be much appreciated! :)
I don't see an easy way to avoid this flash with static HTML. You will still see the static version with the default theme while js is loading/parsing/react hydrating, right? But maybe I am missing something?
@LekoArts Any ideas on this?
@vladar I was thinking maybe I could use the onRenderBody API to either change the SSR stylesheet in place or override it with a second one before the page paints. Something like
import React from 'react'
const noflash = `(function() { try {
if (localStorage.getItem('colorMode') === 'dark') {
// change or replace style sheet here
}
} catch (e) {} })();`
export const onRenderBody = ({ setPreBodyComponents }) => {
setPreBodyComponents(<script>{noflash}</script>)
}
Hi Janosh!
I fear this isn't something we would directly bake into gatsby-plugin-styled-components and require the user to handle that. For a lengthy explanation you can read my colleagues blog post: https://joshwcomeau.com/gatsby/dark-mode/
TL;DR:
Great post by Josh Comeau! I adapted his implementation. Super happy with it.
Most helpful comment
Hi Janosh!
I fear this isn't something we would directly bake into gatsby-plugin-styled-components and require the user to handle that. For a lengthy explanation you can read my colleagues blog post: https://joshwcomeau.com/gatsby/dark-mode/
TL;DR: