emotion: Global component + advanced SSR doesn't hydrate as expected

Current behavior:

When using the “advanced approach” (https://emotion.sh/docs/ssr#advanced-approach) to SSR in combination with the <Global> component, hydration doesn’t appear to be working as expected.

All non-global styles seem to be rehydrated properly, but styles included via <Global> are injected into to the page when the client bundle hydrates instead of rehydrating the existing server-provided global styles.

This leads to issues due to specificity where the global rules now have a chance to override non-globals, causing visual bugs.

From looking at the source, this appears to be because InnerGlobal checks if the global styles are already included on the page for rehydration purposes here: https://github.com/emotion-js/emotion/blob/c85378a204613885a356eaba1480c5151838c458/packages/core/src/global.js#L76

Unfortunately, this selector doesn’t match anything that would be produced by the “advanced approach” SSR guide, which suggests including:

<style data-emotion-css="${ids.join(' ')}">${css}</style>

on the page. So if the InnerGlobal has this.props.serialized.name of deadb33f, it’s expecting to see:

<style data-emotion-css="deadb33f">...</style>

on the page to know where to hydrate, which isn’t the case. The reality will look more like:

<style data-emotion-css="deadb33f s0meId an0th3rOne">...</style>

To reproduce:

TODO: cannot use the codesandbox since it’s not set up for SSR. Will make a repo if this helps, but want to post this first since I do think I know roughly why it’s happening.

Expected behavior:

The styles included in <Global> should be rehydrated in the same way as non-globals: i.e. any styles that are already included on the page via SSR should not be re-inserted.

I need to dig into how this is working for non-global styles, since presumably the logic is set up there to scan through the IDs included as part of the data-emotion-css attribute rather than expecting a single id per style tag.

I’m also not sure if there’s an existing way to use extractCritical to split up the css into multiple <style> tags - it seems like that would also fix it from the other direction, although the docs would likely need to be updated if that’s the way forward.

Environment information:

  • react version: 16.13.1
  • @emotion/core version: 10.0.35

Let me know if there’s any other info you need from me. I’ll try and spin up a repo if my snippets above don’t paint the full picture.

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 7
  • Comments: 18 (10 by maintainers)

Most upvoted comments

I’ve looked into the repro (thank you for that!) - need to give this some more thought, for now gonna just describe what happens here:

  1. we are dealing with a compat cache here (if you are using extractCritical API we set automatically set the compat flag on the cache)
  2. for compat caches we don’t render style elements in the output for global styles
  3. but we insert them into the cache
  4. we don’t keep track in the cache which styles come from regular emotion styles and which are global
  5. because the extracted IDs are just in a single “bag” you put all of them into a single <style/> element
  6. this fails while rehydrating here
  7. because global styles can be removed at runtime we probably should separate those IDs as otherwise, we’d have to be able to manipulate the already SSRed .textContent of <style/> elements to remove those and we don’t do that now (and it’s also more complex/error-prone)