Emotion の Server API で本格 SSR を実現

近年、React アプリケーションの開発において CSS-in-JS は欠かせない技術となっており、その中でも Emotion は高いパフォーマンスと柔軟性を兼ね備えたライブラリとして多くの開発者に愛用されています。特に Server-Side Rendering(SSR)環境における Emotion の Server API は、従来の CSS-in-JS が抱えていた課題を解決し、真に実用的な SSR 実装を可能にしてくれます。
本記事では、Emotion の Server API を活用した本格的な SSR 実装について、基本的な仕組みから具体的な実装手順まで、初心者の方にも分かりやすく解説していきます。
背景
CSS-in-JS の SSR における課題
CSS-in-JS ライブラリを使用した React アプリケーションを SSR 環境で動作させる際、多くの開発者が直面する共通の課題があります。
従来の CSS-in-JS ライブラリでは、スタイルがクライアントサイドで動的に生成されるため、サーバーサイドでレンダリングされた HTML にはスタイル情報が含まれていません。これにより、ページの初期読み込み時にスタイルが適用されない状態が発生してしまいます。
また、クライアントサイドでのハイドレーション処理が完了するまでの間、ユーザーは正しくスタイリングされていないコンテンツを目にすることになり、ユーザー体験の大幅な低下を招いてしまうのです。
mermaidflowchart TD
Server[サーバー] -->|HTML生成| HTML[スタイルなしHTML]
HTML -->|配信| Browser[ブラウザ]
Browser -->|JS読み込み| Hydration[ハイドレーション]
Hydration -->|スタイル適用| StyledPage[スタイル適用済みページ]
Browser -->|一時的表示| FOUC[スタイルなし状態<br/>FOUC発生]
上図のように、従来の手法では HTML 配信からハイドレーション完了まで、ユーザーはスタイルが適用されていない状態のページを見ることになります。
Emotion の Server API の登場背景
これらの課題を解決するために、Emotion チームは専用の Server API を開発しました。この API は、サーバーサイドでスタイルを事前に収集・生成し、初回の HTML レスポンスに含めることを可能にします。
Emotion の Server API が注目される理由は、その設計思想にあります。従来の CSS-in-JS ライブラリが「クライアントファースト」の思想で設計されていたのに対し、Emotion は最初から SSR を意識した設計が施されているのです。
従来の SSR 手法との違い
従来の SSR 実装では、CSS ファイルを事前にビルドしてサーバーから配信する手法が一般的でした。しかし、この手法には以下のような制約がありました。
mermaidflowchart LR
TraditionalCSS[従来のCSS] -->|静的| Build[ビルド時生成]
Build -->|固定スタイル| StaticCSS[静的CSSファイル]
EmotionCSS[Emotion CSS] -->|動的| Runtime[ランタイム生成]
Runtime -->|柔軟なスタイル| DynamicCSS[動的スタイル]
従来手法の制約:
- プロップスに基づく動的スタイリングの困難
- コンポーネントレベルでのスタイル管理の複雑さ
- JavaScript とスタイルの分離による保守性の問題
一方、Emotion の Server API を使用することで、JavaScript の柔軟性を保ちながら SSR の恩恵を受けることができるようになります。これにより、動的なスタイリングと高速な初回レンダリングを両立できるのです。
課題
スタイルの重複読み込み問題
CSS-in-JS を使用した SSR アプリケーションでよく発生する問題の一つが、スタイルの重複読み込みです。サーバーサイドで生成されたスタイルと、クライアントサイドで再度生成されるスタイルが重複してしまい、不要なリソース消費を引き起こします。
typescript// 問題のあるパターン例
// サーバーサイドで生成されたスタイル
const serverStyles = `
.css-abc123 {
color: red;
font-size: 16px;
}
`;
// クライアントサイドで再度生成される同じスタイル
const clientStyles = `
.css-abc123 {
color: red;
font-size: 16px;
}
`;
この重複により、以下のような問題が発生します:
問題 | 影響 | 解決の必要性 |
---|---|---|
バンドルサイズの増加 | ページ読み込み速度の低下 | 高 |
メモリ使用量の増加 | アプリケーションパフォーマンスの低下 | 中 |
CSS の競合リスク | レイアウト崩れの可能性 | 高 |
FOUC(Flash of Unstyled Content)の発生
FOUC は、ページの初回読み込み時にスタイルが適用されていないコンテンツが一瞬表示される現象です。CSS-in-JS を使用した SSR アプリケーションでは、この問題が特に顕著に現れます。
mermaidsequenceDiagram
participant User as ユーザー
participant Server as サーバー
participant Browser as ブラウザ
participant JS as JavaScript
User->>Server: ページリクエスト
Server->>Browser: スタイルなしHTML
Browser->>User: 未スタイル状態表示(FOUC)
Browser->>JS: JSファイル読み込み
JS->>Browser: スタイル生成・適用
Browser->>User: 正常なページ表示
FOUC が発生すると、ユーザー体験に以下のような悪影響を与えます:
- 視覚的な違和感: レイアウトが突然変わることでユーザーが驚く
- 信頼性の低下: 技術的に未熟なサイトという印象を与える
- 離脱率の増加: 初回表示の品質低下により、ユーザーがサイトを離れる可能性が高まる
パフォーマンスへの影響
適切に実装されていない CSS-in-JS の SSR は、アプリケーション全体のパフォーマンスに深刻な影響を与える可能性があります。
主なパフォーマンス課題:
typescript// パフォーマンスに影響を与える要因の例
// 1. 不要なスタイル再計算
const expensiveCalculation = () => {
return complexStyleLogic(); // 毎回実行される重い処理
};
// 2. 大量のCSS生成
const generateAllStyles = () => {
return components.map((comp) => generateCSS(comp)); // 必要以上のCSS生成
};
// 3. メモリリーク
let styleCache = {}; // クリアされないキャッシュ
これらの問題を数値で表すと:
メトリクス | 問題のある実装 | 最適化後 | 改善率 |
---|---|---|---|
初回レンダリング時間 | 3.2 秒 | 1.1 秒 | 66%短縮 |
バンドルサイズ | 450KB | 280KB | 38%削減 |
メモリ使用量 | 85MB | 45MB | 47%削減 |
解決策
Emotion Server API の仕組み
Emotion の Server API は、これらの課題を解決するために設計された強力なツールセットです。その核心的な仕組みを理解することで、効果的な SSR 実装が可能になります。
Server API の基本的な動作フローは以下のようになっています:
mermaidflowchart TD
Request[リクエスト受信] --> CreateCache[EmotionCacheの作成]
CreateCache --> RenderApp[アプリケーションレンダリング]
RenderApp --> CollectStyles[スタイル収集]
CollectStyles --> GenerateHTML[HTML + CSS生成]
GenerateHTML --> Response[レスポンス送信]
CollectStyles --> StyleExtraction[スタイル抽出]
StyleExtraction --> CriticalCSS[クリティカルCSS特定]
このフローにより、サーバーサイドで必要なスタイルのみを事前に生成し、HTML に埋め込むことができます。
Emotion Server API の主要コンポーネント:
typescriptimport { CacheProvider } from '@emotion/react';
import createEmotionServer from '@emotion/server/create-instance';
import createCache from '@emotion/cache';
// 1. キャッシュインスタンスの作成
const cache = createCache({ key: 'css' });
// 2. サーバーインスタンスの作成
const {
extractCriticalToChunks,
constructStyleTagsFromChunks,
} = createEmotionServer(cache);
サーバーサイドでのスタイル収集
サーバーサイドでのスタイル収集は、Emotion Server API の最も重要な機能の一つです。この機能により、レンダリング中に使用されたスタイルを自動的に追跡し、必要な CSS のみを抽出できます。
スタイル収集の実装例:
typescript// サーバーサイドでのスタイル収集処理
import { renderToString } from 'react-dom/server';
const collectStyles = (App: React.ComponentType) => {
// アプリケーションをレンダリングしてHTML文字列を生成
const html = renderToString(
<CacheProvider value={cache}>
<App />
</CacheProvider>
);
// レンダリング中に収集されたスタイル情報を抽出
const chunks = extractCriticalToChunks(html);
return {
html,
styles: constructStyleTagsFromChunks(chunks),
};
};
この処理により、以下のメリットが得られます:
- 最小限の CSS: 実際に使用されるスタイルのみを抽出
- 自動最適化: 不要なスタイルの除去が自動的に行われる
- 一貫性の保証: サーバーとクライアントで同じスタイルが適用される
クライアントハイドレーション戦略
適切なハイドレーション戦略は、スムーズな SSR アプリケーションの実現に不可欠です。Emotion では、サーバーで生成されたスタイルとクライアントサイドのスタイルを効率的に統合する仕組みを提供しています。
mermaidstateDiagram-v2
[*] --> ServerRendered: サーバーレンダリング完了
ServerRendered --> Hydrating: ハイドレーション開始
Hydrating --> StyleSync: スタイル同期
StyleSync --> ClientReady: クライアント準備完了
ClientReady --> [*]
note right of StyleSync
サーバースタイルを保持
クライアントスタイルと統合
重複スタイルの除去
end note
ハイドレーション戦略の実装:
typescript// クライアントサイドでのハイドレーション
import { hydrateRoot } from 'react-dom/client';
import { CacheProvider } from '@emotion/react';
const clientCache = createCache({ key: 'css' });
// サーバーで生成されたスタイルを保持
const preserveServerStyles = () => {
const serverStyleTags = document.querySelectorAll(
'[data-emotion]'
);
return Array.from(serverStyleTags);
};
// ハイドレーション実行
const hydrate = () => {
const serverStyles = preserveServerStyles();
hydrateRoot(
document.getElementById('root')!,
<CacheProvider value={clientCache}>
<App />
</CacheProvider>
);
// 不要になったサーバースタイルを適切なタイミングで除去
cleanupServerStyles(serverStyles);
};
具体例
Next.js での実装手順
Next.js は React ベースの SSR フレームワークとして広く使用されており、Emotion との統合も非常にスムーズに行えます。以下、段階的な実装手順を説明いたします。
ステップ 1: 必要なパッケージのインストール
bashyarn add @emotion/react @emotion/styled @emotion/server @emotion/cache
これらのパッケージにより、Emotion の完全な SSR 機能を利用できるようになります。各パッケージの役割は以下の通りです:
@emotion/react
: React 用の基本機能@emotion/styled
: styled-components 風の API@emotion/server
: SSR 専用のサーバー機能@emotion/cache
: スタイルキャッシュ管理
ステップ 2: カスタム Document の設定
Next.js での SSR 設定は、pages/_document.tsx
ファイルで行います:
typescript// pages/_document.tsx
import Document, {
Html,
Head,
Main,
NextScript,
DocumentContext,
} from 'next/document';
import createEmotionServer from '@emotion/server/create-instance';
import createCache from '@emotion/cache';
// Emotionキャッシュの設定
const getCache = () =>
createCache({ key: 'css', prepend: true });
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const originalRenderPage = ctx.renderPage;
const cache = getCache();
const {
extractCriticalToChunks,
constructStyleTagsFromChunks,
} = createEmotionServer(cache);
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
(
<CacheProvider value={cache}>
<App {...props} />
</CacheProvider>
),
});
const initialProps = await Document.getInitialProps(
ctx
);
return initialProps;
}
}
ステップ 3: スタイル抽出と HTML 生成の最適化
typescript// pages/_document.tsx(続き)
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
// ... 前のコード
const initialProps = await Document.getInitialProps(
ctx
);
// クリティカルスタイルの抽出
const emotionStyles = extractCriticalToChunks(
initialProps.html
);
const emotionStyleTags =
constructStyleTagsFromChunks(emotionStyles);
return {
...initialProps,
styles: [
...React.Children.toArray(initialProps.styles),
...emotionStyleTags,
],
};
}
render() {
return (
<Html lang='ja'>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
Express.js での実装例
Express.js を使用したカスタムサーバーでの実装例も見てみましょう。より柔軟な設定が可能で、複雑な要件にも対応できます。
基本的なサーバー設定:
typescript// server.js
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { CacheProvider } from '@emotion/react';
import createEmotionServer from '@emotion/server/create-instance';
import createCache from '@emotion/cache';
const app = express();
// 静的ファイルの配信設定
app.use(express.static('public'));
// SSRエンドポイントの実装
app.get('*', (req, res) => {
const cache = createCache({ key: 'css' });
const {
extractCriticalToChunks,
constructStyleTagsFromChunks,
} = createEmotionServer(cache);
// アプリケーションのレンダリング
const html = renderToString(
<CacheProvider value={cache}>
<App url={req.url} />
</CacheProvider>
);
// スタイルの抽出と構築
const chunks = extractCriticalToChunks(html);
const styles = constructStyleTagsFromChunks(chunks);
res.send(createHTML(html, styles));
});
HTML テンプレートの生成関数:
typescript// HTMLテンプレート生成
const createHTML = (html: string, styles: string) => `
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Emotion SSR App</title>
${styles}
</head>
<body>
<div id="root">${html}</div>
<script src="/bundle.js"></script>
</body>
</html>
`;
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`サーバーがポート ${PORT} で起動しました`);
});
パフォーマンス最適化テクニック
実際のプロダクション環境では、さらなるパフォーマンス最適化が重要になります。以下に効果的なテクニックをご紹介します。
1. スタイルのキャッシュ戦略
typescript// 効率的なキャッシュ管理
import LRU from 'lru-cache';
const styleCache = new LRU<string, any>({
max: 1000, // 最大1000エントリ
maxAge: 1000 * 60 * 30, // 30分でキャッシュ失効
});
const getCachedStyles = (
key: string,
generator: () => any
) => {
if (styleCache.has(key)) {
return styleCache.get(key);
}
const styles = generator();
styleCache.set(key, styles);
return styles;
};
2. クリティカル CSS 最適化
typescript// クリティカルCSSの最適化
const optimizeCriticalCSS = (chunks: any) => {
// 重複するCSSルールの除去
const uniqueRules = new Set();
const optimizedChunks = chunks.filter((chunk: any) => {
const ruleHash = generateRuleHash(chunk.css);
if (uniqueRules.has(ruleHash)) {
return false;
}
uniqueRules.add(ruleHash);
return true;
});
return optimizedChunks;
};
const generateRuleHash = (css: string): string => {
// CSS内容のハッシュ値を生成
return crypto.createHash('md5').update(css).digest('hex');
};
3. 非同期スタイル読み込み
typescript// 非クリティカルCSSの遅延読み込み
const loadNonCriticalStyles = () => {
if (typeof window !== 'undefined') {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = '/styles/non-critical.css';
link.media = 'print';
link.onload = () => {
link.media = 'all';
};
document.head.appendChild(link);
}
};
// コンポーネントでの使用例
useEffect(() => {
loadNonCriticalStyles();
}, []);
パフォーマンス測定のためのメトリクス:
最適化項目 | 測定方法 | 目標値 |
---|---|---|
初回描画時間 | First Contentful Paint (FCP) | < 1.8 秒 |
操作可能時間 | Time to Interactive (TTI) | < 3.8 秒 |
レイアウトシフト | Cumulative Layout Shift (CLS) | < 0.1 |
CSS サイズ | バンドルアナライザー | < 50KB |
これらの最適化により、ユーザー体験を大幅に向上させることができます。
まとめ
本記事では、Emotion の Server API を活用した本格的な SSR 実装について詳しく解説いたしました。
実装のポイントを振り返ると:
- 課題の理解: CSS-in-JS における FOUC やパフォーマンス問題の本質的な原因を把握する
- 適切な設計: Server API の仕組みを理解し、サーバーサイドでのスタイル収集を効率的に行う
- 実践的な実装: Next.js や Express.js での具体的な実装手順を段階的に進める
- 継続的な最適化: キャッシュ戦略や非同期読み込みなどの高度なテクニックを適用する
Emotion の Server API を使用することで、CSS-in-JS の柔軟性を保ちながら、優れたパフォーマンスを持つ SSR アプリケーションを構築できることをご理解いただけたでしょう。
特に、従来の手法では困難だった動的なスタイリングと高速な初回レンダリングの両立が、Server API により実現できる点は大きなメリットです。
今後 React アプリケーションで SSR を実装される際は、ぜひ Emotion の Server API を活用して、ユーザー体験の向上を図ってください。適切な実装により、開発効率とアプリケーション品質の両方を大幅に向上させることができるはずです。
関連リンク
- article
Emotion の Server API で本格 SSR を実現
- article
Emotion で SVG アイコンや画像にスタイルを適用する
- article
Motion(旧 Framer Motion)Variants 完全攻略:staggerChildren・when で複雑アニメを整理する
- article
Emotion の Babel プラグインで開発体験を向上させる
- article
Emotion と Jest/Testing Library で UI テストを快適に
- article
Emotion の@emotion/cache を活用したパフォーマンス最適化
- article
Vitest カバレッジ技術の全貌:閾値設定・除外ルール・レポート可視化
- article
gpt-oss 技術ロードマップ 2025:機能進化と対応エコシステムの見取り図
- article
【徹底理解】GPT-5 のマルチモーダル活用最前線:テキスト・画像・音声・動画の融合ポイント
- article
【完全版】Vite ライブラリモード徹底ガイド:npm 配布のための設計と落とし穴
- article
【解決策】TypeScript TS2307「Cannot find module…」が出る本当の原因と最短復旧手順
- article
Emotion の Server API で本格 SSR を実現
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来