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>
);
}
}
@bcourtney5965
Copy link

Hi @colinhacks, came from your article on @emotion/core vs Vanilla Emotion.

This is interesting to me too: it would be nice to have students at @upleveled avoid the footgun of using the css prop on an <a> inside of a Next.js <Link> and then the href not being passed (see the problem at this CodeSandbox)

I am not sure this is up to date with Emotion 11 though - do you have a more current version of this? For example, when I look at the version on the Emotion docs, it imports createEmotionServer from @emotion/server/create-instance and then creates a server, before finally destructuring extractCritical out of it.

Oh and if you do have a version, does it also use a function component instead of a class component (since Next.js supports this now)?

Full disclosure:

I am brand new to NextJS and Emotion. These seem relevant - but take them with a grain of salt.

@karlhorky, I thought this might interest you
https://emotion.sh/docs/ssr
image

The sentence I've highlighted seems misleading or possibly incomplete. I assume they mean emotion-server (pre Emotion 11) or @emotion/server (Emotion 11). But maybe perhaps I'm wrong.

Also check out their Emotion 11 notes here: https://emotion.sh/docs/emotion-11

@karlhorky
Copy link

@bcourtney5965 Thanks for the response :)

The highlighted sentence applies to @emotion/core (actually now called @emotion/react in v11).

I worked on the current official example in the Next.js repo to bring this up to date:

What this Gist is talking about is Vanilla emotion - which is a completely different animal :)

@colinhacks
Copy link
Author

@karlhorky you're totally right, this is for Emotion 10 specifically. I'll update my blog post to indicate that. I don't have upgraded versions at this time, i'll try to get around to it soon though.

@karlhorky
Copy link

Ok great, thanks!

@bcourtney5965
Copy link

Ah I see, thank you karlhorky for the clarification. And thank you colinhacks for the writeup.

@MMT-LD
Copy link

MMT-LD commented Dec 14, 2020

In latest Nextjs i have this that seems to work for me - typescript version. emotion >= 11

  1. yarn add @emotion/server
  2. copy the contents of this file into your pages directory
  3. save it as _document.tsx

_document.tsx

import * as React from 'react';
import Document, {
  DocumentContext,
  Head,
  Html,
  Main,
  NextScript,
} from 'next/document';
import { extractCritical } from '@emotion/server';

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>
    );
  }
}
  1. yarn add @emotion/css @emotion/react
  2. copy the contents of this file into your pages directory
  3. save it as _app.tsx

_app.tsx

import * as React from 'react';
import { cache } from '@emotion/css';
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;

Hope that helps

@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