styled-components: createGlobalStyle creates memory leak in Next.js

Environment

Binaries:

  • Node: 13.7.0 - ~/.nvm/versions/node/v13.7.0/bin/node
  • Yarn: 1.21.1 - /usr/local/bin/yarn
  • npm: 6.13.6 - ~/.nvm/versions/node/v13.7.0/bin/npm

npmPackages:

  • babel-plugin-styled-components: ^1.10.7 => 1.10.6
  • styled-components: ^5.0.1 => 5.0.1

Problem

We are using styled-components and Next.js (7.2.1) and we have experienced memory leaks in our app for some time, so I started to investigate it, in the process I found other issues here about:

  • using the css helper when composing styling partials to be interpolated into a styled component
  • not to use @import inside createGlobalStyle
  • not dynamically create components

So I went through the whole app making sure to avoid these. I deployed a new version but the memory leak was still present. As seen on the graph below: Skjermbilde 2020-02-14 19 49 28

After inspecting the node server I found out it was createGlobalStyle in our _app.js (Next.js file) that caused the memory to rise. In the createGlobalStyle we used fontFace/normalize from polished), and around 50 other lines of reset css (nothing special).

Removing createGlobalStyle solved the memory leak (or at least reduced to a minimum). See the graph below: Skjermbilde 2020-02-14 19 50 55

Did we use createGlobalStylewrong or do someone have some insight to this?

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 32
  • Comments: 43 (9 by maintainers)

Commits related to this issue

Most upvoted comments

So I’m giving up on properly solving this issue, I can’t figure out how to pass context in my particular case with getDataFromTree(<Tree />, ctx), because I’m not sure where to even take it. But issue is definitely as I described here. I still wonder why masterSheet exists, because it’s singleton which is never cleaned up, at least from my understanding.

Nevertheless I have found a workaround to mitigate this issue:

            await getDataFromTree(
              // workaround for memory leak, see https://github.com/styled-components/styled-components/issues/3022
              <StyleSheetManager sheet={new ServerStyleSheet().instance}>
                <AppTree {...props} />
              </StyleSheetManager>
            )

Just wrapping it with StyleManager will make it use new StyleSheet every time. Not sure if there will be any harmful side effects here.

@probablyup, the issue is still there 😦

Packages versions: “styled-components”: “^5.0.1” “next”: “9.2.2”

One of our global styles:

import { createGlobalStyle } from 'styled-components';

const AppGlobalStyles = createGlobalStyle`
  #__next {
     display: flex;
     flex-direction: column;
     height: 100vh;
  }
`;

AppGlobalStyles.displayName = 'AppGlobalStyles';

export default AppGlobalStyles;

Memory usage with createGlobalStyle ^^: Screenshot from 2020-03-06 14-39-07

Memory usage without createGlobalStyle: Screenshot from 2020-03-06 14-40-19

For sure we have some other small memory leaks, but createGlobalStyle produces the biggest one. This is how our memory usage looks using createGlobalStyle:

Screenshot from 2020-03-06 14-35-34

Constructor: #__next{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;height:100vh;/*\|*/}:

Screenshot from 2020-03-06 14-46-53

The object referred to the constructor ^^: Screenshot from 2020-03-06 14-47-45

As you can see the memory is being actively spammed on each page render by CSS string produced by createGlobalStyle output. This is a huge issue. Possible solutions:

  • Downgrade as mentioned by @ghengeveld
  • import '../global.css'
  • Fix createGlobalStyle ASAP

This is not related to Next.js. We’re experiencing the same problems with a custom built React SSR implementation. Downgrading to version 4 solved the issue.

Seeing the same picture in my debugger as https://github.com/styled-components/styled-components/issues/3022#issuecomment-612628550

Has anyone been able to resolve this?

I wonder if the issue is specifically with Next.js rather than styled-components. For example would any large string located in _app.js cause a memory leak?

We were also forced to downgrade to 4.4.3. This is our memory consumption before and after styled-components v5, I am sure you can pinpoint the points where the version changed 😃).

image

