Skip to content

Instantly share code, notes, and snippets.

@colinhacks
Last active January 12, 2023 16:44
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save colinhacks/c40519a6a050a99091862319151377ec to your computer and use it in GitHub Desktop.
Save colinhacks/c40519a6a050a99091862319151377ec to your computer and use it in GitHub Desktop.
How to support server-side rendering for plain "emotion" package in Next.js
// ⚠️ works with Emotion 10 only! ⚠️
// 1. `yarn add emotion-server`
// 2. copy the contents of this file into your `pages` directory
// 3. save it as `_document.tsx`
// should work out of the box
import Document, { Head, Main, NextScript } from 'next/document';
import { extractCritical } from 'emotion-server';
export default class MyDocument extends Document {
static async getInitialProps(ctx: any) {
const initialProps = await Document.getInitialProps(ctx);
const styles = extractCritical(initialProps.html);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
<style
data-emotion-css={styles.ids.join(' ')}
dangerouslySetInnerHTML={{ __html: styles.css }}
/>
</>
),
};
}
render() {
return (
<html>
<Head />
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
@karlhorky
Copy link

Very cool @MMT-LD! 💯 Maybe the Next.js team would actually accept this as a further example in the /examples directory - maybe called with-emotion-vanilla or with-vanilla-emotion - to provide an alternative to the with-emotion example.

cc @lfades - this would be an example of how to use Next.js with vanilla Emotion (as opposed to @emotion/react

cc @Andarist @mitchellhamilton

@MMT-LD
Copy link

MMT-LD commented Dec 14, 2020

Diclaimer - not fully tested this its rough but think it'll work.

Another way to get this to work with the custom way (if you want a custom config) would be...

shared/emotion.ts

import createEmotion from '@emotion/css/create-instance';
export const {
  flush,
  hydrate,
  cx,
  getRegisteredStyles,
  injectGlobal,
  keyframes,
  css,
  cache,
  sheet,
} = createEmotion({ key: 'css-whatever' });

_document.tsx - note the new import shared/emotion and @emotion/server/create-instance

import * as React from 'react';
import Document, {
  DocumentContext,
  Head,
  Html,
  Main,
  NextScript,
} from 'next/document';
import createEmotionServer from '@emotion/server/create-instance';
import { cache } from 'shared/emotion';

const { extractCritical } = createEmotionServer(cache);

export default class AppDocument extends Document {
  static async getInitialProps(
    ctx: DocumentContext
  ): ReturnType<typeof Document.getInitialProps> {
    const initialProps = await Document.getInitialProps(ctx);
    const styles = extractCritical(initialProps.html);

    return {
      ...initialProps,
      styles: (
        <React.Fragment>
          {initialProps.styles}
          <style
            data-emotion-css={styles.ids.join(' ')}
            dangerouslySetInnerHTML={{ __html: styles.css }}
          />
        </React.Fragment>
      ),
    };
  }

  render(): JSX.Element {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

_app.tsx - note the new import shared/emotion

import * as React from 'react';
import { cache } from 'shared/emotion';
import { CacheProvider } from '@emotion/react';
import type { AppProps } from 'next/app';

function App(props: AppProps): JSX.Element {
  const { Component, pageProps } = props;

  return (
    <CacheProvider value={cache}>
        <Component {...pageProps} />
    </CacheProvider>
  );
}

export default App;

Doing it this way is a little maintenance, however, more flexible. All that is required is to use the css, keyframes etc etc from shared/emotion rather than emotion css. When styling your app. The css classname should now have classname='css-whatever-randomstring'. For example,

import { css } from 'shared/emotion';
const style = css({ color: 'white', padding: 0, background: 'red' });
<div className={style}>cool</div>

@karlhorky
Copy link

Nice, thanks!

I've opened a Next.js issue to see if they are interested in this: vercel/next.js#20199

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment