はじめに

長いタイトルですが今回は短編です。
飽くまでこんな方法がそういえばあったな、くらいでご覧ください。

最近名前聞かないけれど

aphrodite

https://github.com/Khan/aphrodite

私もNextを使い始めてからは存在をすっかり忘れていましたが、最近は専らaphroditeを使用しています。

  1. スタイリングをするだけのコンポーネントとロジックを持つコンポーネントを完全に分割できる
  2. ベースのスタイルを持ったコンポーネントを定義して、上書きする形で自由なスタイルを当てられる
  3. サーバーサイドでhead内にスタイルを先に読み込むことができるので、スタイルの読み込み遅延が発生しづらい。

この辺のパフォーマンスとか管理面のメリットについては'19年のairbnbのReact confでのプレゼンが参考になります。
https://www.youtube.com/watch?v=fHQ1WSx41CA

以下、使用例(Next.js)

import Document, {
    Html,
    Head,
    Main,
    NextScript,
    DocumentContext
} from 'next/document';
import { StyleSheetServer } from 'aphrodite';

type WithStyleProps = {
    ids: string[];
    css: {
        content: string;
        renderedClassNames: string[];
    };
};

export default class MyDocument extends Document<WithStyleProps> {
    static async getInitialProps({ renderPage }: DocumentContext) {
    const { html, css } = StyleSheetServer.renderStatic(
            () => renderPage() as any
    ) as {
            html: any;
            css: {
            content: string;
                renderedClassNames: string[];
        };
        };
    const ids = css.renderedClassNames;
    return { ...html, css, ids };
    }

    render(): JSX.Element {
    const { css, ids } = this.props;
    return (
        <Html>
                <Head>
                <style
                        data-aphrodite
            dangerouslySetInnerHTML={{
                __html: css.content
            }}
        </Head>
        <body>
            <Main />
            <NextScript />
            {ids && (
                <script
                dangerouslySetInnerHTML={{
                    __html: `window.__REHYDRATE_IDS = ${JSON.stringify(
                    ids
                )}`
                }}
            />
            )}
        </body>
        </Html>
    );
    }
}
import { AppProps } from 'next/app';
import { StyleSheet } from 'aphrodite';

if (typeof window !== 'undefined') {
    StyleSheet.rehydrate(window.__REHYDRATE_IDS);
}

const App = ({ Component, pageProps }: AppProps): JSX.Element => {
    return <Component {...pageProps} />;
}
import { StyleDeclaration, css, StyleSheet } from 'aphrodite';
import { HTMLAttributes } from 'react';

export type ContainerStyles = StyleDeclaration<{
    container: unknown;
}>;

type Props = HTMLAttributes<HTMLDivElement> & {
    styles?: ContainerStyles;
};

const containerBaseStyles = StyleSheet.create({
    container: {
        background: 'transparent';
    position: 'relative';
    }
});

const Container: forwardRef<HTMLDivElement, Props>(
    ({ styles, className, children, ...props }, ref) => (
        <div
        ref={ref}
        {...props}
        className={`
            ${css(
            containerBaseStyles.container,
            styles && StyleSheet.create(styles).container
        )} ${className}
        `}
        >
        {children}
    </div>
    )
);

export default Container;

このContainerコンポーネントはただのdivを出力するコンポーネントですが、
独自のクラス名を渡すこともできるし、aphroditeのお作法に乗っ取りstylesのpropsにオブジェクト形式でスタイルを渡すこともできます。
コレを使うことで、例えばカラーパレットやpaddingなどの値をcss変数ではなくtypescriptの変数で持たせておくこともできます。

css変数が動かないブラウザ(IEなど)での利用が想定される場合など、おすすめです。

import { FC } from 'react';
import Container from './Container';

const SomeComponent: FC = () => (
    <Container styles={{
        container: {
        background: 'red',
        ':before': {
            content: '""',
        position: 'absolute'
        }
    }
    }}>
        <p>Some child elements.</p>
    </Container>
);

追記

https://techlife.cookpad.com/entry/2021/03/15/090000
CSS in JSについてはcookpad様も採用されたらしい。

aphrodite自体は決して新しくないけど、CSS in JS自体は支持されてますね。
今の人気はemotionとかstyled-componentなのかな。

以上でした。