A/B test variations styling doesn’t often look right using `emotion` css styling with Next.js

In @builder.io/react@1.1.46 and above we introduced SSR support for a/b tests variations by default, those variations are rendered under template html tags so they don’t have any effect on rendering performance, the issue with using emotion css lib is that it doesn’t hoist the resulting styles all the way to the body tag, which could cause a flashing of unstyled content if you’re assigned to one of the tests groups of the content. To fix this issue we need to post process the rendered html to pluck all those styles tags that are tucked under the template tag up to the body tag.

In Next.js you can do that by extracting the styles into a custom document

To do this, you will first create a file at ./pages/_document.js, this will replace the default Document normally provided by Next.js

then we’re going to need a library to make it possible to extract the necessary styles, we recommend cheerio

npm install cheerio

Import cheerio into your _document file and then we can create a function to extract all the necessary styles

const extractABTestingStyles = (body) => {
  let globalStyles = ''

  if (body.includes('<template')) {
    const $ = cheerio.load(body)
    const templates = $('template')
    templates.toArray().forEach((element) => {
      const str = $(element).html()
      const styles = cheerio.load(String(str))('style')
      globalStyles += styles
        .toArray()
        .map((el) => $(el).html())
        .join(' ')
    })
  }
  return globalStyles
}

From here, you will run extractABTestStyles within the MyDocument component to pull all styles, and then set them inside the body using

<style dangerouslySetInnerHTML={{ __html: this.props.globalStyles }}></style>

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const originalRenderPage = ctx.renderPage

    let globalStyles = ''
    ctx.renderPage = async (options) => {
      const render = await originalRenderPage(options)
      globalStyles = extractABTestingStyles(render.html)
      return render
    }
    const initialProps = await Document.getInitialProps(ctx)
    return {
      ...initialProps,
      globalStyles,
    }
  }
  render() {
    return (
      <Html>
        <Head />
        <body>
          <style
            dangerouslySetInnerHTML={{
              __html: this.props.globalStyles,
            }}
          ></style>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

And that should do it. You can see an example of how we have achieved the same in our Next.js/Shopify starter using the technique outlined above.

1 Like

Thanks so much for this suggestion. It has been working great for us.

Unfortunately though, while upgrading to NextJS v13, this solution stopped working. I suspect that it has something to do with comment that the NextJS documentation has about customization of getInitialProps and renderPage not being supported by React 18.

Any ideas for a new solution that will work with Next 13 / React 18?

Hi @mike.lambert,

Could you please tell us the error you are getting in the browser console when using Next 13 / React 18?

Hi Manish,
I’m not getting a particular error in the console and the issue only rears its head in our production builds in which most console errors are suppressed.

getPersonalizedURL was not working initially due to the issue described here.

Once past this, we have found that pages with AB tests have a flashing of unstyled content. This was the same behavior that we had prior to implementing the suggestion to hoist the styles using a custom document. We were getting hydration errors in when running in dev mode initially, but those have been fixed and this styling issue still persists.

I’m not sure if it’s related, but when there is a flashing of content without any styling, all of these script tags get appended to the body.

Screenshot of elements tab from dev tools

We are using the following dependencies:
next - 13.1.1
react- 18.2.0
@emotion/react - 11.10.5
@builder.io/react - 2.0.12
cheerio - 1.0.0-rc.12

Hi @mike.lambert,

Possibly this could be an issue related to Monitor Next.js 13 Layout integration with Emotion + MUI · Issue #21 · accelist/nextjs-starter · GitHub and may not be resolved until this is fixed.

Could you still confirm how you are using next13 with emotion?

My apologies for the delay in getting back to you @manish-sharma ! Darn those vacations… :wink:

When attempting to upgrade, we were continuing to use the pages directory and had not yet moved to the app directory because it’s still experimental. We’re not yet attempting to use layout.tsx files like described in this issue.

Hi @mike.lambert,

Could you tell if this flashing of unstyled content is only with text or if the entire content is flashing?

This could be due to nextJS recent changes Flash Of Unstyled Text (FOUT) on reload using next.js and styled components

Hi @manish-sharma,
All of the content flashes without any CSS applied for a moment. It’s the same as flash that we observed in NextJS v12 prior to implementing the solution that @TimG originally suggested in this post.

Hi @mike.lambert,

Would you be able to share the code reproduction, which would help us speed up the process to find a new solution?

Sorry for the delay on this again. I see that y’all have been working on v2 of the the @builder.io/react package, which is for React v18 and am guessing that that will fix the issue. I’ll let you know if the problem persists after the upgrade.