Yes we are having the same issue in our Next app as well. Only difference is that we are using latest Next version.

I’m not common with the code base, but investigating it further led to Sheet.js, this particular line:

// line 69
  allocateGSInstance(id: string) {
    return (this.gs[id] = (this.gs[id] || 0) + 1);
  }

New style sheets are allocated later with this unique number in const styleSheet = useStyleSheet();, polluting the names map entry: image

Why this happening and what is the reasoning behind incrementing in allocateGSInstance I don’t know. Will investigate further, but it would be great if someone with better styled-components project knowledge could take a look at it.

Just a string in style tag, put in head:

export const GlobalStyles: any = () => (
  <style
    dangerouslySetInnerHTML={{
      __html: `
 ... styles ...
`
    }}
  />
)

Using Typescript, so had to cast an any type to use this component as before

UPD: One can also use just a .css file (in case of Next, which supports them out of the box), but we have variables there, so was not an option in my case.

We are running StoryShots over our 2000+ stories and after upgrading to Styled Components v5 our tests are timing out. I’ve been able to bring down the test time drastically by stripping our global styles (with createGlobalStyle) down to just a few simple css rules. At one point adding a single rule on an element selector increased test time from 12 minutes to 17 minutes. Adding more increased it to 55 minutes, and that wasn’t even our complete set of global styles. Before upgrading, our tests took only 3 minutes or so, with the complete set of global styles from @storybook/design-system.

@Oikio that’s fine to do, it’s essentially what sheet.collectStyles does anyway

Allocation always increments id here:

  allocateGSInstance(id: string) {
    return (this.gs[id] = (this.gs[id] || 0) + 1);
  }

But previous id for createGlobalStyle is never cleared, here is the flow of the renderStyles:

...
    if (__SERVER__) {
      renderStyles(instance, props, styleSheet, theme, stylis); // instance here is "current id"
    } else {
...
->
...
globalStyle.renderStyles(instance, STATIC_EXECUTION_CONTEXT, styleSheet, stylis);
...
->
...
  renderStyles(
    instance: number,
    executionContext: Object,
    styleSheet: StyleSheet,
    stylis: Stringifier
  ) {
    if (instance > 2) StyleSheet.registerId(this.componentId + instance);

    // NOTE: Remove old styles, then inject the new ones
    this.removeStyles(instance, styleSheet); // and it's called here with current id, this.removeStyles() is never called for previous id.
    this.createStyles(instance, executionContext, styleSheet, stylis);
  }
..

At least it looks like the issue. I’m not sure where to fix it. Why does allocateGSInstance increments numerical part of id, isn’t id hash unique? Where should removeStyles() be called for previous allocated styles?

Still not much progress in finding our why this.names field is reused, though ServerStylesheet is recreated on every request. We are using integration from examples of Next.js:

  static async getInitialProps(ctx: DocumentContext) {
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: App => props => sheet.collectStyles(<App {...props} />)
        })

      const initialProps = await Document.getInitialProps(ctx)
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        )
      }
    } finally {
      sheet.seal()
    }
  }

It looks pretty straightforward and I wanted to blame Next.js first, until I saw that version 4 was fine and also there is a comment from peeter-tomberg above, having same issue with non Next.js project. Will continue to investigate, help appreciated.

BTW, @peeter-tomberg if you can share how styled-components are integrated in your app without Next, it would great for invsetigating the issue.

We ran into a similar issue where stylis trim function was causing a mem leak. Downgrading to 4.4.0, validating on server.

Screen Shot 2020-07-31 at 5 15 08 PM

EDIT: Confirmed.

EDIT 2: retracting confirmed, the memory is still rising on our server, but slightly slower.

EDIT 3: A heap snapshot:

Screen Shot 2020-07-31 at 6 43 47 PM

EDIT 4: mistook the last one, it was still on SC 5.0 due to one of our component libraries.

This is the memory snapshot from 4.4.0:

Screen Shot 2020-07-31 at 7 06 03 PM

Yep works fine when deployed:

Screen Shot 2020-07-31 at 7 40 58 PM