<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[POSTD | ニジボックスが運営するエンジニアに向けたキュレーションメディア]]></title><description><![CDATA[POSTD は、ニジボックスが運営する、エンジニアに向けたキュレーションメディアです。ニジボックスはWebサービスの企画、制作、開発、運用を一貫して担うリクルートの100%子会社です。 リクルートグループのオンラインサービスをはじめ、様々な業種・業界・業態のサービス開発を行っております。]]></description><link>https://postd.cc</link><generator>GatsbyJS</generator><lastBuildDate>Sat, 28 Mar 2026 00:39:18 GMT</lastBuildDate><language>ja</language><atom:link href="https://postd.cc/feed/" rel="self" type="application/rss+xml"/><image><title>POSTD | ニジボックスが運営するエンジニアに向けたキュレーションメディア</title><link>https://postd.cc</link></image><item><title><![CDATA[React Server ComponentsにおけるCSS]]></title><description><![CDATA[2023年5月4日、VercelはNext 13.4の安定版リリースを発表し、React Server Componentsを基盤として構築された初のReactフレームワークとなりました。 これは非…]]></description><link>https://postd.cc/css-in-react-server-components/</link><guid isPermaLink="false">https://postd.cc/css-in-react-server-components/</guid><category><![CDATA[開発手法・プロジェクト管理]]></category><category><![CDATA[React]]></category><category><![CDATA[CSS]]></category><pubDate>Wed, 25 Mar 2026 00:00:01 GMT</pubDate><content:encoded><![CDATA[<p>2023年5月4日、VercelはNext 13.4の安定版リリースを発表し、<em>React Server Components</em>を基盤として構築された初のReactフレームワークとなりました。</p>
<p>これは非常に大きな出来事です！RSC（React Server Components）は、Reactにおいて<em>サーバー専用のコード</em>を記述するための公式な方法を提供してくれます。私のブログ記事「<a href="https://www.joshwcomeau.com/react/server-components/">Making Sense of RSC（RSCを理解する）</a>」でも書いたように、RSCは多くの興味深い新たな扉を開いてくれます。</p>
<p><strong>しかし、オムレツを作るには卵を割らなければなりません。</strong> RSCはReactの仕組みに対する根本的な変更です。その影響で、私たちが使ってきたライブラリやツールの一部は…スクランブルエッグのようにごちゃ混ぜになってしまいました 😅。styled-componentsやEmotionのようなライブラリを使用している私たちにとって、今後の明確な道筋は存在していませんでした。</p>
<p>過去数ヶ月間、私はこの問題について深く掘り下げ、互換性の問題についての理解を深め、どのような選択肢があるのかを学んできました。現時点で、私は状況全体をかなりしっかりと把握できていると感じています。</p>
<p>また、水面下で進んでいた非常にエキサイティングな動向もいくつか発見しました。 ✨</p>
<p>CSS-in-JSライブラリを使用しているなら、この記事が多くの疑問を解消し、今後の具体的な選択肢を提示する手助けになれば幸いです。</p>
<p><strong>ただ<strong>____</strong>を使えばいい。</strong></p>
<p>ネット上でこの議論が持ち上がると、最もよくある提案の1つは、別のCSSツールに切り替えることです。結局のところ、Reactエコシステムには選択肢が山ほどあるのですから！</p>
<p>しかし、私たちの多くにとって、これは現実的な提案ではありません。私は自分のブログとコースプラットフォーム全体で、5,000以上のstyled-componentを使用しています。まったく別のツールへ移行するというのは、口で言うほど簡単なことではありません。</p>
<p>LLM（大規模言語モデル）の登場により、この種の大規模なリファクタリングは<em>潜在的には</em>実行可能になりますが、それでも多くの手作業による検証が必要になるでしょう。そして正直なところ、たとえ指を鳴らすだけで即座に別のライブラリに切り替えることができたとしても、私はそうしたくありません。私は<code class="language-text">styled</code> APIが本当に好きなのです！</p>
<p>このブログ記事の後半では、いくつかの代替CSSライブラリについて説明しますが、ここではstyled-componentsに似たAPIを持つ選択肢に焦点を当てます。</p>
<p><strong>2026年のアップデート：styled-componentsが復活しました！</strong></p>
<p>2026年1月、styled-componentsチームはv6.3.0を公開し、React Server Componentsのサポートを導入しました！ 🎉</p>
<p>これは正直言って私にとって<em>非常に</em>エキサイティングなことです。
昨年、styled-componentsが最小限の体制（人員）で「メンテナンスモード」に入ったとき、私はライブラリに大きなアップデートはないだろうと思い込んでいましたが、それは間違っていました！つい先週、リードメンテナーのEvan Jacobs氏が、<a href="https://github.com/orgs/styled-components/discussions/5657#discussioncomment-15730658">このライブラリが現在も活発にメンテナンスされていることを明言しました（別タブで開きます）</a>。</p>
<p>React Server Componentsのコンテキストでstyled-componentsを使用する方法については、こちらで学べます：</p>
<ul>
<li><a href="https://styled-components.com/docs/advanced#react-server-components">styled-components ドキュメント：React Server Components（別タブで開きます）</a></li>
</ul>
<h2>React Server Componentsを紐解く</h2>
<p>互換性の問題を理解するためには、React Server Componentsについて理解する必要があります。しかし、その話をする前に、<em>サーバーサイドレンダリング（SSR）</em>について理解しているか確認する必要があります。</p>
<p>SSRは、いくつかの異なる戦略や実装を含む総称ですが、最も典型的なバージョンは次のようになります：</p>
<ol>
<li>ユーザーが私たちのウェブアプリにアクセスする。</li>
<li>リクエストはNode.js（ブラウザを持たないサーバーランタイム）によって受信され、そこでReactが実行される。アプリケーションがレンダリングされ、初期UIのすべてを含む完全に形成されたHTMLドキュメントが生成される。</li>
<li>このHTMLドキュメントがユーザーのデバイスに読み込まれると、Reactはまったく同じコンポーネントをすべて再レンダリングし、サーバーで行われた作業を繰り返す。ただし、<em>新しい</em>HTML要素を生成するのではなく、サーバーによって生成された既存のHTML要素を「引き継ぐ（adopt）」(*)。これは<em>ハイドレーション（hydration）</em>として知られている。</li>
</ol>
<p>*ここでは「HTML要素」という用語を使用していますが、より正確な用語は「DOMノード」です。サーバーがHTMLを生成し、ブラウザがそれをDOMとして知られる動的な表現にレンダリングします。</p>
<p>Reactは、対話性（インタラクティビティ）を処理するためにユーザーのデバイス上で実行される必要があります。サーバーによって生成されたHTMLは完全に静的です。そこには私たちが記述したイベントハンドラ（例：<code class="language-text">onClick</code>）は含まれず、私たちが指定した参照（<code class="language-text">ref</code>属性による）もキャプチャされていません。</p>
<p><strong>ではなぜ、まったく同じ作業をすべてやり直さなければならないのでしょうか？</strong> Reactがユーザーのデバイスで起動したとき、既存のUIの集まりを発見します。しかし、どのコンポーネントがどのHTML要素を所有しているかといった<em>コンテキスト（文脈）</em>は一切持っていません。Reactは、コンポーネントツリーを再構築するためにまったく同じ作業をする必要があります。これにより、既存のHTMLを正しく結びつけ、イベントハンドラや参照を正しい要素にアタッチできるようになります。</p>
<p>Reactは、サーバーの処理が終わった時点からスムーズに引き継げるよう、内部でコンポーネントツリーの対応関係（マップ）を構築する必要があるのです。</p>
<p><strong>このモデルには大きな制限が1つあります。</strong> 私たちが書くコードはすべて、サーバー<em>と</em>クライアントの両方で実行されます。サーバー上でのみ排他的にレンダリングされるコンポーネントを作成する方法はありません。</p>
<p>データベースにデータを持つ、フルスタックのウェブアプリケーションを構築していると仮定しましょう。もしあなたがPHPなどの言語の経験者であれば、次のような書き方ができると期待するかもしれません：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Home</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> data <span class="token operator">=</span> db<span class="token punctuation">.</span><span class="token function">query</span><span class="token punctuation">(</span><span class="token string">'SELECT * FROM SNEAKERS'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>main<span class="token operator">></span>
      <span class="token punctuation">{</span>data<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">item</span> <span class="token operator">=></span> <span class="token punctuation">(</span>
        <span class="token operator">&lt;</span>Sneaker key<span class="token operator">=</span><span class="token punctuation">{</span>item<span class="token punctuation">.</span>id<span class="token punctuation">}</span> item<span class="token operator">=</span><span class="token punctuation">{</span>item<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
      <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>main<span class="token operator">></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>理論的には、このコードはサーバー上で問題なく動作する可能性がありますが、その<em>まったく同じコード</em>がユーザーのデバイス上で再実行されます。クライアント側のReactは私たちのデータベースにアクセスできないため、これは問題となります。Reactに対して「このコードはサーバー上でのみ実行し、結果のデータをクライアントで再利用してくれ」と伝える方法はありません。</p>
<p>Reactの上に構築されたメタフレームワークは、独自の解決策を考え出しました。例えば、Next.jsでは次のようなことができます：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getServerSideProps</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> db<span class="token punctuation">.</span><span class="token function">query</span><span class="token punctuation">(</span><span class="token string">'SELECT * FROM SNEAKERS'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      data<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">Home</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> data <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>main<span class="token operator">></span>
      <span class="token punctuation">{</span>data<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">item</span> <span class="token operator">=></span> <span class="token punctuation">(</span>
        <span class="token operator">&lt;</span>Sneaker key<span class="token operator">=</span><span class="token punctuation">{</span>item<span class="token punctuation">.</span>id<span class="token punctuation">}</span> item<span class="token operator">=</span><span class="token punctuation">{</span>item<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
      <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>main<span class="token operator">></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Next.jsチームはこう言いました。「よし、まったく同じ<em>React</em>のコードがサーバーとクライアントで実行されなければならないことは分かった…しかし、Reactの外部に、サーバー上でのみ実行される<em>追加の</em>コードを加えることができるぞ！」。</p>
<p>Next.jsサーバーがリクエストを受信すると、まず<code class="language-text">getServerSideProps</code>関数を呼び出し、そこから返された値はすべて、propsとしてReactコードに渡されます。
まったく同じ<em>React</em>コードがサーバーとクライアントで実行されるため、問題はありません。なかなか賢いやり方ですよね？</p>
<p>私は正直、今日でもこのアプローチの大ファンです。しかし、Reactの制限による必要性から作成されたAPIであり、少し回避策のように感じられることも事実です。</p>
<p>また、各ルートの最上部にある<em>ページ</em>レベルでしか機能しません。好きな場所に<code class="language-text">getServerSideProps</code>関数を自由に配置できるわけではないのです。</p>
<p><strong>React Server Componentsは、この問題に対してより直感的な解決策を提供します。</strong> RSCを使用すると、データベース呼び出しなどサーバー専用の作業をReactコンポーネント内で直接行えます：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">Home</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> db<span class="token punctuation">.</span><span class="token function">query</span><span class="token punctuation">(</span><span class="token string">'SELECT * FROM SNEAKERS'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>main<span class="token operator">></span>
      <span class="token punctuation">{</span>data<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">item</span> <span class="token operator">=></span> <span class="token punctuation">(</span>
        <span class="token operator">&lt;</span>Sneaker key<span class="token operator">=</span><span class="token punctuation">{</span>item<span class="token punctuation">.</span>id<span class="token punctuation">}</span> item<span class="token operator">=</span><span class="token punctuation">{</span>item<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
      <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>main<span class="token operator">></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>「React Server Components」のパラダイムでは、コンポーネントはデフォルトで<em>Server Components（サーバーコンポーネント）</em>になります。サーバーコンポーネントはサーバー上でのみ実行されます。このコードはユーザーのデバイスでは再実行<em>されません</em>。コードはJavaScriptバンドルに含まれることすらありません！</p>
<p>この新しいパラダイムには<em>Client Components（クライアントコンポーネント）</em>も含まれています。クライアントコンポーネントは、サーバーとクライアントの<em>両方</em>で実行されるコンポーネントです。あなたが「従来の」（RSC以前の）Reactで書いてきたすべてのReactコンポーネントは、クライアントコンポーネントです。<strong>つまり、既存の概念に新しい名前がついただけなのです。</strong></p>
<p>ファイルの先頭に新しい<code class="language-text">"use client"</code>ディレクティブを記述することで、明示的にクライアントコンポーネントとして指定（オプトイン）します：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token string">'use client'</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">Counter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>count<span class="token punctuation">,</span> setCount<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setCount</span><span class="token punctuation">(</span>count <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token operator">></span>
      <span class="token literal-property property">Count</span><span class="token operator">:</span> <span class="token punctuation">{</span>count<span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>このディレクティブは「クライアント境界（client boundary）」を作成します。このファイル内のすべてのコンポーネントとインポートされたコンポーネントは、クライアントコンポーネントとしてレンダリングされます。最初にサーバーで、次にクライアントで再び実行されます。</p>
<p>他のReact機能（例：フック）とは異なり、React Server Componentsはバンドラーとの深い統合を必要とします。私がこれを書いている2024年4月現在、React Server Componentsを使用する唯一の現実的な方法はNext.jsを使うことです。ただし、将来的にはこれも変わると予想しています。</p>
<h3>サーバーコンポーネントには制限がある</h3>
<p>サーバーコンポーネントについて理解しておくべき重要な点は、これらが「完全な」React体験を提供するわけではないということです。ほとんどのReact APIはサーバーコンポーネントでは機能しません。</p>
<p>例えば、<code class="language-text">useState</code>です。状態（state）変数が変更されると、コンポーネントは再レンダリングされますが、サーバーコンポーネントは再レンダリング<em>できません</em>。そのコードは決してブラウザに送信されないため、Reactは状態の変更をどのように処理すればよいか全く分かりません。Reactの観点から見ると、サーバーコンポーネントによって生成されたマークアップは確定しており、クライアントで変更できません(*)。</p>
<p>*誤解のないように言っておくと、DOM自体が不変なわけではありません。プレーンなJavaScriptを使用して変更は可能です。しかし、Reactはサーバーコンポーネントによって生成されたアプリケーションの部分を再レンダリングはできません。</p>
<p>同様に、サーバーコンポーネント内で<code class="language-text">useEffect</code>を使用できません。エフェクトはサーバー上で実行されず、クライアントでのレンダリング後にのみ実行されるからです。そして、サーバーコンポーネントはJavaScriptバンドルから除外されるため、クライアント側のReactは私たちが<code class="language-text">useEffect</code>フックを記述したことを知る由もありません。</p>
<p><code class="language-text">useContext</code>フックでさえ、サーバーコンポーネントの内部では使用できません。なぜなら、Reactコンテキストをサーバーコンポーネントとクライアントコンポーネントの両方でどのように共有するかという問題を、Reactチームがまだ解決していないからです。</p>
<p><strong>私はこのように考えています：</strong> サーバーコンポーネントは、少なくとも私たちが従来理解してきたような意味において、<em>本当の</em>Reactコンポーネントではありません。オリジナルのHTMLを作成するためにサーバーによってレンダリングされる、PHPテンプレートにずっと近いものです。本当の革新は、サーバーコンポーネントとクライアントコンポーネントが同じアプリケーション内に共存できることなのです！</p>
<p><strong>さらに深く掘り下げる</strong></p>
<p>このブログ記事では、React Server Componentsの最も関連性の高い詳細、つまりCSS-in-JSフレームワークとの互換性の問題を理解するために知っておくべきことに焦点を当てます。</p>
<p>しかし、もしReact Server Componentsについてもっと詳しく学びたいのであれば、この新しい世界をより深く探求した別のブログ記事があります：</p>
<ul>
<li><a href="https://www.joshwcomeau.com/react/server-components/">「Making Sense of React Server Components（React Server Componentsを理解する）」</a></li>
</ul>
<h2>CSS-in-JSライブラリの仕組み</h2>
<p>さて、React Server Componentsの基本についてカバーしました。次に、💅 styled-componentsのようなCSS-in-JSライブラリの基本について話しましょう！</p>
<p>簡単な例を挙げます：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">import</span> styled <span class="token keyword">from</span> <span class="token string">'styled-components'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Homepage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>BigRedButton<span class="token operator">></span>
      Click me<span class="token operator">!</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>BigRedButton<span class="token operator">></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> BigRedButton <span class="token operator">=</span> styled<span class="token punctuation">.</span>button<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">
  font-size: 2rem;
  color: red;
</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><code class="language-text">.red-btn</code>のようなクラスにCSSを記述する代わりに、新しく生成されたReactコンポーネントにそのCSSをアタッチします。これがstyled-componentsを特別なものにしている理由です。クラスではなく、<em>コンポーネント</em>が再利用可能なプリミティブ（基本要素）なのです。</p>
<p><code class="language-text">styled.button</code>は、新しいReactコンポーネントを動的に生成してくれる関数であり、そのコンポーネントを<code class="language-text">BigRedButton</code>という変数に割り当てています。その後、他のReactコンポーネントを使用するのと同じ方法で、このReactコンポーネントを使用できます。これにより、大きな赤いテキストを持つ<code class="language-text">&lt;button></code>タグがレンダリングされます。</p>
<p>しかし、ライブラリは具体的にどのようにしてこのCSSをこの要素に<em>適用</em>するのでしょうか？主に3つの選択肢があります(*)：</p>
<p>*さらに、CSSオブジェクトモデル（CSSOM）を通じて動的にスタイルを追加できますが、これはSSR中には選択できないため、ここでは含めていません。</p>
<ol>
<li>スタイルは、<code class="language-text">style</code>属性を通じてインラインで適用できる。</li>
<li>スタイルは、個別のCSSファイルに記述し、<code class="language-text">&lt;link></code>経由で読み込める。</li>
<li>スタイルは、<code class="language-text">&lt;style></code>タグ内に配置できる。通常は現在のHTMLドキュメントの<code class="language-text">&lt;head></code>内に配置される。</li>
</ol>
<p>このコードを実行してDOMを検査すると、答えが明らかになります：</p>
<div class="gatsby-highlight" data-language="html"><pre style="counter-reset: linenumber NaN" class="language-html line-numbers"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>html</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span> <span class="token attr-name">data-styled</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>active<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
      <span class="token selector">.abc123</span> <span class="token punctuation">{</span>
        <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span>
        <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>abc123<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
      Click me!
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>html</span><span class="token punctuation">></span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>styled-componentsは、提供されたスタイルをライブラリが管理する<code class="language-text">&lt;style></code>タグに書き込みます。これらのスタイルをこの特定の<code class="language-text">&lt;button></code>に紐付ける（適用する）ために、<code class="language-text">"abc123"</code>という一意のクラス名を生成します。</p>
<p>これらの作業はすべて、最初のReactレンダリング時に行われます：</p>
<ul>
<li>クライアントサイドレンダリングのコンテキスト（例：Parcel、create-react-app）では、Reactが作成する他のDOMノード同様、<code class="language-text">&lt;style></code>タグもデバイス上で動的に生成される。</li>
<li>サーバーサイドレンダリングのコンテキスト（例：Next、Remix）では、この作業はサーバー上で行われる。生成されたHTMLドキュメントには、この<code class="language-text">&lt;style></code>タグが含まれる。</li>
</ul>
<p>ユーザーがアプリケーションを操作するにつれて、特定のスタイルを作成、変更、または破棄する必要が生じる場合があります。例えば、条件付きでレンダリングされるstyled-componentを考えてみましょう：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Header</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token function">useUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span><span class="token operator">></span>
      <span class="token punctuation">{</span>user <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>
        <span class="token operator">&lt;</span>SignOutButton onClick<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>signOut<span class="token punctuation">}</span><span class="token operator">></span>
          Sign Out
        <span class="token operator">&lt;</span><span class="token operator">/</span>SignOutButton<span class="token operator">></span>
      <span class="token punctuation">)</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token operator">></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> SignOutButton <span class="token operator">=</span> styled<span class="token punctuation">.</span>button<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">
  color: white;
  background: red;
</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>最初は、<code class="language-text">user</code>が未定義であれば<code class="language-text">&lt;SignOutButton></code>はレンダリングされないため、これらのスタイルは存在しません。後でユーザーがログインすると、アプリケーションが再レンダリングされ、styled-componentが作動して、これらのスタイルを<code class="language-text">&lt;style></code>タグに注入します。</p>
<p>基本的に、すべてのstyled componentは通常のReactコンポーネントですが、ちょっとした副作用も併せ持っています：<strong>それは、自身のスタイルも<code class="language-text">&lt;style></code>タグにレンダリングするということです。</strong></p>
<p>本記事の趣旨としては、これが最も重要なポイントです。ライブラリの内部動作についてさらに深く掘り下げたい場合は、「<a href="https://www.joshwcomeau.com/react/demystifying-styled-components/">Demystifying styled-components（styled-componentsの謎を解く）</a>」というブログ記事をご参照ください。</p>
<h2>問題の核心</h2>
<p>これまでに学んだことを要約すると：</p>
<ul>
<li>「React Server Components」はReactの新しいパラダイムであり、新しいタイプのコンポーネントである<em>サーバーコンポーネント</em>を提供する。サーバーコンポーネントはサーバー上でのみレンダリングされる。そのコードは、クライアントに送信されるJSバンドルに含まれることすらない。</li>
<li>styled-componentsライブラリは、CSSがアタッチされたReactコンポーネントを動的に作成できるようにする。これは、コンポーネントが再レンダリングされる際に更新される<code class="language-text">&lt;style></code>タグを管理することによって機能する。</li>
</ul>
<p>根本的な非互換性は、styled-componentsがブラウザ内で実行されるように設計されているのに対し、サーバーコンポーネントは決してブラウザに触れないという点にあります。</p>
<p>styled-componentsは内部で<code class="language-text">useContext</code>フックに大きく依存しています。Reactのライフサイクルに組み込まれることを意図していますが、サーバーコンポーネントにはReactのライフサイクルが<em>存在しません</em>。そのため、この新しいRSCの世界でstyled-componentsを使用したい場合は注意が必要です。styled-componentを<em>1つ</em>でも使用しているコンポーネントは、すべてクライアントコンポーネントにする必要があります。</p>
<p>皆さんはどうか分かりませんが、私の場合、スタイリングを<em>一切含まない</em>Reactコンポーネントを作ることはかなり稀です。私のコンポーネントファイルの90%以上がstyled-componentsを使用していると見積もっています。これらのコンポーネントの大部分は、それ以外は完全に静的です。状態やその他のクライアントコンポーネントの機能は使用していません。</p>
<p>この新しいパラダイムの利点を最大限に活かせないということなので、これは確かに残念なことです…<strong>しかし、だからといって絶望するほどのことではありません。</strong></p>
<p>もし私がReact Server Componentsについて1つだけ変えることができるとしたら、それは「クライアントコンポーネント」という名前でしょう。この名前は、これらのコンポーネントがクライアントでのみレンダリングされることを示唆していますが、それは事実ではありません。覚えておいてください、「クライアントコンポーネント」とは古いものに対する新しい名前なのです。2023年5月以前に作成されたすべてのReactアプリケーションにおける、すべてのReactコンポーネントはクライアントコンポーネントです。</p>
<p>styled-componentsアプリケーションでは、コンポーネントの10%しかサーバーコンポーネントにできないかもしれません。<em>それでも改善であることに変わりはありません！</em> 私たちのアプリケーションは、RSC以前の世界よりも少し軽く、速くなるでしょう。<strong>私たちは依然としてSSRのすべての恩恵を受けています。それは変わっていません。</strong></p>
<p><strong>2026年 アップデート</strong></p>
<p>前述の通り、styled-componentsは最近更新され、React Server Componentsをサポートするようになりました。これは、<code class="language-text">&lt;style></code>タグをホイスト（巻き上げ）し、重複を排除する新しいReact機能によって可能になりました。</p>
<p>幸いなことに、サーバーコンポーネントはクライアントコンポーネントに切り替える必要なくstyled-componentsを使用できるようになりました。開発チームはこのアップデートで見事な対応をしてくれました。 ❤️</p>
<h2>ゼロランタイムCSS-in-JSライブラリの世界</h2>
<p>React Server Componentsが登場する以前から、CSS-in-JSの領域では、作業をより早い段階へ移し、ランタイム（実行時）ではなく<em>コンパイル時</em>にスタイルを抽出する動きがありました。</p>
<p>最新のReactアプリケーションにはビルドステップがあり、そこでTypeScript/JSXをJavaScriptに変換し、何千もの個別のファイルを少数のバンドルにパッケージ化します。この作業は、アプリケーションがデプロイされたとき、本番環境で実行を開始する前に行われます。<strong>ランタイム（実行時）ではなく、このステップの間にstyled componentを処理してはどうでしょうか？</strong></p>
<p>これが、このセクションで議論するすべてのライブラリの背後にある核となるアイデアです。さっそく見ていきましょう！</p>
<h3>Linaria</h3>
<p><a href="https://github.com/callstack/linaria">Linaria（別タブで開きます）</a>はずっと前、<em>2017年</em>に作成されました。styled-componentsとほぼ同じくらい古いです！</p>
<p>APIはstyled-componentsとまったく同じに見えます：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> styled <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@linaria/react'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Homepage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>BigRedButton<span class="token operator">></span>
      Click me<span class="token operator">!</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>BigRedButton<span class="token operator">></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> BigRedButton <span class="token operator">=</span> styled<span class="token punctuation">.</span>button<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">
  font-size: 2rem;
  color: red;
</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>ここで非常に巧妙なのが以下の点です：</strong> コンパイルステップの間に、Linariaはこのコードを変換し、すべてのスタイルを<a href="https://github.com/css-modules/css-modules">CSS Modules（別タブで開きます）</a>に移動します。</p>
<p>Linariaを実行した後、コードは次のようになります：</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token comment">/* /components/Home.module.css */</span>
<span class="token selector">.BigRedButton</span> <span class="token punctuation">{</span>
  <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span>
  <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">/* /components/Home.js */</span>
<span class="token keyword">import</span> styles <span class="token keyword">from</span> <span class="token string">'./Home.module.css'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Homepage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>button className<span class="token operator">=</span><span class="token punctuation">{</span>styles<span class="token punctuation">.</span>BigRedButton<span class="token punctuation">}</span><span class="token operator">></span>
      Click me<span class="token operator">!</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>CSS Modulesにまだ馴染みがない方のために説明すると、これはCSSに対する軽量な抽象化です。ほとんどプレーンなCSSとして扱うことができますが、グローバルに一意な名前を気にする必要はありません。コンパイルステップの間に、Linariaが魔法をかけた直後、<code class="language-text">.BigRedButton</code>のような一般的な名前は<code class="language-text">.abc123</code>のような一意な名前に変換されます。</p>
<p><strong>重要なのは、CSS Modulesがすでに広くサポートされているということです。</strong> 存在する中で最も人気のある選択肢の1つです。Next.jsのようなメタフレームワークは、すでにCSS Modulesをファーストクラスでサポートしています。</p>
<p>そのため、車輪を再発明し、堅牢で本番環境に対応できるCSSソリューションを構築するのに何年も費やすのではなく、Linariaチームは近道を取ることにしました。私たちがstyled-componentsを書くと、Linariaがそれを事前処理してCSS Modulesにし、それがさらにプレーンなCSSに処理されます。これらはすべてコンパイル時に行われます。</p>
<p><strong>ランタイム vs コンパイル時のトレードオフ</strong></p>
<p>RSCが存在するずっと前から、コミュニティはLinariaのようなコンパイル時のライブラリを構築してきました。パフォーマンス上の利点は明白です：styled-componentsはJavaScriptバンドルに11キロバイト（gzip）を追加しますが、Linariaは事前にすべての作業を完了させるため0kbです。さらに、スタイルの収集・適用に時間を費やす必要がなく、サーバーサイドレンダリングも少し速くなります。</p>
<p>とは言え、styled-componentsのランタイムは単なるお荷物というわけではありません。コンパイル時には不可能なことをstyled-componentsで行うことができます。例えば、styled-componentsはReactの状態の一部が変更されたときにCSSを動的に更新できます。</p>
<p>幸いなことに、styled-componentsが最初に作成されてからの10年近くで、CSSははるかに強力になりました。ほとんどの動的なユースケースをCSS変数で処理できます。最近では、ランタイムを持つことが特定の状況下で少し良い開発者体験（DX）を提供できる場合もありますが、私の意見では、もはや本当に必要なものではありません。</p>
<p>これは、Linariaやその他のコンパイル時のCSS-in-JSライブラリが、真の意味でstyled-componentsやEmotionにそのまま置き換えられる代替品には<em>ならない</em>ということです。動的なコンポーネントを作り直すのにいくらかの時間を費やす必要があります。しかし、これは全く異なるCSSツールに切り替えることと比較すれば、ほんのわずかな作業にすぎません。</p>
<h4>Linariaへの移行</h4>
<p>では、私たちは皆、自分のstyled-componentsアプリケーションをLinariaに移行すべきなのでしょうか？</p>
<p>私が2024年に<a href="https://www.joshwcomeau.com/blog/how-i-built-my-blog-v2/">ブログを再構築した（別タブで開きます）</a>際、styled-componentsからLinariaに切り替えることを決めました。私のブログはNext.jsを使用しており、統合を管理するパッケージである<a href="https://github.com/dlehmhus/next-with-linaria">next-with-linaria（別タブで開きます）</a>を見つけました。</p>
<p>このアプローチは私にとって十分うまく機能しましたが、途中で多くの癖に悩まされました。そしておそらく最大の問題は、LinariaとNext.jsバインディングの両方がかなりニッチであるということです。このスタックを使用する開発者の大規模なコミュニティが存在しないため、問題に遭遇した際に行き詰まりを解消するリソースが少ないのです。</p>
<p>もしあなたが経験豊富な開発者であり、問題をデバッグするためにNPMパッケージを掘り下げることを気にしないのであれば、このスタックは実際にはかなり素晴らしいものでしょう。しかし、ほとんどの状況において本当にお勧めできるものではありません。</p>
<h3>Panda CSS</h3>
<p><img src="https://www.joshwcomeau.com/_next/image/?url=%2Fimages%2Fcss-in-rsc%2Fpandacss.png&#x26;w=256&#x26;q=75" alt="スケートボードとタピオカティーを持ったかわいいパンダ、Panda CSSのマスコット"></p>
<p><a href="https://github.com/chakra-ui/panda">Panda CSS（別タブで開きます）</a>は、人気のあるコンポーネントライブラリであるChakra UIを構築した人々によって開発された最新のCSS-in-JSライブラリです。</p>
<p>Panda CSSには多くの異なるインターフェースが付属しています。Tailwindのように使用し、<code class="language-text">mb-5</code>のような省略形のクラスを指定できます。Stitchesのように使用し、バリアントやcvaを使用できます。あるいは、styled-componentsのように使用できます。</p>
<p><code class="language-text">styled</code> APIを使用した場合は次のようになります：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> styled <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../styled-system/jsx'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Homepage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>BigRedButton<span class="token operator">></span>
      Click me<span class="token operator">!</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>BigRedButton<span class="token operator">></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> BigRedButton <span class="token operator">=</span> styled<span class="token punctuation">.</span>button<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">
  font-size: 2rem;
  color: red;
</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Linariaと同様に、Panda CSSもコンパイル時に処理されランタイムコードは取り除かれますが、代わりに<em>Tailwindスタイルのユーティリティクラス</em>へとコンパイルされます。最終的な結果は次のようになります：</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token comment">/* /styles.css */</span>
<span class="token selector">.font-size_2rem</span> <span class="token punctuation">{</span>
  <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.color_red</span> <span class="token punctuation">{</span>
  <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">/* /components/Home.js */</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Homepage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>button className<span class="token operator">=</span><span class="token string">"font-size_2rem color_red"</span><span class="token operator">></span>
      Click me<span class="token operator">!</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><code class="language-text">color: red</code>のような一意のCSS宣言ごとに、Panda CSSは1つの中央CSSファイルに新しいユーティリティクラスを作成します。その後、このファイルが私たちのReactアプリケーションのすべてのルートで読み込まれることになります。</p>
<p>個人的には、Panda CSSをぜひ採用したいところです。関連する経験を豊富に持つ堅実なチームによって開発されており、使い慣れたAPIを提供し、かわいいスケートボードに乗るパンダのマスコットまでいるのですから！</p>
<p>しかし、実験してみた結果、これは私には合わないということに気づきました。私の抱える問題のいくつかは、取るに足らない/表面的なものです。例えば、Panda CSSはプロジェクト内に大量の<em>ファイル</em>を生成し、ディレクトリが散らかります。これは私には少し厄介に感じられますが、最終的には重大な問題ではありません。</p>
<p>私にとってのより大きな問題は、Panda CSSに重要な機能が欠けていることです。<strong>コンポーネントを簡単に相互参照（クロスリファレンス）できないのです。</strong></p>
<p>これは少し高度なトピックであり、ここで取り上げるには話が逸れすぎます。別のブログ記事「<a href="https://www.joshwcomeau.com/css/styled-components/#single-source-of-styles-2">The styled-components Happy Path（styled-componentsの幸せな道）</a>」でこのパターンを共有しています。</p>
<p>核となるアイデアは、<em>文脈に応じた（contextual）</em>スタイルを含め、特定のコンポーネントに対する<em>すべて</em>のスタイルを1箇所にまとめられるべきだということです。例えば<code class="language-text">TextLink</code>コンポーネントなら、デフォルトのスタイルを指定できる<em>だけでなく</em>、<code class="language-text">Aside</code>や<code class="language-text">Quote</code>内でレンダリングされた場合のオーバーライド（上書き）も指定できるべきです：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">import</span> Link <span class="token keyword">from</span> <span class="token string">'next/link'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token punctuation">{</span> AsideWrapper <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/components/Aside'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> QuoteWrapper <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/components/Quote'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> TextLink <span class="token operator">=</span> <span class="token function">styled</span><span class="token punctuation">(</span>Link<span class="token punctuation">)</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">
  /* デフォルトのスタイル */
  color: var(--color-primary);
  text-decoration: none;

  /* TextLinkがAside内にある場合のオーバーライド */
  </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>AsideWrapper<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> &amp; {
    color: inherit;
    text-decoration: underline;
  }

  /* TextLinkがQuote内にある場合のオーバーライド */
  </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>QuoteWrapper<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> &amp; {
    font-weight: var(--font-weight-bold);
    color: var(--color-secondary);
  }
</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>悲しいことに、このパターンはPanda CSSではうまく機能しません。上で述べたように、Panda CSSは各コンポーネントに対して一意のクラス名を生成するのではなく、<em>ユーティリティ</em>クラスを生成します。その結果、このようにコンポーネントを相互参照するための組み込みの方法が存在しないのです(*)。</p>
<p>*データ属性を使用して自分たちで無理やり実装はできますが、実装のハードルがはるかに高くなるため、現実的にこのパターンを使い続ける人がいるとは思えません。</p>
<p>もしあなたがこのパターンに興味がないのであれば、Panda CSSはあなたのアプリケーションにとって良い選択肢になるでしょう！しかし私にとって、これは採用を見送る決定的な理由となります。</p>
<h3>Pigment CSS</h3>
<p>最も人気のあるReactコンポーネントライブラリの1つである<a href="https://mui.com/material-ui/">Material UI（別タブで開きます）</a>は、Emotionの上に構築されています(*)。彼らの開発チームは、RSCの互換性を巡るまったく同じ問題に取り組んでおり、それについて何か行動を起こすことを決定しました。</p>
<p>*このライブラリはstyled-componentsに切り替えるオプションも提供しています。</p>
<p>彼らは最近、新しいライブラリをオープンソース化しました。<a href="https://github.com/mui/pigment-css">Pigment CSS（別タブで開きます）</a>という名前です。そのAPIは、この時点でかなり馴染みのあるものに見えるはずです：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> styled <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@pigment-css/react'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Homepage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>BigRedButton<span class="token operator">></span>
      Click me<span class="token operator">!</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>BigRedButton<span class="token operator">></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> BigRedButton <span class="token operator">=</span> styled<span class="token punctuation">.</span>button<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">
  font-size: 2rem;
  color: red;
</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Pigment CSSはコンパイル時に実行され、Linariaと同じ戦略を使用し、CSS Modulesにコンパイルします。Next.jsとViteの両方用のプラグインがあります。</p>
<p>実際、これは<a href="https://github.com/Anber/wyw-in-js">WyW-in-JS（別タブで開きます）</a>（"What you Want in JS"：JSであなたが望むもの）という低レベルのツールを使用しています。このツールはLinariaのコードベースから進化し、「CSS Modulesへのコンパイル」のビジネスロジックを分離して汎用化しました。これにより、Pigment CSSのようなライブラリがその上に独自のAPIを構築できるようになりました。</p>
<p>正直に言って、これは私にとって完璧な解決策のように感じられます。CSS Modulesはすでに十分に実戦テストされ、高度に最適化されています。そして、私がこれまでに見た限りでは、Pigment CSSは優れたパフォーマンスとDX（開発者体験）を備えた素晴らしいものです。</p>
<p>Material UIの次のメジャーバージョンはPigment CSSをサポートし、最終的にはEmotion/styled-componentsのサポートを完全に打ち切る計画です。結果として、Pigment CSSは最も広く使用されるCSS-in-JSライブラリの1つになるでしょう。Material UIはNPMで週に約500万回ダウンロードされており、これはReact自体の約5分の1に相当します！</p>
<p>まだ非常に初期の段階です。Pigment CSSは2024年3月にオープンソース化されたばかりです。しかし、チームはこのプロジェクトに多大なリソースを投入しています。状況がどのように発展していくのか見るのが待ちきれません！</p>
<p><strong>2026年 アップデート</strong></p>
<p>このブログ記事は元々2024年4月に公開されましたが、その当時、Pigmentには多くの勢いがあるように見えました。残念ながら、その勢いは少し衰えてしまったようです。</p>
<p>私の知る限り、このライブラリはまだ安定版（stable）とは見なされておらず、NPMパッケージは1年以上更新されていません。</p>
<p>これが最良の移行オプションになるだろうと本当に思っていたので、非常に残念です。しかし、うまくいっていないようです。</p>
<h3>まだまだ続く</h3>
<p>これまでに取り上げたライブラリに加えて、エコシステムには興味深いことを行っているプロジェクトがさらに多く存在します。私が注目している他のプロジェクトをいくつか紹介します：</p>
<ul>
<li><a href="https://github.com/jantimon/next-yak">next-yak（別タブで開きます）</a> — スイス最大のeコマース小売業者の開発者によって作成されたコンパイル時のCSS-in-JSライブラリ。二次的なAPIの多くを再実装し、可能な限りstyled-componentsのドロップイン代替となることを目指している。</li>
<li><a href="https://github.com/kuma-ui/kuma-ui">Kuma UI（別タブで開きます）</a> — このライブラリはかなり野心的なことに挑戦している。「ハイブリッド」設計であり、ほとんどのスタイルはコンパイル時に抽出されるが、クライアントコンポーネント用にランタイムも利用可能である。</li>
<li><a href="https://github.com/devongovett/unplugin-parcel-macros">Parcel macros（別タブで開きます）</a> — Parcelは最近「マクロ（macros）」を実装したバンドラーである。マクロは、コンパイル時のCSS-in-JSライブラリを含むあらゆるものを構築するために使用できるツールである。驚くべきことに、この機能はParcel固有のものではなく、Next.jsと一緒に使用できる。</li>
</ul>
<h2>今後の道筋</h2>
<p>さて、私たちは非常に多くの選択肢を検討してきましたが、依然として疑問は残ります。もしあなたが「レガシーな」CSS-in-JSライブラリを使用している本番アプリケーションを持っている場合、<strong>実際に何をすべきなのでしょうか？</strong></p>
<p>少し直感に反しますが、多くの場合、私はあなたが実際に何かをする必要は<em>ない</em>と考えています。 😅</p>
<p>オンラインでの多くの議論は、モダンなReact / Next.jsアプリケーションではstyled-componentsを<em>使用できない</em>かのように聞こえます。あるいは大きなパフォーマンス上のペナルティがあるかのようにも。<strong>しかし、それは本当ではありません。</strong></p>
<p>多くの人々がRSC（React Server Components）とSSR（Server Side Rendering）を混同しているように感じます。サーバーサイドレンダリングは、これまでと全く同じように機能し続けており、これらの影響を受けることはありません。</p>
<p>NextのApp Routerやその他のRSC実装に移行したからといって、アプリケーションが<em>遅く</em>なるべきではありません。実際には、おそらく少し速くなるでしょう！</p>
<p>パフォーマンスの観点から見ると、RSCとゼロランタイムCSSライブラリによる主な利点は、TTI（Time To Interactive：操作可能になるまでの時間）です。これは、UIがユーザーに表示されてから、UIが完全にインタラクティブ（対話可能）になるまでの遅延です。これを軽視すると、ユーザーがボタンをクリックしても何も起こらない（裏側でアプリケーションのハイドレーションがまだ完了していないため）という悪いユーザー体験を生み出す可能性があります。</p>
<p>したがって、現在アプリケーションのハイドレーションに長い時間がかかっている場合は、ゼロランタイムCSSライブラリへの移行を主張する強力な根拠となります。しかし、アプリがすでに安定したTTIを持っている場合、ユーザーがおそらくこの移行から利益を得ることはないでしょう。</p>
<p><strong>多くの場合、最大の問題はFOMO（Fear Of Missing Out：取り残される恐怖）でしょう。</strong> 開発者として、私たちは最新かつ最高のツールを使いたいものです。新しい最適化の恩恵を十分に受けていないと知りながら、たくさんの<code class="language-text">"use client"</code>ディレクティブを追加するのは楽しいことではありません。しかし、これは本当に大規模な移行をするための説得力のある理由なのでしょうか？</p>
<p><strong>2026年 アップデート</strong></p>
<p>この結論は、styled-componentsがReact Server Componentsをサポートするように更新されることは決してないと思われていた2024年に書かれました。現在、styled-componentsが更新<em>された</em>ことで、答えはずっとシンプルになりました。</p>
<p>私は、styled-componentsが積極的にメンテナンスされている限り、これを使い続けるつもりです。 😄</p>
<p>そうは言っても、コミュニティの勢いがもはやstyled-componentsにないことは認めるべきでしょう。2023年、styled-componentsとTailwindはどちらもNPMから週に約600万回ダウンロードされていました。2026年初頭、styled-componentsは30%以上成長し週に約800万ダウンロードとなりました。一方Tailwindは週に約4500万ダウンロードへと急上昇しています（<a href="https://npmtrends.com/styled-components-vs-tailwindcss">ソース（別タブで開きます）</a>）。</p>
<p>TailwindがReactアプリケーションのスタイリングにおける事実上の標準となったことに疑いの余地はありません。</p>
<p>しかし、styled-componentsを使用しており、Tailwindに切り替えるつもりが全くない開発者も依然として<em>数多く</em>存在します。そして、この立場にいる人々にとって、実行可能な代替手段があることは<em>本当に</em>大きな安堵です。 ✨</p>
<h3>私がやっていること</h3>
<p>私は2つの主要な本番アプリケーションを保守しています。1つはこのブログ、もう1つはインタラクティブなコース（<a href="https://css-for-js.dev/">CSS for JavaScript Developers（別タブで開きます）</a> および <a href="https://joyofreact.com/">The Joy of React（別タブで開きます）</a>）用のコースプラットフォームです。</p>
<p>私のコースプラットフォームは依然としてstyled-componentsと共にNext.js Pages Routerを使用しており、近い将来に移行する計画はありません。そのユーザー体験に満足しており、移行によるパフォーマンス上の大きな利点があるとは信じていません。</p>
<p>このブログ記事を最初に公開して以来、私はブログを移行しました。styled-componentsを備えたNext.js Pages Routerから、<a href="https://github.com/callstack/linaria">Linaria（別タブで開きます）</a>および<a href="https://github.com/dlehmhus/next-with-linaria">next-with-linaria（別タブで開きます）</a>を備えたNext.js App Routerへの移行です。正直に言って、それはかなり期待外れでした。 😅</p>
<p><em>理論上は</em>、RSCの採用とコンパイル時のCSS-in-JSの採用によって、ちょっとした嬉しいパフォーマンスの向上を見込めたはずでした。しかし実際には、パフォーマンスはわずかに悪化しました。その理由は正確には分かりませんが、1つの大きな理由は、Next.js App RouterがCSS Modulesを異なる方法で処理し、大量のスタイルが先回りして読み込まれてしまうことにあるようです。</p>
<p>これは後続の内部リンクのクリックを高速化する一方で、最初のリクエストを遅くします。このブログのようなプロジェクトにとっては理にかなったトレードオフでは<em>ありません</em>。</p>
<p>React Server Componentsは<em>超クール</em>です。React/Vercelチームは、サーバー上でのReactの動作方法を見直すという素晴らしい仕事をしました。しかし正直なところ、自分自身でこれらの移行の1つに着手してみて、ほとんどの本番アプリケーションにこれを推奨できるかどうか確信が持てません。</p>
<p>全体として、開発者体験とユーザー体験の両方において、Pages + styled-componentsの方が優れていたと言えるでしょう。</p>
<p>もしあなたがアプリケーションのパフォーマンスに満足しているなら、アップデートや移行を急ぐ必要はないでしょう ❤️。エコシステムが成熟し続け、新しい選択肢が現れることを私は引き続き期待しています。しかしそれまでの間、隣の芝生は青く見えるものですが、無理に乗り換える必要はありません。</p>
<p><strong>The Joy of React</strong></p>
<p>私はこれまで10年近くReactを使用してきましたが、動的なウェブアプリケーションを構築する上で、本当に私のお気に入りの方法であり続けています。数年をかけて、Reactについて知っているすべてを<a href="https://joyofreact.com/">The Joy of React（別タブで開きます）</a>というインタラクティブな自分のペースで学べるコースにまとめました。</p>
<p>このコースでは、Reactがどのように機能するかのメンタルモデルを構築し、私がReactでこれほど生産的になるのに役立った「幸せな実践（happy practices）」を共有しています。React Server ComponentsやNext.js App Routerについても、SuspenseやStreaming SSRなどの他のモダンな機能と並んで深くカバーしています。</p>
<p>詳細はこちらで学べます：</p>
<ul>
<li><a href="https://joyofreact.com/">The Joy of React（別タブで開きます）</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[Web上の巨大HTMLドキュメントを探る]]></title><description><![CDATA[ほとんどのHTMLドキュメントは比較的小さく、ページ上の他のリソースを読み込むための起点として機能しています。 しかし、なぜ一部のWebサイトでは数メガバイトものHTMLコードが読み込まれるのでしょ…]]></description><link>https://postd.cc/exploring-large-html-documents-on-the-web/</link><guid isPermaLink="false">https://postd.cc/exploring-large-html-documents-on-the-web/</guid><category><![CDATA[プログラミング]]></category><category><![CDATA[パフォーマンス]]></category><pubDate>Fri, 27 Feb 2026 00:00:01 GMT</pubDate><content:encoded><![CDATA[<p>ほとんどのHTMLドキュメントは比較的小さく、ページ上の他のリソースを読み込むための起点として機能しています。</p>
<p>しかし、なぜ一部のWebサイトでは数メガバイトものHTMLコードが読み込まれるのでしょうか？通常、ページ上に大量のコンテンツがあるわけではなく、他の種類のリソースがドキュメント内に埋め込まれていることが原因です。</p>
<p>この記事では、Web上に存在する巨大なHTMLドキュメントの実例を取り上げ、コードの中身を覗いて何がそれほど大きくしているのかを探っていきます。</p>
<p>Web上のHTMLは驚きの連続です。この記事を執筆する過程で、DebugBearの<a href="https://www.debugbear.com/html-size-analyzer">HTMLサイズアナライザー</a>のほとんどを作り直しました。スクリプトの中にJSON、その中にHTML、さらにCSSや画像——HTMLにそんな深い入れ子構造があっても、今なら対応しています！</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i1.png" alt="HTML Size Analyzer result showing overall size, size by tag attribute, and specific attribute examples"></p>
<h2>埋め込み画像</h2>
<p>Base64エンコーディングは、画像をテキストに変換する方法で、HTMLやCSSなどのテキストファイルに埋め込むことができます。画像を直接HTMLに埋め込むことには大きなメリットがあります。画像を表示するためにブラウザが別途リクエストを行う必要がなくなるのです。</p>
<p>しかし、大きなファイルの場合は問題を引き起こす可能性があります。たとえば、画像を個別にキャッシュできなくなり、また画像がドキュメントコンテンツと同じ優先度で取得されてしまいます——通常、画像は後から読み込まれても問題ないのですが。</p>
<p>以下は、データURLを使用してHTMLに埋め込まれたPNGファイルの例です。</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i2.png" alt="HTML code with two embedded images that are 1.53 and 1.15 MB large"></p>
<p>このパターンにはさまざまなバリエーションがあります：</p>
<ul>
<li>誤って含まれた数メガバイトの単一画像である場合もあれば、時間の経過とともに蓄積された数百個の小さなアイコンの場合もある</li>
<li>レスポンシブイメージとデータURLを組み合わせて使用しているサイトもあった。レスポンシブイメージの目的の1つは必要最小限の解像度の画像だけを読み込むことだが、すべてのバージョンをHTMLに埋め込むと逆効果になる</li>
<li>間接的に埋め込まれた画像：
<ul>
<li>PNGやJPEGの単なるラッパーにすぎないインラインSVG</li>
<li>インライン化されたCSSスタイルシートに含まれる背景画像</li>
<li>JSONデータ内の画像（これについては後述 😬）</li>
</ul>
</li>
</ul>
<p>以下は、背景画像が埋め込まれた201のルールを含むstyleタグの例です。</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i3.png" alt="Inline style with many WebP images under 10 kilobytes"></p>
<h2>インラインCSS</h2>
<p>大きなインラインCSSの原因は通常、画像です。ただし、深くネストされたCSSによる長いセレクタも、CSSおよびHTMLのサイズ増大に寄与します。</p>
<p>以下の例では、HTMLに類似した内容の20個のインラインstyleタグが含まれています（"header"、"header-mobile"、"header-desktop" などのバリエーション）。ほとんどのセレクタは200文字以上あり、その結果、スタイルシート全体の内容の47%がスタイル宣言ではなくセレクタで占められています。</p>
<p>ただし、セレクタ内の繰り返しによりHTMLの圧縮効率は良好で、GZIP圧縮後は20.5メガバイトからわずか2.3メガバイトにまで縮小されます。</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i4.png" alt="Inline style with many long CSS selectors, showing a 447-byte selector"></p>
<h2>埋め込みフォント</h2>
<p>画像と同様に、フォントもBase64でエンコードされることがあります。1つか2つの小さなフォントであれば、テキストを適切なフォントですぐにレンダリングできるため、実際にうまく機能します。</p>
<p>しかし、多くのフォントが埋め込まれている場合、これらのフォントのダウンロードが完了するまでページコンテンツのレンダリングを待たなければならないことを意味します。</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i5.png" alt="Embedded fonts between 29 and 44 kilobytes"></p>
<h2>クライアントサイドのアプリケーション状態</h2>
<p>現代の多くのWebサイトは、JavaScriptアプリケーションとして構築されています。すべてのJavaScriptと必要なデータの読み込みが完了してからコンテンツを表示するのでは遅いため、初回のページ読み込み時にはHTMLもサーバーサイドでレンダリングされます。</p>
<p>クライアントサイドのアプリケーションコードが読み込まれると、静的なHTMLは「ハイドレーション」されます。ページコンテンツがJavaScriptでインタラクティブになり、クライアントサイドのコードがそれ以降のコンテンツ更新を制御するようになります。</p>
<p>通常、クライアントサイドのコードはバックエンドのAPIエンドポイントにfetchリクエストを送信して必要なデータを取得します。しかし、初回のクライアントサイドレンダリングにはサーバーサイドレンダリングと同じデータが必要なため、サーバーはハイドレーション用の状態を最終的なHTMLに埋め込みます。これにより、クライアントサイドのハイドレーションは追加のAPIリクエストを行うことなく、すべてのJavaScriptの読み込み完了直後に実行できます。</p>
<p>ご想像のとおり、このハイドレーション用の状態は巨大になり得ます！ 以下のようなフレームワーク固有のキーワードを参照するscriptタグから識別できます：</p>
<ul>
<li>Next.js: <code class="language-text">self.__next_f.push</code> または <code class="language-text">__NEXT_DATA__</code></li>
<li>Nuxt: <code class="language-text">__NUXT_DATA__</code></li>
<li>Redux: <code class="language-text">__PRELOADED_STATE__</code></li>
<li>Apollo: <code class="language-text">__APOLLO_STATE__</code></li>
<li>Angular: <code class="language-text">ng-state</code> など</li>
<li>多くの独自構成: <code class="language-text">__INITIAL_STATE__</code> または <code class="language-text">__INITIAL_DATA__</code></li>
</ul>
<p>ローカルの開発環境ではデータが少ないため、ハイドレーション用の状態のサイズは気にならないかもしれません。しかし、本番のデータベースにデータが追加されるにつれて、ハイドレーション用の状態も増大します。たとえば、あるホテル一覧では3,561枚もの異なる画像が参照されています（さいわい、Base64で埋め込まれてはいませんが 😅）。</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i6.png" alt="Breakdown of 7.9 megabytes of JSON data"></p>
<p>Base64画像をフロントエンドコンポーネントに渡すと、それらはハイドレーション用の状態にも含まれることになります。</p>
<p>あるWebサイトでは、HTMLドキュメント内のJSONデータの中に42枚の画像が埋め込まれていました。最大の画像サイズは2.5メガバイトでした。</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i7.png" alt="HTML Size Analyzer showing inline images within hydration state"></p>
<p>驚くほど深いネストが発生しています。先ほどの例では、HTML内のscript内のJSON内に画像がありました。</p>
<p>しかし、さらに深く潜ることができます！ 次の例を見てみましょう：</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i8.png" alt="15 megabyte uncompressed HTML document"></p>
<p>ハイドレーション用の状態を掘り下げると、<code class="language-text">judgmeWidget</code> プロパティを持つ52個の商品が見つかります。このプロパティの値自体がHTMLフラグメントなのです！</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i9.png" alt="Hydration state with 1 megabyte of judgmeWidet data"></p>
<p>その値の1つをHTMLサイズアナライザーに入れてみましょう。ここでも、HTMLの大部分は実際には埋め込まれたJSONコードであり、今回はdivのdata-json属性の形式になっています！</p>
<p>そして、そのJSON内で最も大きなプロパティの名前は？ <code class="language-text">body_html</code> です 😂😂😂</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i10.png" alt="HTML with JSON and 1.3 kilobyte body_html value"></p>
<h2>その他の巨大HTMLの原因</h2>
<p>調査中に見つけたその他の例をいくつか紹介します：</p>
<ul>
<li>4メガバイトのインラインスクリプト</li>
<li>Figmaからの予期しないメタデータ</li>
<li>7,000以上の項目と1,300のインラインSVGを含むメガメニュー</li>
<li>180種類のサイズに対応したレスポンシブイメージ</li>
</ul>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i11.png" alt="Figma data-buffer values on a web page"></p>
<p>GZIPやBrotli圧縮をHTMLに適用していない大規模なWebサイトもまだ存在します。そのため、コード量自体はそれほど多くなくても、転送サイズが大きくなってしまいます。</p>
<p>53キロバイトの <code class="language-text">NREUM</code> スクリプトを見かけると、いつも残念に思います。多くのWebサイトが New Relic のエンドユーザー監視スクリプトをドキュメントの <code class="language-text">&lt;head></code> に直接埋め込んでいます。ユーザー体験を計測するのであれば、そのパフォーマンスへの影響は避けたいところです！</p>
<h2>HTMLサイズはページ速度にどう影響するか？</h2>
<p>HTMLコードは、ページ読み込み処理の一部としてダウンロードおよびパースされる必要があります。これに時間がかかるほど、訪問者がコンテンツの表示を待つ時間が長くなります。</p>
<p>ブラウザはHTMLコンテンツに高い優先度を割り当て、そのすべてが重要なページコンテンツであると想定します。これにより、重要でないハイドレーション用の状態が、レンダリングをブロックするスタイルシートやJavaScriptファイルよりも先にダウンロードされてしまうことがあります。</p>
<p>DebugBearのWebサイト速度テストによるリクエストウォーターフォールの例を見るとわかります。ブラウザは他のファイルの存在を早い段階で把握しているにもかかわらず、すべての帯域幅がドキュメントに消費されています。</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i12.png" alt="Request waterfall showing that CSS requests are sent early by the browser but the server only sends response data once the HTML download is complete"></p>
<p>画像やフォントをHTMLに埋め込むことは、それらのファイルをキャッシュしてページ間で再利用できなくなることも意味します。代わりに、Webサイト上のすべてのページ読み込みで再ダウンロードする必要があります。</p>
<p>HTMLのパースにかかる時間も懸念されるのでしょうか？ 私のMacBookでは、1メガバイトのHTMLコードをパースするのに約6ミリ秒かかります。一方、テストに使用しているローエンドのスマートフォンでは、1メガバイトあたり約80ミリ秒かかります。したがって、非常に大きなドキュメントの場合、CPU処理も考慮すべき要因になり始めます。</p>
<h2>巨大なHTMLでもWebサイトは高速であり得る</h2>
<p>おわかりのとおり、私はHTMLサイズに少しこだわりすぎているかもしれません。実際のところ、多くの実際の訪問者にとって、本当に問題なのでしょうか？</p>
<p>巨大なHTMLファイルを実際以上に大きな問題であるかのように見せたくはありません。今日あなたのWebサイトを訪れるほとんどの訪問者は、おそらく十分に高速な回線とデバイスを持っています。他のWebパフォーマンスの問題のほうが、通常はより差し迫った課題です。（たとえば、ハイドレーション用の状態を使用するJavaScriptアプリケーションコードの実行など）</p>
<p>また、ページはHTMLドキュメント全体のダウンロードが完了する前にレンダリングを開始できます。ここでは、ドキュメントと重要なスタイルシートが並行して読み込まれていることがわかります。その結果、メインコンテンツはドキュメントの読み込みが完了する前にレンダリングされます。</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i13.png" alt="Website waterfall with long HTML download, but other files are received in-between."></p>
<p>GoogleのChrome User Experience Report（CrUX）の実ユーザーデータによると、このWebサイトでは通常2秒以内にレンダリングされています。しかもモバイルデバイスでの結果です！</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i14.png" alt="CrUX data showing a 1.97 second load time"></p>
<p>とはいえ、巨大なドキュメントは確実にページを遅くしています。その指標の1つは、Largest Contentful Paint（LCP）の画像が読み込み直後に表示されないことです。CrUXでは584ミリ秒のレンダリング遅延が報告されています。</p>
<p>これは、メインのWebサイトサーバー上のレンダリングブロッキングスタイルシートが大きなHTMLドキュメントと帯域幅を奪い合っていることを示しています。その結果、別のサーバーから配信される画像よりも読み込みが遅くなっているのです。</p>
<p><img src="https://calendar.perfplanet.com/images/2025/matt/i15.png" alt="CrUX data with a TTFB of 702 milliseconds, 325 milliseconds of load delay, 158 milliseconds of load duration, and 584 milliseconds of render delay"></p>
<p>自分のWebサイトのHTMLをざっと確認し、実際に何が含まれているかをチェックすることは価値があります。多くの場合、すぐにできるインパクトの大きな修正が見つかります。</p>
<p>画像がHTMLやCSSコードにインライン化されている場合、それはパフォーマンス最適化を意図していることが多いです。しかし、良い仕組みがあると、後から画像を追加するのが容易になりすぎて、埋め込まれるファイルの中身を確認しないまま増え続けてしまうことがあります。CIビルドにガードレールを設けて、意図しないファイルサイズの急増を検知することを検討してください。</p>]]></content:encoded></item><item><title><![CDATA[ブラウザを再び好きにさせてくれる、5つの奇妙なWeb API]]></title><description><![CDATA[2025年に入って3度目の自分のサイト再構築をしていた時（どうか突っ込まないでください）、私はあることを思い出しました。それは、「ブラウザは実は魔法のような存在だ」ということです。 私たちが日々扱う…]]></description><link>https://postd.cc/5-weird-web-apis/</link><guid isPermaLink="false">https://postd.cc/5-weird-web-apis/</guid><category><![CDATA[開発手法・プロジェクト管理]]></category><category><![CDATA[WebAPI]]></category><category><![CDATA[ブラウザ]]></category><pubDate>Tue, 27 Jan 2026 00:00:01 GMT</pubDate><content:encoded><![CDATA[<p>2025年に入って3度目の自分のサイト再構築をしていた時（どうか突っ込まないでください）、私はあることを思い出しました。それは、「ブラウザは実は魔法のような存在だ」ということです。</p>
<p>私たちが日々扱う終わりなきフォームバリデーションやエラー状態、APIコールの裏側で、Webにはまだ「奇妙な小さな機能」がたくさん詰まっています。それはまるで、昔のゲームで隠しステージを見つけた時のような、思わずニヤリとしてしまうようなものです。</p>
<p>私たちは普段、<code class="language-text">fetch()</code> や <code class="language-text">addEventListener()</code> といったお決まりのツールキットにこだわりがちです。しかしブラウザAPIにはエンジニアリングというより「イースターエッグ（隠し要素）」に近い、忘れ去られた層が存在します。CRUDアプリというより、「えっ、こんなところで何してるの？！」という感覚に近いものです。</p>
<p>正直なところ、B2Bダッシュボードの開発でスプリント112あたりを回しているような状況なら、あなたには少しの楽しみが必要でしょう。これらのAPIは、ただ奇妙なものを作るためだけのものではありません。Web開発の楽しさや遊び心、そして純粋な有用さを再発見させてくれるものです。</p>
<p>プログラムでスマホを振動させるのは、確かに面白い。でも、その同じAPIを使えば、モバイルでのエラーハンドリングをよりアクセシブル（親切）にできます。</p>
<p>MIDIキーボードでタイピングをするのは、単なる宴会芸に過ぎないでしょう。でもそれは、代替ハードウェアを必要とする人々にとって正当な入力方法にもなり得るのです。</p>
<p>一見すると常軌を逸しているように見えるこれらの機能も、それぞれが「心地よいインターフェース」「予想外のインタラクション」、そして「思慮深いアクセシビリティの向上」への扉を開いてくれます。</p>
<p>それでは、驚くほど奇妙で素晴らしい5つのブラウザAPIを紹介しましょう。これらは遊び心にあふれ、便利で、Reactコンポーネントのデバッグよりもきっと楽しいはずです。</p>
<h2>1. Battery Status API：世話焼き（だけど少し不気味）な友人</h2>
<ul>
<li><strong>実用的なユースケース：</strong> 電力消費を考慮したUXの設計</li>
<li><strong>楽しいユースケース：</strong> バッテリー低下時のディスコパーティー</li>
</ul>
<p>ノートPCのバッテリーが10%を切った時、私のWebサイトはディスコに変わり、「充電しろ！」と叫び始めます。どうやら私のコンピュータは、私の人生の選択に意見したいようです。</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript">navigator<span class="token punctuation">.</span><span class="token function">getBattery</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">battery</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">function</span> <span class="token function">checkBattery</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>battery<span class="token punctuation">.</span>level <span class="token operator">&lt;</span> <span class="token number">0.1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>style<span class="token punctuation">.</span>animation <span class="token operator">=</span> <span class="token string">'disco 0.5s infinite'</span><span class="token punctuation">;</span>
      document<span class="token punctuation">.</span>body<span class="token punctuation">.</span><span class="token function">insertAdjacentHTML</span><span class="token punctuation">(</span><span class="token string">'beforeend'</span><span class="token punctuation">,</span> <span class="token string">'&lt;div style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 3rem; z-index: 9999;">🔋 GO CHARGE YOURSELF! 🔋&lt;/div>'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
  battery<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'levelchange'</span><span class="token punctuation">,</span> checkBattery<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h3>しかし、落とし穴も…</h3>
<p>注意点として、Battery Status APIはプライバシー上の懸念から、ほとんどのブラウザで非推奨となっています。Firefoxでは2016年に削除され、現在は特定のバージョンのChrome系ブラウザでしかサポートされていません。しかし、イースターエッグや実験的な機能としては、今でも完璧（かつ時として便利）です。</p>
<h3>思慮深いユースケース：電力を考慮したUX</h3>
<p>あなたのサイトが訪問者のバッテリー残量を気遣うことができます。残量が少ない？ それなら自動的に軽量テーマに切り替えたり、アニメーションをオフにしたり、PCが切れる前に保存を促したりしましょう。会計ソフトやCMS、ERPといった、重要な作業を伴うサイトには最適です。</p>
<h3>その他の試せること</h3>
<ul>
<li>バッテリー低下時にビデオの画質を下げる</li>
<li>残量20%以下で重いアニメーションを無効化する</li>
<li>「作業を保存してください」という警告を表示する</li>
</ul>
<h2>2. Vibration API：コードで語るスマホシェイカー</h2>
<ul>
<li><strong>実用的なユースケース：</strong> エラーやメッセージの触覚フィードバック</li>
<li><strong>楽しいユースケース：</strong> リズムゲームやビートに同期した振動</li>
</ul>
<p>振動パターンでメッセージを送ってみましょう。準備はいいですか？モールス信号で「HELLO」を送ります。</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// モールス信号 "HELLO": .... . .-.. .-.. ---</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token string">"vibrate"</span> <span class="token keyword">in</span> navigator<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  navigator<span class="token punctuation">.</span><span class="token function">vibrate</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
    <span class="token number">100</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token comment">// H: ....</span>
    <span class="token number">300</span><span class="token punctuation">,</span> <span class="token comment">// pause</span>
    <span class="token number">100</span><span class="token punctuation">,</span> <span class="token comment">// E: .</span>
    <span class="token number">300</span><span class="token punctuation">,</span> <span class="token comment">// pause</span>
    <span class="token number">100</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">300</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token comment">// L: .-..</span>
    <span class="token number">300</span><span class="token punctuation">,</span> <span class="token comment">// pause</span>
    <span class="token number">100</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">300</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token comment">// L: .-..</span>
    <span class="token number">300</span><span class="token punctuation">,</span> <span class="token comment">// pause</span>
    <span class="token number">300</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">300</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">300</span> <span class="token comment">// O: ---</span>
  <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>あなたのスマートフォンが電信機になりました！</p>
<h3>Vibration APIの何が良いのか？</h3>
<p>Vibration APIを使えば、Webサイトから文字通りユーザーのスマホを揺らすことができます。現代のモバイルブラウザでサポートされています（iOS Safariは残念ながら非対応）。</p>
<h3>正当な使い道（本当に）</h3>
<p>Webベースのゲームやアプリにおいて、触覚フィードバックは臨場感を高めます。キャラがダメージを受けたら「ブルッ」。タイマーが切れたら「微振動」。フォームの入力エラーなら「クイックな振動」で注意を引く。クリエイティブな活用例には以下があります：</p>
<ul>
<li>ビートに同期した振動を伴うリズムゲーム</li>
<li>視覚障害者のためのアクセシビリティ機能</li>
<li>呼吸パターンに合わせた振動を出す瞑想アプリ</li>
</ul>
<h2>3. Web Speech API：ブラウザの内なる独り言</h2>
<ul>
<li><strong>実用的なユースケース：</strong> インクルーシブデザイン、スクリーンリーダーの強化、音声操作フォーム</li>
<li><strong>楽しいユースケース：</strong> 喋るフォーチュンクッキー、高音のロボットボイス</li>
</ul>
<p>私は、プログラミングの格言をバカバカしいほど高音のロボットボイスで届ける「喋るフォーチュンクッキー」サイトを作りました。ブラウザをモチベーショナル・スピーカーにしたかったからです。</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">const</span> fortunes <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token string">"You will debug for exactly three hours and discover the bug was a missing semicolon."</span><span class="token punctuation">,</span>
  <span class="token string">"A wild merge conflict will appear in your near future."</span><span class="token punctuation">,</span>
  <span class="token string">"Your code will work perfectly in staging but break spectacularly in production."</span><span class="token punctuation">,</span>
  <span class="token string">"You are the chosen one. But only until the next deploy."</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">speakFortune</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> fortune <span class="token operator">=</span> fortunes<span class="token punctuation">[</span>Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> fortunes<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> utterance <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SpeechSynthesisUtterance</span><span class="token punctuation">(</span>fortune<span class="token punctuation">)</span><span class="token punctuation">;</span>
  utterance<span class="token punctuation">.</span>pitch <span class="token operator">=</span> <span class="token number">1.5</span><span class="token punctuation">;</span> <span class="token comment">// 少しおどけた感じにする</span>
  utterance<span class="token punctuation">.</span>rate <span class="token operator">=</span> <span class="token number">0.9</span><span class="token punctuation">;</span>
  speechSynthesis<span class="token punctuation">.</span><span class="token function">speak</span><span class="token punctuation">(</span>utterance<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>ブラウザがロボットボイスでプログラミングの知恵を授けてくれるのは、どこか魔法のような感覚があります。</p>
<h3>Web Speech APIは実は強力</h3>
<p>このAPIは2つの魔法から成ります。「音声合成（Speech Synthesis）」がブラウザに喋らせ、「音声認識（Speech Recognition）」がブラウザに聞き取らせます。音声合成については、Chrome、Firefox、そしてSafariでも十分にサポートされています。</p>
<iframe src="https://giphy.com/embed/mIZ9rPeMKefm0" width="344" height="480" frameBorder="0" class="giphy-embed" allowFullScreen></iframe><p><a href="https://giphy.com/gifs/dancing-happy-mIZ9rPeMKefm0">via GIPHY</a></p>
<h3>真のアクセシビリティ向上</h3>
<p>このAPIはアクセシビリティにおいて驚異的です。ダッシュボードの更新内容を自動で読み上げたり、複雑なUI要素に音声説明を加えたり、音声制御のナビゲーションを作成したり。これこそが最高のインクルーシブデザインです。</p>
<h3>実用的なユースケース</h3>
<ul>
<li>スクリーンリーダーの機能強化</li>
<li>音声制御フォーム</li>
<li>視覚障害者向けのオーディオフィードバック</li>
<li>発音確認ができる言語学習アプリ</li>
</ul>
<h2>4. WebHID API：ハンドルがスクロールバーになる時</h2>
<ul>
<li><strong>実用的なユースケース：</strong> アクセシブルなWeb UIの設計</li>
<li><strong>楽しいユースケース：</strong> スクロールをマリオカート風に</li>
</ul>
<p>Webの入力といえばキーボードやマウスのことだと思っていた時代を覚えていますか？ 新たなレベルへようこそ。それがWebHIDです。</p>
<p>私は古いレーシングホイール（ハンドル型コントローラー）をUSBでブラウザに繋ぎ、ハンドルを左右に切ることでブログ記事をスクロールできるようにしました。だってできるんですから！</p>
<p>人間工学的だったか？ 全く違います。
客観的に見て最高だったか？ もちろんです。</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">const</span> devices <span class="token operator">=</span> <span class="token keyword">await</span> navigator<span class="token punctuation">.</span>hid<span class="token punctuation">.</span><span class="token function">requestDevice</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">filters</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token literal-property property">vendorId</span><span class="token operator">:</span> <span class="token number">0x046d</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>devices<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">await</span> devices<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  devices<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'inputreport'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token comment">// Assume axis 0 is steering</span>
    <span class="token keyword">const</span> steer <span class="token operator">=</span> e<span class="token punctuation">.</span>data<span class="token punctuation">.</span><span class="token function">getInt8</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    window<span class="token punctuation">.</span><span class="token function">scrollBy</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> steer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Steer = scroll!</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>突然、LogRocketの記事を読むのがマリオカートをプレイしているような気分になりました。生産性には危険ですが、金曜午後の実験としては最高です。</p>
<h3>そもそもWebHIDとは？</h3>
<p>WebHID APIを使えば、WebサイトがHID（Human Interface Device）と直接通信できます。ゲームパッド、MIDIドラム、バーコードスキャナー、あるいは引き出しの奥に眠っている謎のUSBハードウェアまで。現在はChrome 89+やEdge 89+でサポートされていますが、FirefoxやSafariでは未対応です。</p>
<h3>本当に役に立つの？</h3>
<p>もちろんです。STEMフェアの学生たちと協力したり、アクセシブルなWeb UIを設計したりする場面を想像してください。誰かがカスタムジョイスティックや特殊なコントローラーを持っていれば、それをネイティブに動かすことができます。ダウンロードや仲介ソフトは不要、ブラウザから「繋ぐだけ」です。WASMのようですが、良いものです。</p>
<h3>他のぶっ飛んだアイデア</h3>
<ul>
<li>PlayStationコントローラーの入力で動くSVG blob</li>
<li>MIDIドラムパッドで発動する紙吹雪</li>
<li>バーコードスキャナーでイースターエッグを解除</li>
</ul>
<h2>5. WebMIDI API：キーストロークを音符に変える</h2>
<ul>
<li><strong>実用的なユースケース：</strong> インタラクティブなサウンドデザイン</li>
<li><strong>楽しいユースケース：</strong> リックロール（釣り動画）の絶好のチャンス</li>
</ul>
<p>埃を被ったMIDIキーボードを引っ張り出し、鍵盤を叩くたびに画面上のあちこちにランダムな絵文字が現れるようにしました。我が家ではこれが「生産性」と呼ばれています。</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript">navigator<span class="token punctuation">.</span><span class="token function">requestMIDIAccess</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">access</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> input <span class="token keyword">of</span> access<span class="token punctuation">.</span>inputs<span class="token punctuation">.</span><span class="token function">values</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    input<span class="token punctuation">.</span><span class="token function-variable function">onmidimessage</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">msg</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>msg<span class="token punctuation">.</span>data<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token number">144</span> <span class="token operator">&amp;&amp;</span> msg<span class="token punctuation">.</span>data<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Note-on message</span>
        <span class="token keyword">const</span> emoji <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'🎵'</span><span class="token punctuation">,</span> <span class="token string">'🎶'</span><span class="token punctuation">,</span> <span class="token string">'🎸'</span><span class="token punctuation">,</span> <span class="token string">'🥁'</span><span class="token punctuation">,</span> <span class="token string">'🎹'</span><span class="token punctuation">,</span> <span class="token string">'🎺'</span><span class="token punctuation">,</span> <span class="token string">'🎷'</span><span class="token punctuation">]</span><span class="token punctuation">[</span>Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
        document<span class="token punctuation">.</span>body<span class="token punctuation">.</span><span class="token function">insertAdjacentHTML</span><span class="token punctuation">(</span><span class="token string">'beforeend'</span><span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;span style="font-size: 2rem; position: absolute; left: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">100</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">%; top: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">100</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">%;"></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>emoji<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&lt;/span></span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>演奏するたびにWebサイトが視覚的なシンフォニーへと変わります。馬鹿げているけれど楽しく、驚くほど引き込まれます（そしてリックロールの絶好の機会でもある）。</p>
<iframe src="https://giphy.com/embed/Ju7l5y9osyymQ" width="480" height="360" frameBorder="0" class="giphy-embed" allowFullScreen></iframe><p><a href="https://giphy.com/gifs/rick-astley-Ju7l5y9osyymQ">via GIPHY</a></p>
<h3>真面目な側面：共同音楽制作</h3>
<p>WebMIDI APIはブラウザを、MIDIメッセージの送受信が可能な楽器へと変貌させます。大陸を超えてミュージシャンがセッションできるWebアプリを想像してください。ベルリンの誰かが弾いた音でモントリオールのシンセを鳴らし、東京の照明を制御し、あなたのブラウザでビジュアライザーを作る。何もインストールせず、ブラウザだけでリアルタイムの音楽コラボレーションが可能になります。</p>
<h3>他にも試せるぶっ飛んだ活用法</h3>
<ul>
<li>ブラウザベースのDAW（デジタル・オーディオ・ワークステーション）やシーケンサー</li>
<li>音楽理論を学べる知育アプリ</li>
<li>インタラクティブなサウンドデザイン・ツール</li>
</ul>
<h2>ボーナスラウンド：さらなるWebの魔法</h2>
<p>もっとブラウザの奇妙さを知りたいなら：</p>
<ul>
<li><strong>Ambient Light API</strong>：部屋の照明に合わせてサイトのテーマを変化させる（動けばラッキー）</li>
<li><strong>Device Motion API</strong>：スマホの傾きに反応するデジタル溶岩ランプを作る</li>
<li><strong>Clipboard API</strong>：コンテンツを自動でフォーマットする「スマート・ペースト」を作成</li>
</ul>
<p>特にAmbient Light APIは面白いです。照度（ルクス）を検知してUIを調整できるので、Kindleのような読書体験やスマートホームのインターフェースに最適です。</p>
<h2>なぜ「奇妙なもの」にこだわるのか？</h2>
<p>実のところ、これらのAPIのほとんどは、次のSaaSダッシュボードに革命を起こすようなものではありません。しかし、もっと重要なことを教えてくれます。それは、「Webは今でも実験と喜び、そして嬉しい驚きに満ちた場所である」ということです。</p>
<p>私がWebサイトを作り始めた頃、すべてが可能だと感じられました。これらのAPIはその感覚を呼び戻してくれます。以下のような場面に最適です：</p>
<ul>
<li><strong>学習</strong>：ブラウザの内部的な仕組みを理解する</li>
<li><strong>プロトタイピング</strong>：複雑な設定なしに突飛なアイデアを試す</li>
<li><strong>アクセシビリティ</strong>：より包括的な体験を創造する</li>
<li><strong>喜び</strong>：Webをもう少しだけ魔法のような場所に変える</li>
</ul>
<p>これらのAPIは、スキルを磨き、型破りなアイデアを形にし、誰にとっても楽しい体験を構築するための完璧な口実になります。平凡な枠を超えて、遊び心を再発見しましょう。</p>
<p>次にフォーム作成やAPIコールの繰り返しに行き詰まったら、これらのAPIを1つ手に取って、「素晴らしいほどに無駄なもの」を作ってみてください。Webの奇妙さを保ってくれたことに、未来の自分が感謝するでしょう。</p>
<p>※訳者註：本翻訳記事は執筆者の意図を尊重するため、原文掲載内容をそのまま掲載しております。また、記載されている会社名、製品名は、各社の商標または登録商標です。</p>]]></content:encoded></item><item><title><![CDATA[React Server Componentsの本番運用上の課題について]]></title><description><![CDATA[数週間前、私たちの本番アプリがハングし始めました。コンポーネントがランダムに読み込まれなくなったのです。ユーザーの画面ローディングスピナーの前で固まってしまいました。40時間デバッグした末に、私たち…]]></description><link>https://postd.cc/react-server-components-are-breaking-production-apps-and-nobodys-talking-about-it/</link><guid isPermaLink="false">https://postd.cc/react-server-components-are-breaking-production-apps-and-nobodys-talking-about-it/</guid><category><![CDATA[開発手法・プロジェクト管理]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[TypeScript]]></category><pubDate>Thu, 27 Nov 2025 00:00:01 GMT</pubDate><content:encoded><![CDATA[<p>数週間前、私たちの本番アプリがハングし始めました。コンポーネントがランダムに読み込まれなくなったのです。ユーザーの画面ローディングスピナーの前で固まってしまいました。40時間デバッグした末に、私たちは気づきました。React Server Components（RSC）が問題だったのです。</p>
<hr>
<h2>イントロダクション：理想 vs. 現実</h2>
<p>当初、React Server Components（RSC）は革命的であるはずでした。
Reactチームは以下を強調していました：</p>
<ul>
<li>✅ パフォーマンスの向上 </li>
<li>✅ バンドルサイズの削減</li>
<li>✅ 自動的なコード分割 </li>
<li>✅ コンポーネントからのダイレクトなデータベースアクセス</li>
</ul>
<p>私たちは彼らを信頼し、Next.jsアプリ全体をServer Componentsと共にApp Routerへ移行しました。</p>
<h3>3カ月後、私たちのアプリは以下の状況に陥りました：</h3>
<ul>
<li>初期ロードが遅い</li>
<li>デバッグがより複雑に</li>
<li>経験の浅い開発者にとって理解しにくい</li>
<li>原因不明なキャッシュ問題に悩まされている</li>
</ul>
<p>この記事は、Reactコミュニティーが必要としている率直な会話です。マーケティングでも、誇張でもありません。React Server Componentsを使った、<strong>本番環境でのリアルな体験談</strong>です。</p>
<hr>
<h2>Part 1：React Server Componentsとは何か（シンプルバージョン）</h2>
<h3>従来モデル（Client Components）</h3>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// This runs in the browser</span>
<span class="token string">'use client'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">UserProfile</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>user<span class="token punctuation">,</span> setUser<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span>

  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api/user'</span><span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>setUser<span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>user<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span>Loading<span class="token operator">...</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>

  <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>name<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>処理フロー</strong>：</p>
<ol>
<li>ブラウザーがJavaScriptをダウンロードする</li>
<li>コンポーネントがマウントされる</li>
<li><code class="language-text">useEffect</code>が発火</li>
<li>APIへのフェッチリクエスト</li>
<li>レスポンスを待機</li>
<li>stateを更新</li>
<li>再レンダリング</li>
</ol>
<p><strong>結果</strong>：ユーザーに「Loading...」が1〜2秒間表示される</p>
<h3>Server Componentモデル</h3>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// This runs on the server</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> db <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/lib/database'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">UserProfile</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> db<span class="token punctuation">.</span>user<span class="token punctuation">.</span><span class="token function">findFirst</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

  <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>name<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>処理フロー</strong>：</p>
<ol>
<li>リクエストがサーバーに到達</li>
<li>コンポーネントがサーバーで実行される</li>
<li>データベースクエリが実行される</li>
<li>データを含んだHTMLがブラウザーに送信される</li>
<li>ユーザーにコンテンツが即座に表示される</li>
</ol>
<p><strong>結果</strong>：ユーザーは即座にデータを閲覧できる（理論上は）</p>
<h3>理想</h3>
<p>Server Componentsは以下の問題の解決を目指すものでした:</p>
<ul>
<li>ローディング状態</li>
<li>クライアントサイドのデータフェッチ</li>
<li>APIルートのボイラープレート</li>
<li>巨大なJavaScriptバンドル</li>
</ul>
<p><strong>現実は、もっと複雑です</strong>。</p>
<hr>
<h2>Part 2：落とし穴</h2>
<h3>問題点 1：暗黙のウォーターフォール</h3>
<p>Server Componentsで実際に起こることを見てみましょう：</p>
<div class="gatsby-highlight" data-language="typescript"><pre style="counter-reset: linenumber NaN" class="language-typescript line-numbers"><code class="language-typescript"><span class="token comment">// app/dashboard/page.tsx</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">Dashboard</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 200ms</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>div<span class="token operator">></span>
      <span class="token operator">&lt;</span>Header user<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
      <span class="token operator">&lt;</span>Stats userId<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>id<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token punctuation">{</span><span class="token comment">/* Another server component */</span><span class="token punctuation">}</span>
      <span class="token operator">&lt;</span>RecentActivity userId<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>id<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token punctuation">{</span><span class="token comment">/* Another server component */</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token comment">// Stats component</span>
<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">Stats</span><span class="token punctuation">(</span><span class="token punctuation">{</span> userId <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> stats <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getStats</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span> <span class="token comment">// 300ms - WAITS for parent!</span>
  <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">{</span>stats<span class="token punctuation">.</span>total<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
<span class="token punctuation">}</span>

<span class="token comment">// RecentActivity component  </span>
<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">RecentActivity</span><span class="token punctuation">(</span><span class="token punctuation">{</span> userId <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> activity <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getActivity</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span> <span class="token comment">// 250ms - WAITS for Stats!</span>
  <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">{</span>activity<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token operator">...</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>期待する動作</strong>：並列リクエスト（最大300ms）</p>
<p><strong>実際の動作</strong>：シーケンシャルなウォーターフォール</p>
<ol>
<li><code class="language-text">getUser()</code> - 200ms</li>
<li>コンポーネントがレンダリングされ、<code class="language-text">&lt;Stats></code>を検出</li>
<li><code class="language-text">getStats()</code> - 300ms（ステップ1の後に開始）</li>
<li>コンポーネントがレンダリングされ、<code class="language-text">&lt;RecentActivity></code>を検出</li>
<li><code class="language-text">getActivity()</code> - 250ms（ステップ3の後に開始）</li>
</ol>
<p><strong>合計時間：750ms</strong>（並列化されていない！）</p>
<p><strong>なぜこうなるのか</strong>：Reactはコンポーネントをシーケンシャルにレンダリングします。各非同期コンポーネントが、次のコンポーネントをブロックするのです。</p>
<h3>修正</h3>
<p>手動で並列化しなければなりません：</p>
<div class="gatsby-highlight" data-language="typescript"><pre style="counter-reset: linenumber NaN" class="language-typescript line-numbers"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">Dashboard</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// Run all queries in parallel</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>user<span class="token punctuation">,</span> stats<span class="token punctuation">,</span> activity<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
    <span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token function">getStats</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token function">getActivity</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">]</span><span class="token punctuation">)</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>div<span class="token operator">></span>
      <span class="token operator">&lt;</span>Header user<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
      <span class="token operator">&lt;</span>Stats data<span class="token operator">=</span><span class="token punctuation">{</span>stats<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token punctuation">{</span><span class="token comment">/* Now a regular component */</span><span class="token punctuation">}</span>
      <span class="token operator">&lt;</span>RecentActivity data<span class="token operator">=</span><span class="token punctuation">{</span>activity<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token punctuation">{</span><span class="token comment">/* Now a regular component */</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>しかし、これでは以下の利点が失われます：</strong></p>
<ul>
<li>コンポーネントのカプセル化</li>
<li>関心の分離</li>
<li>Server Componentsの目的</li>
</ul>
<hr>
<h3>問題点 2：キャッシュというブラックボックス</h3>
<p>React 19とNext.js 14以降は、積極的なキャッシュ機構を備えています。これは良いことのように聞こえますが、本番環境で問題を起こすまでは、です。</p>
<p><strong>私たちが遭遇した実際のバグ</strong>：</p>
<div class="gatsby-highlight" data-language="typescript"><pre style="counter-reset: linenumber NaN" class="language-typescript line-numbers"><code class="language-typescript"><span class="token comment">// app/posts/page.tsx</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">PostsPage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> posts <span class="token operator">=</span> <span class="token keyword">await</span> db<span class="token punctuation">.</span>post<span class="token punctuation">.</span><span class="token function">findMany</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">return</span> <span class="token operator">&lt;</span>PostList posts<span class="token operator">=</span><span class="token punctuation">{</span>posts<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>起こったこと</strong>：</p>
<ol>
<li>ユーザーが新しい投稿を作成</li>
<li><code class="language-text">/posts</code>にリダイレクトされる</li>
<li>新しい投稿が表示されない</li>
<li>ページをリロードしても無駄</li>
<li>ブラウザーのキャッシュをクリアしても無駄</li>
</ol>
<p><strong>理由</strong>：Next.jsがサーバー上でデータベースのクエリ結果をキャッシュしていました。そして、そのキャッシュを無効化（invalidate）していなかったのです。</p>
<p><strong>解決策</strong>：</p>
<div class="gatsby-highlight" data-language="typescript"><pre style="counter-reset: linenumber NaN" class="language-typescript line-numbers"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> revalidate <span class="token operator">=</span> <span class="token number">0</span> <span class="token comment">// Disable caching</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">PostsPage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> posts <span class="token operator">=</span> <span class="token keyword">await</span> db<span class="token punctuation">.</span>post<span class="token punctuation">.</span><span class="token function">findMany</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">return</span> <span class="token operator">&lt;</span>PostList posts<span class="token operator">=</span><span class="token punctuation">{</span>posts<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>しかしながら</strong>：</p>
<ul>
<li>パフォーマンス上の利点が失われる</li>
<li>ページロードごとにデータベースにアクセスが走る</li>
<li>Client Componentsを使っていた頃のパフォーマンスに逆戻り</li>
</ul>
<p><strong>より深刻な問題</strong>：何がキャッシュされているのか確認できません。キャッシュインスペクターのようなものはありません。推測するしかないのです。</p>
<hr>
<h3>問題点 3：クライアントとサーバーの境界が分かりにくい</h3>
<p>これが私たちのチームにとって最大の問題です：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// ❌ This looks like it should work</span>
<span class="token string">'use client'</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ServerComponent <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./ServerComponent'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">ClientComponent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>count<span class="token punctuation">,</span> setCount<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>div<span class="token operator">></span>
      <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setCount</span><span class="token punctuation">(</span>count <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token operator">></span>
        <span class="token literal-property property">Count</span><span class="token operator">:</span> <span class="token punctuation">{</span>count<span class="token punctuation">}</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>
      <span class="token operator">&lt;</span>ServerComponent <span class="token operator">/</span><span class="token operator">></span> <span class="token punctuation">{</span><span class="token comment">/* Error! */</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>エラー</strong>：「Server ComponentをClient Componentにインポートしています」</p>
<p><strong>理由</strong>：ひとたび<code class="language-text">'use client'</code>を使うと、その配下は全てClient Componentでなければならないからです。</p>
<p><strong>修正</strong>：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// ✅ Pass Server Component as children</span>
<span class="token string">'use client'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">ClientComponent</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>count<span class="token punctuation">,</span> setCount<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>div<span class="token operator">></span>
      <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setCount</span><span class="token punctuation">(</span>count <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token operator">></span>
        <span class="token literal-property property">Count</span><span class="token operator">:</span> <span class="token punctuation">{</span>count<span class="token punctuation">}</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>
      <span class="token punctuation">{</span>children<span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token comment">// In parent (Server Component)</span>
<span class="token operator">&lt;</span>ClientComponent<span class="token operator">></span>
  <span class="token operator">&lt;</span>ServerComponent <span class="token operator">/</span><span class="token operator">></span>
<span class="token operator">&lt;</span><span class="token operator">/</span>ClientComponent<span class="token operator">></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>これは直感的でないように見えます。経験の浅い開発者は、この仕様に何週間も苦しめられています。</p>
<hr>
<h3>問題点 4：フォームの複雑性</h3>
<p>従来のフォームハンドリング：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token string">'use client'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Form</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">handleSubmit</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">const</span> res <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api/submit'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">'POST'</span><span class="token punctuation">,</span>
      <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>formData<span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>res<span class="token punctuation">.</span>ok<span class="token punctuation">)</span> router<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token string">'/success'</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">return</span> <span class="token operator">&lt;</span>form onSubmit<span class="token operator">=</span><span class="token punctuation">{</span>handleSubmit<span class="token punctuation">}</span><span class="token operator">></span><span class="token operator">...</span><span class="token operator">&lt;</span><span class="token operator">/</span>form<span class="token operator">></span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>シンプルに機能し、誰もが理解できます</strong>。</p>
<p>Server Actions（RSC流）：</p>
<div class="gatsby-highlight" data-language="typescript"><pre style="counter-reset: linenumber NaN" class="language-typescript line-numbers"><code class="language-typescript"><span class="token comment">// app/actions.ts</span>
<span class="token string">'use server'</span>

<span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">submitForm</span><span class="token punctuation">(</span>formData<span class="token operator">:</span> FormData<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> name <span class="token operator">=</span> formData<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'name'</span><span class="token punctuation">)</span>
  <span class="token keyword">await</span> db<span class="token punctuation">.</span>user<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> name <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token function">revalidatePath</span><span class="token punctuation">(</span><span class="token string">'/users'</span><span class="token punctuation">)</span>
  <span class="token function">redirect</span><span class="token punctuation">(</span><span class="token string">'/success'</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token comment">// Form component</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Form</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>form action<span class="token operator">=</span><span class="token punctuation">{</span>submitForm<span class="token punctuation">}</span><span class="token operator">></span>
      <span class="token operator">&lt;</span>input name<span class="token operator">=</span><span class="token string">"name"</span> <span class="token operator">/</span><span class="token operator">></span>
      <span class="token operator">&lt;</span>button type<span class="token operator">=</span><span class="token string">"submit"</span><span class="token operator">></span>Submit<span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>form<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>問題点</strong>：</p>
<ol>
<li><strong>エラーハンドリングが不明確</strong>：どこでエラーをキャッチすればよいのでしょう？</li>
<li><strong>ローディング状態</strong>：どうやってスピナーを表示するのでしょう？</li>
<li><strong>バリデーション</strong>：クライアントサイドのバリデーションにはClient Componentが必要</li>
</ol>
<p><strong>「解決」には<code class="language-text">useFormStatus</code>が必要です</strong>：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token string">'use client'</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useFormStatus <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-dom'</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> submitForm <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./actions'</span>

<span class="token keyword">function</span> <span class="token function">SubmitButton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> pending <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useFormStatus</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>button disabled<span class="token operator">=</span><span class="token punctuation">{</span>pending<span class="token punctuation">}</span><span class="token operator">></span>
      <span class="token punctuation">{</span>pending <span class="token operator">?</span> <span class="token string">'Submitting...'</span> <span class="token operator">:</span> <span class="token string">'Submit'</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Form</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>form action<span class="token operator">=</span><span class="token punctuation">{</span>submitForm<span class="token punctuation">}</span><span class="token operator">></span>
      <span class="token operator">&lt;</span>input name<span class="token operator">=</span><span class="token string">"name"</span> <span class="token operator">/</span><span class="token operator">></span>
      <span class="token operator">&lt;</span>SubmitButton <span class="token operator">/</span><span class="token operator">></span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>form<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>このために必要なもの</strong>：</p>
<ul>
<li>Server Actions用の別ファイル</li>
<li>ボタン用のClient Component</li>
<li>学習すべき新しいフック</li>
<li>増えるファイルと複雑さ</li>
</ul>
<p>以前の方法のシンプルさと比べて、どれほどのメリットがあるのでしょうか。</p>
<hr>
<h3>問題点 5：TypeScriptの型安全性が失われる</h3>
<p>Server Componentsは、TypeScriptを巧妙なやり方で壊します：</p>
<div class="gatsby-highlight" data-language="typescript"><pre style="counter-reset: linenumber NaN" class="language-typescript line-numbers"><code class="language-typescript"><span class="token comment">// lib/db.ts</span>
<span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token keyword">await</span> db<span class="token punctuation">.</span>user<span class="token punctuation">.</span><span class="token function">findFirst</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token comment">// app/page.tsx - Server Component</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">Page</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">return</span> <span class="token operator">&lt;</span>UserProfile user<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token comment">// Type error!</span>
<span class="token punctuation">}</span>

<span class="token comment">// components/UserProfile.tsx - Client Component</span>
<span class="token string">'use client'</span>
<span class="token keyword">interface</span> <span class="token class-name">Props</span> <span class="token punctuation">{</span>
  user<span class="token operator">:</span> User <span class="token comment">// Prisma type with Date objects</span>
<span class="token punctuation">}</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">UserProfile</span><span class="token punctuation">(</span><span class="token punctuation">{</span> user <span class="token punctuation">}</span><span class="token operator">:</span> Props<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>createdAt<span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token comment">// Runtime error!</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>問題点</strong>：Server ComponentsはpropsをJSONにシリアライズします。Dateオブジェクトは文字列になってしまうのです。</p>
<p><strong>TypeScriptはこれを型エラーとして検知できません</strong>。本番環境でランタイムエラーが発生します。</p>
<p><strong>修正</strong>：手動でのシリアライズ</p>
<div class="gatsby-highlight" data-language="typescript"><pre style="counter-reset: linenumber NaN" class="language-typescript line-numbers"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> db<span class="token punctuation">.</span>user<span class="token punctuation">.</span><span class="token function">findFirst</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    <span class="token operator">...</span>user<span class="token punctuation">,</span>
    createdAt<span class="token operator">:</span> user<span class="token punctuation">.</span>createdAt<span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Manual conversion</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>このために必要なこと</strong>：</p>
<ul>
<li>データベース呼び出しごとのシリアライズ関数</li>
<li>サーバー用とクライアント用で別々の型定義</li>
<li>安全のためのランタイムチェック</li>
</ul>
<hr>
<h2>Part 3：Server Componentsがうまく機能するケース</h2>
<p>ただ否定ばかりしたいわけではありません。Server Componentsがうまく機能する特定のユースケースもあります。</p>
<h3>✅ ユースケース 1：静的コンテンツサイト</h3>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// Blog post page</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">BlogPost</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> params <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> post <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getPost</span><span class="token punctuation">(</span>params<span class="token punctuation">.</span>slug<span class="token punctuation">)</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>article<span class="token operator">></span>
      <span class="token operator">&lt;</span>h1<span class="token operator">></span><span class="token punctuation">{</span>post<span class="token punctuation">.</span>title<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span>
      <span class="token operator">&lt;</span>Markdown content<span class="token operator">=</span><span class="token punctuation">{</span>post<span class="token punctuation">.</span>content<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>article<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>うまくいく理由</strong>：</p>
<ul>
<li>インタラクティビティーが不要</li>
<li>コンテンツの変更がまれ</li>
<li>キャッシュに最適</li>
<li>SEOに強い</li>
</ul>
<p><strong>結論</strong>：Server Componentsが輝ける場所です。</p>
<h3>✅ ユースケース 2：ダッシュボードのレイアウト</h3>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">DashboardLayout</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getCurrentUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>div<span class="token operator">></span>
      <span class="token operator">&lt;</span>Sidebar user<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
      <span class="token operator">&lt;</span>main<span class="token operator">></span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>main<span class="token operator">></span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>うまくいく理由</strong>：</p>
<ul>
<li>全てのページでユーザーデータが必要</li>
<li>レイアウト部分のインタラクティビティーは最小限</li>
<li>ユーザーセッションをキャッシュできる</li>
</ul>
<p><strong>結論</strong>：良いユースケースです。</p>
<h3>✅ ユースケース 3：データテーブル（フィルターなし）</h3>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">UsersTable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> users <span class="token operator">=</span> <span class="token keyword">await</span> db<span class="token punctuation">.</span>user<span class="token punctuation">.</span><span class="token function">findMany</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>table<span class="token operator">></span>
      <span class="token punctuation">{</span>users<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">user</span> <span class="token operator">=></span> <span class="token punctuation">(</span>
        <span class="token operator">&lt;</span>tr key<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>id<span class="token punctuation">}</span><span class="token operator">></span>
          <span class="token operator">&lt;</span>td<span class="token operator">></span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>name<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>td<span class="token operator">></span>
          <span class="token operator">&lt;</span>td<span class="token operator">></span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>email<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>td<span class="token operator">></span>
        <span class="token operator">&lt;</span><span class="token operator">/</span>tr<span class="token operator">></span>
      <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>table<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>うまくいく理由</strong>：</p>
<ul>
<li>表示専用のデータ</li>
<li>クライアントサイドのstateが不要</li>
<li>サーバーサイドレンダリングの方が速い</li>
</ul>
<p><strong>結論</strong>：適切なユースケースです。</p>
<hr>
<h2>Part 4：Server Componentsが失敗するケース</h2>
<h3>❌ アンチパターン 1：リアルタイム更新</h3>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// ❌ This doesn't work</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">LiveFeed</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> posts <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

  <span class="token keyword">return</span> <span class="token operator">&lt;</span>PostList posts<span class="token operator">=</span><span class="token punctuation">{</span>posts<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>問題点</strong>：更新をサブスクライブする方法がありません。WebSocketを利用するためにはClient Componentが必要です。</p>
<p><strong>必要なもの</strong>：useEffectとWebSocket接続を持つClient Component。</p>
<p><strong>Server Componentsはこういった場面では力を発揮できません</strong>。</p>
<hr>
<h3>❌ アンチパターン 2：複雑なフォーム</h3>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// ❌ This gets messy fast</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">MultiStepForm</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// How do you manage form state across steps?</span>
  <span class="token comment">// How do you validate before submission?</span>
  <span class="token comment">// How do you show field-level errors?</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>問題点</strong>：フォームはクライアントサイドのstateを必要とします。Server Actionsとクライアントのstateを混ぜるのは混乱のもとです。</p>
<p><strong>解決策</strong>：制御された入力を持つClient Componentを使う。</p>
<hr>
<h3>❌ アンチパターン 3：インタラクティブ性の高いUI</h3>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// ❌ Server Components are wrong here</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">DataGrid</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

  <span class="token comment">// Users need to:</span>
  <span class="token comment">// - Sort columns</span>
  <span class="token comment">// - Filter rows</span>
  <span class="token comment">// - Select items</span>
  <span class="token comment">// - Paginate</span>

  <span class="token keyword">return</span> <span class="token operator">&lt;</span>Table data<span class="token operator">=</span><span class="token punctuation">{</span>data<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>問題点</strong>：全てのインタラクションでサーバーとのラウンドトリップが必要になります。</p>
<p><strong>解決策</strong>：ローカルstateまたはTanstack Query（旧React Query）等を持つClient Component。</p>
<hr>
<h2>Part 5：Server Componentsの本当のコスト</h2>
<p>Server Componentsの隠れたコストについて話しましょう。</p>
<h3>コスト 1：開発者体験</h3>
<p><strong>Server Components以前</strong>：</p>
<ul>
<li>経験の浅い開発者がチームに参加</li>
<li>Reactフックを学ぶ</li>
<li>クライアントサイドのデータフェッチを理解する</li>
<li>1〜2週間で価値を発揮し始める</li>
</ul>
<p><strong>Server Components以後</strong>：</p>
<ul>
<li>経験の浅い開発者がチームに参加</li>
<li>Reactフックを学ぶ</li>
<li>Server Componentsを学ぶ</li>
<li>クライアント/サーバーの境界ルールを学ぶ</li>
<li>Server Actionsを学ぶ</li>
<li>キャッシュの挙動を学ぶ</li>
<li>どのパターンをいつ使うべきか学ぶ</li>
<li>1〜2カ月で生産的になる（運が良ければ）</li>
</ul>
<p><strong>私たちのチームの実際の統計</strong>：オンボーディング期間が2週間から6週間に増加しました。</p>
<hr>
<h3>コスト 2：デバッグの難しさ</h3>
<p><strong>Client Componentのバグ</strong>：</p>
<ol>
<li>DevToolsを開く</li>
<li>コンソールでエラーを確認</li>
<li>ブレークポイントを追加</li>
<li>コードをステップ実行</li>
<li>バグを修正</li>
</ol>
<p><strong>所要時間：10〜30分</strong></p>
<p><strong>Server Componentのバグ</strong>：</p>
<ol>
<li>エラーはターミナルに表示される（ブラウザーではない）</li>
<li>ブラウザーのDevToolsが使えない</li>
<li>console.log文を追加</li>
<li>問題を再現させる</li>
<li>ターミナルのログを確認</li>
<li>ステップ3〜6を何度も繰り返す</li>
<li>最終的にバグを発見</li>
</ol>
<p><strong>所要時間：1〜3時間</strong></p>
<hr>
<h3>コスト 3：バンドルサイズ</h3>
<p><strong>理想</strong>：Server Components利用によるバンドルサイズ削減</p>
<p><strong>現実の確認</strong>：</p>
<p><strong>Server Components以前（純粋なクライアント）</strong>：</p>
<ul>
<li>Reactバンドル：45KB</li>
<li>アプリコード：120KB</li>
<li>合計：165KB</li>
</ul>
<p><strong>Server Components以後</strong>：</p>
<ul>
<li>Reactバンドル：45KB</li>
<li>React Server Componentsランタイム：28KB（new！）</li>
<li>アプリコード（クライアント部分）：80KB</li>
<li>Server Actionボイラープレート：15KB</li>
<li>合計：168KB</li>
</ul>
<p><strong>バンドルサイズ　＋3KB（1.8%）</strong></p>
<p>しかし、待ってください、まだあります：</p>
<ul>
<li>HTMLサイズの増加（サーバーでレンダリングされたコンテンツ）</li>
<li>ネットワークリクエストの増加（Server Componentツリー）</li>
<li>RSCペイロードのオーバーヘッド</li>
</ul>
<p><strong>実際の結果</strong>：初期バンドルは減少どころかわずかに増大し、総転送データ量は増加しました。</p>
<hr>
<h3>コスト 4：パフォーマンス（という驚き）</h3>
<p>移行前後で測定しました：</p>
<p><strong>メトリクス：Time to Interactive (TTI)</strong></p>
<p><strong>Server Components以前</strong>：</p>
<ul>
<li>ホームページ：1.2秒</li>
<li>ダッシュボード：1.8秒</li>
<li>製品ページ：1.4秒</li>
</ul>
<p><strong>Server Components以後</strong>：</p>
<ul>
<li>ホームページ：1.9秒（58%悪化！）</li>
<li>ダッシュボード：2.4秒（33%悪化！）</li>
<li>製品ページ：1.1秒（21%改善）</li>
</ul>
<p><strong>なぜ遅くなったのか？</strong></p>
<ul>
<li>サーバーレンダリングに時間がかかる</li>
<li>ウォーターフォールリクエスト（問題点 1を参照）</li>
<li>APIレスポンスのクライアントサイドキャッシュがない</li>
</ul>
<p><strong>なぜ製品ページは速くなったのか？</strong></p>
<ul>
<li>シンプルでデータ中心のページ</li>
<li>インタラクティビティーがない</li>
<li>RSCの完璧なユースケース</li>
</ul>
<p><strong>学び</strong>：Server Componentsは自動的に速くなるわけではありません。</p>
<hr>
<h2>Part 6：コミュニケーションの問題</h2>
<p>私が最もフラストレーションを感じるのは、<strong>Reactチームがこれらの問題を認識していたことです</strong>。</p>
<p>私の率直な印象：</p>
<ol>
<li>ウォーターフォール問題：Reactのドキュメントに記載があるがやや伝わりづらい</li>
<li>キャッシュ問題：「より良いdevtoolsを開発中です」（2年間ずっと）</li>
<li>TypeScript問題：「これは期待される動作です」</li>
<li>デバッグの難しさ：「console.logを使ってください」（本気で？）</li>
</ol>
<p>コミュニティーは、ドキュメントからではなく、<strong>本番環境でのつらい経験</strong>を通じてこれらの問題を発見しました。</p>
<p>以下と比較してみてください：</p>
<ul>
<li>Svelte：優れたドキュメント、明確な制限事項</li>
<li>Vue：トレードオフについて正直</li>
<li>Solid：学習曲線について率直</li>
</ul>
<hr>
<h2>Part 7：では、実際どうすべきか？</h2>
<h3>戦略 1：選択的導入（推奨）</h3>
<p><strong>Server Componentsを使うケース</strong>：</p>
<ul>
<li>静的コンテンツ</li>
<li>シンプルなデータ表示</li>
<li>レイアウトコンポーネント</li>
<li>SEOが重要なページ</li>
</ul>
<p><strong>Client Componentsを使うケース</strong>：</p>
<ul>
<li>バリデーション付きのフォーム</li>
<li>リアルタイム機能</li>
<li>インタラクティブなUI</li>
<li>複雑なstate管理</li>
</ul>
<p><strong>構成例</strong>：</p>
<div class="gatsby-highlight" data-language="text"><pre style="counter-reset: linenumber NaN" class="language-text line-numbers"><code class="language-text">app/
  (marketing)/          # Server Components
    page.tsx
    about/page.tsx
  (dashboard)/          # Mixed
    layout.tsx         # Server Component
    page.tsx           # Client Component (interactive)
  (blog)/               # Server Components
    [slug]/page.tsx</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<hr>
<h3>戦略 2：ハイブリッドレンダリング</h3>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// Server Component (page)</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">ProductPage</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> params <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> product <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getProduct</span><span class="token punctuation">(</span>params<span class="token punctuation">.</span>id<span class="token punctuation">)</span>

  <span class="token comment">// Render static content on server</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>div<span class="token operator">></span>
      <span class="token operator">&lt;</span>h1<span class="token operator">></span><span class="token punctuation">{</span>product<span class="token punctuation">.</span>name<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span>
      <span class="token operator">&lt;</span>p<span class="token operator">></span><span class="token punctuation">{</span>product<span class="token punctuation">.</span>description<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">></span>

      <span class="token punctuation">{</span><span class="token comment">/* Interactive parts as Client Components */</span><span class="token punctuation">}</span>
      <span class="token operator">&lt;</span>AddToCartButton productId<span class="token operator">=</span><span class="token punctuation">{</span>product<span class="token punctuation">.</span>id<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
      <span class="token operator">&lt;</span>Reviews productId<span class="token operator">=</span><span class="token punctuation">{</span>product<span class="token punctuation">.</span>id<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token comment">// Client Component (interactive)</span>
<span class="token string">'use client'</span>
<span class="token keyword">function</span> <span class="token function">AddToCartButton</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> productId <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>loading<span class="token punctuation">,</span> setLoading<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span>

  <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">setLoading</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span>
    <span class="token keyword">await</span> <span class="token function">addToCart</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span>
    <span class="token function">setLoading</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">return</span> <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">{</span>handleClick<span class="token punctuation">}</span><span class="token operator">></span>Add to Cart<span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>これがうまくいく理由</strong>：</p>
<ul>
<li>サーバーが静的コンテンツをレンダリング</li>
<li>クライアントがインタラクティビティーを処理</li>
<li>関心の分離が明確</li>
</ul>
<hr>
<h3>戦略 3：待つ（議論の余地はあるが、妥当）</h3>
<p>新しいプロジェクトを始める場合：</p>
<p><strong>以下に該当するなら、まだServer Componentsを使わないことを検討してください</strong>：</p>
<ul>
<li>小規模なチームである</li>
<li>迅速なイテレーションが必要</li>
<li>アプリのインタラクティブ性が高い</li>
<li>開発者体験を重視する</li>
</ul>
<p><strong>代わりに以下を使い続けてください</strong>：</p>
<ul>
<li>Pages Router（Next.js 12までの標準）</li>
<li>Tanstack Queryを使ったClient Components</li>
<li>従来のAPIルート</li>
</ul>
<p><strong>理由</strong>：これらのパターンは：</p>
<ul>
<li>ドキュメントが整備されている</li>
<li>よく理解されている</li>
<li>実戦でテスト済み</li>
<li>デバッグが容易</li>
</ul>
<p>Server Componentsはいずれ成熟します。エコシステムも改善されるでしょう。移行は後からでもできます。</p>
<hr>
<h2>Part 8：移行ガイド（どうしても移行する場合）</h2>
<h3>ステップ 1：アプリの棚卸し</h3>
<p>全てのページを分類します：</p>
<div class="gatsby-highlight" data-language="text"><pre style="counter-reset: linenumber NaN" class="language-text line-numbers"><code class="language-text">✅ Good for RSC:
- Marketing pages
- Blog posts  
- Documentation
- Static dashboards

⚠️ Maybe:
- User profiles
- Product listings
- Search results

❌ Bad for RSC:
- Real-time chat
- Complex forms
- Canvas/drawing apps
- Admin panels with lots of interactivity</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h3>ステップ 2：小さく始める</h3>
<p><strong>全てを書き換えないでください</strong>。1種類のページタイプを選びます：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// Start with: Static blog posts</span>
<span class="token comment">// app/blog/[slug]/page.tsx</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">BlogPost</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> params <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> post <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getPost</span><span class="token punctuation">(</span>params<span class="token punctuation">.</span>slug<span class="token punctuation">)</span>
  <span class="token keyword">return</span> <span class="token operator">&lt;</span>Article post<span class="token operator">=</span><span class="token punctuation">{</span>post<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>まずはシンプルなページでパターンを学びましょう。</p>
<h3>ステップ 3：徐々にインタラクティビティーを追加する</h3>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// app/blog/[slug]/page.tsx (Server Component)</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">BlogPost</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> params <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> post <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getPost</span><span class="token punctuation">(</span>params<span class="token punctuation">.</span>slug<span class="token punctuation">)</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>article<span class="token operator">></span>
      <span class="token operator">&lt;</span>h1<span class="token operator">></span><span class="token punctuation">{</span>post<span class="token punctuation">.</span>title<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span>
      <span class="token operator">&lt;</span>Content<span class="token operator">></span><span class="token punctuation">{</span>post<span class="token punctuation">.</span>content<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>Content<span class="token operator">></span>

      <span class="token punctuation">{</span><span class="token comment">/* Client Component for interactions */</span><span class="token punctuation">}</span>
      <span class="token operator">&lt;</span>LikeButton postId<span class="token operator">=</span><span class="token punctuation">{</span>post<span class="token punctuation">.</span>id<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
      <span class="token operator">&lt;</span>Comments postId<span class="token operator">=</span><span class="token punctuation">{</span>post<span class="token punctuation">.</span>id<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>article<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Server Componentsは、データフェッチと静的コンテンツに集中させましょう。</p>
<h3>ステップ 4：ウォーターフォールに注意する</h3>
<p>React DevToolsのProfilerを使いましょう：</p>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// ❌ Bad: Sequential</span>
<span class="token operator">&lt;</span>ServerComponent1 <span class="token operator">/</span><span class="token operator">></span>
<span class="token operator">&lt;</span>ServerComponent2 <span class="token operator">/</span><span class="token operator">></span> <span class="token punctuation">{</span><span class="token comment">/* Waits for 1 */</span><span class="token punctuation">}</span>
<span class="token operator">&lt;</span>ServerComponent3 <span class="token operator">/</span><span class="token operator">></span> <span class="token punctuation">{</span><span class="token comment">/* Waits for 2 */</span><span class="token punctuation">}</span>

<span class="token comment">// ✅ Good: Parallel</span>
<span class="token keyword">const</span> <span class="token punctuation">[</span>data1<span class="token punctuation">,</span> data2<span class="token punctuation">,</span> data3<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
  <span class="token function">getData1</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token function">getData2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token function">getData3</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">]</span><span class="token punctuation">)</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h3>ステップ 5：適切なエラー境界（Error Boundary）を設定する</h3>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token comment">// app/error.tsx</span>
<span class="token string">'use client'</span> <span class="token comment">// Error boundaries must be Client Components</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Error</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> error<span class="token punctuation">,</span> reset <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>div<span class="token operator">></span>
      <span class="token operator">&lt;</span>h2<span class="token operator">></span>Something went wrong<span class="token operator">!</span><span class="token operator">&lt;</span><span class="token operator">/</span>h2<span class="token operator">></span>
      <span class="token operator">&lt;</span>p<span class="token operator">></span><span class="token punctuation">{</span>error<span class="token punctuation">.</span>message<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">></span>
      <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">{</span>reset<span class="token punctuation">}</span><span class="token operator">></span>Try again<span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Server Componentsは本番環境で失敗する可能性があるため、エラー境界が必要です。</p>
<hr>
<h2>Part 9：代替案</h2>
<h3>代替案 1：Client Components + Tanstack Queryを使い続ける</h3>
<div class="gatsby-highlight" data-language="javascript"><pre style="counter-reset: linenumber NaN" class="language-javascript line-numbers"><code class="language-javascript"><span class="token string">'use client'</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useQuery <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@tanstack/react-query'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">ProductPage</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> params <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> <span class="token literal-property property">data</span><span class="token operator">:</span> product<span class="token punctuation">,</span> isLoading <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useQuery</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token literal-property property">queryKey</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'product'</span><span class="token punctuation">,</span> params<span class="token punctuation">.</span>id<span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token function-variable function">queryFn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/api/products/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>params<span class="token punctuation">.</span>id<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">r</span> <span class="token operator">=></span> r<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span>isLoading<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">&lt;</span>Skeleton <span class="token operator">/</span><span class="token operator">></span>

  <span class="token keyword">return</span> <span class="token operator">&lt;</span>ProductDetails product<span class="token operator">=</span><span class="token punctuation">{</span>product<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>メリット</strong>：</p>
<ul>
<li>よく理解されたパターン</li>
<li>優れたDX</li>
<li>強力なキャッシュ</li>
<li>容易なデバッグ</li>
</ul>
<p><strong>デメリット</strong>：</p>
<ul>
<li>クライアントサイドのローディング状態</li>
<li>大きめの初期バンドル</li>
<li>SEOには追加作業が必要</li>
</ul>
<p><strong>結論</strong>：多くのアプリにとって、いまだに素晴らしい選択肢です。</p>
<hr>
<h3>代替案 2：Remixに移行する</h3>
<p>Remixには Next.js より前から（loader を通じて）Server Components に類似した仕組みがありました：</p>
<div class="gatsby-highlight" data-language="typescript"><pre style="counter-reset: linenumber NaN" class="language-typescript line-numbers"><code class="language-typescript"><span class="token comment">// routes/products/$id.tsx</span>
<span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">loader</span><span class="token punctuation">(</span><span class="token punctuation">{</span> params <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token function">json</span><span class="token punctuation">(</span><span class="token keyword">await</span> <span class="token function">getProduct</span><span class="token punctuation">(</span>params<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Product</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> product <span class="token operator">=</span> <span class="token function">useLoaderData</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">return</span> <span class="token operator">&lt;</span>ProductDetails product<span class="token operator">=</span><span class="token punctuation">{</span>product<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>メリット</strong>：</p>
<ul>
<li>よりシンプルなメンタルモデル</li>
<li>より整備されたドキュメント</li>
<li>明確なデータローディングパターン</li>
<li>優れたエラーハンドリング</li>
</ul>
<p><strong>デメリット</strong>：</p>
<ul>
<li>異なるフレームワーク</li>
<li>移行コスト</li>
</ul>
<p><strong>結論</strong>：新規プロジェクトでは検討する価値があります。</p>
<hr>
<h3>代替案 3：Astroとアイランドアーキテクチャ</h3>
<div class="gatsby-highlight" data-language="astro"><pre style="counter-reset: linenumber NaN" class="language-astro line-numbers"><code class="language-astro">---
// src/pages/product/[id].astro
const product = await getProduct(Astro.params.id)
---

&lt;Layout&gt;
  &lt;h1&gt;{product.name}&lt;/h1&gt;
  &lt;p&gt;{product.description}&lt;/p&gt;

  &lt;AddToCartButton client:load productId={product.id} /&gt;
&lt;/Layout&gt;</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>メリット</strong>：</p>
<ul>
<li>デフォルトで静的</li>
<li>オプトインでインタラクティブにできる</li>
<li>優れたパフォーマンス</li>
<li>シンプルなメンタルモデル</li>
</ul>
<p><strong>デメリット</strong>：</p>
<ul>
<li>純粋なReactではない</li>
<li>エコシステムが小さい</li>
</ul>
<p><strong>結論</strong>：コンテンツ中心のサイトには最適です。</p>
<hr>
<h2>Part 10：未来（これから）</h2>
<h3>Reactチームのロードマップ</h3>
<p>最近のRFCや議論から：</p>
<ol>
<li><strong>より良いDevTools</strong> - 「近日公開」（2年間聞き続けていますが）</li>
<li><strong>キャッシュの改善</strong> - よりきめ細かな制御</li>
<li><strong>ストリーミングの改善</strong> - Suspenseとのより良い統合</li>
<li><strong>TypeScriptサポート</strong> - Server Componentsの型サポート改善</li>
</ol>
<p><strong>私たちが本当に必要としているもの</strong></p>
<ol>
<li>Server Componentsを使うべきでない時についての明確なドキュメント</li>
<li>実際のベンチマークを伴うパフォーマンスガイドライン</li>
<li>RSCを安全に導入するための移行ツール</li>
<li>実際に機能するデバッグツール</li>
<li>制限についての正直なコミュニケーション</li>
</ol>
<hr>
<h2>結論</h2>
<p>React Server Componentsは<strong>銀の弾丸（決め手）ではありません</strong>。特定のユースケース、重大な複雑さ、そして現実的なトレードオフを伴うツールです。</p>
<p><strong>現実</strong>：</p>
<ul>
<li>一部のアプリには適しているが、他のアプリには適していない</li>
<li>メンタルモデルの大幅な転換が必要</li>
<li>ドキュメントが不十分</li>
<li>本番環境での問題が頻発している</li>
<li>学習曲線が険しい</li>
</ul>
<p><strong>私の率直なお勧め</strong>：</p>
<p><strong>Server Componentsを使うべきケース</strong>：</p>
<ul>
<li>✅ コンテンツ中心のサイトを構築している</li>
<li>✅ 複雑さを扱えるシニアチームがいる</li>
<li>✅ アーリーアダプターであることをいとわない</li>
<li>✅ 学習に時間を投資できる</li>
</ul>
<p><strong>Server Componentsを使うべきでないケース</strong>：</p>
<ul>
<li>❌ アプリのインタラクティブ性が高い</li>
<li>❌ 経験の浅い開発者が多い</li>
<li>❌ 迅速な開発が必要</li>
<li>❌ 安定性が最重要</li>
</ul>
<p><strong>Reactコミュニティーは、以下について率直に話し合う必要があります</strong>：</p>
<ul>
<li>RSCが助けになる時 vs ならない時</li>
<li>本当のDXコスト</li>
<li>実際のパフォーマンスへの影響</li>
<li>ドキュメントの不備</li>
</ul>
<p>Server ComponentsはReactの未来です。しかし、一部のアプリケーションにとってはまだ先の話です。
賢明な選択を。</p>
<hr>
<h2>クイック意思決定フレームワーク</h2>
<p><strong>自問してみてください</strong>：</p>
<p>①アプリの何%がインタラクティブですか？</p>
<ul>
<li>30%未満：Server Componentsを検討</li>
<li>30〜70%：ハイブリッドアプローチを使用</li>
<li>70%超：Client Componentsを堅持</li>
</ul>
<p>②チームの経験レベルは？</p>
<ul>
<li>全員シニア：問題なし</li>
<li>混合：慎重に進める</li>
<li>ほぼ経験が浅い開発者：待つ</li>
</ul>
<p>③タイムラインは？</p>
<ul>
<li>学習プロジェクト：実験する</li>
<li>厳しいデッドライン：避ける</li>
<li>長期的な投資：検討の余地あり</li>
</ul>
<p>④優先事項は？</p>
<ul>
<li>パフォーマンス：まず測定する</li>
<li>DX：待つ方がよいかも</li>
<li>SEO：良いユースケース</li>
<li>複雑さ：避ける</li>
</ul>
<h2>リソース</h2>
<ul>
<li><a href="https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md">React Server Components RFC</a></li>
<li><a href="https://nextjs.org/docs/app">Next.js App Router Documentation</a></li>
<li><a href="https://github.com/vercel/next.js/issues?q=is%3Aissue+server+components">Server Components Issues (GitHub)</a></li>
<li><a href="https://github.com/reactwg/server-components/discussions">React Working Group Discussions</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[既存のプロジェクトにCSSカスケードレイヤーを統合する]]></title><description><![CDATA[クイックサマリー:この記事の目的は、CSSカスケードレイヤーを既存のレガシーなコードベースに統合するプロセスを、ありのままに全てお伝えすることです。具体的には、何も壊さないように既存のCSSをリファ…]]></description><link>https://postd.cc/integrating-css-cascade-layers-existing-project/</link><guid isPermaLink="false">https://postd.cc/integrating-css-cascade-layers-existing-project/</guid><category><![CDATA[開発手法・プロジェクト管理]]></category><category><![CDATA[CSS]]></category><pubDate>Thu, 30 Oct 2025 00:00:01 GMT</pubDate><content:encoded><![CDATA[<p><strong>クイックサマリー</strong>:この記事の目的は、CSSカスケードレイヤーを既存のレガシーなコードベースに統合するプロセスを、ありのままに全てお伝えすることです。具体的には、何も壊さないように既存のCSSをリファクタリングしてカスケードレイヤーを使えるようにする方法について解説します。</p>
<hr>
<p>Stephenie Eckles氏の記事「<a href="https://www.smashingmagazine.com/2022/01/introduction-css-cascade-layers/">Getting Started With CSS Cascade Layers</a>」を読めば、いつでも素晴らしい概要を学べます。しかし、この記事では、カスケードレイヤーを実際のコードに統合する体験、その長所、短所、そしてスパゲッティコードとの格闘まで、全てお話ししたいと思います。</p>
<p>よくある解説記事のようにサンプルプロジェクトを用意することもできます。しかし現実の世界はそんな風にはいきません。なぜか動いているけれど、誰もその理由を知らないようなスタイルが書かれたコードを引き継ぐ、といった感じで、実際に手を汚してみたいのです。</p>
<p>カスケードレイヤーを使っていないプロジェクトを見つけるのは簡単でした。難しいのは、詳細度や構成に問題を抱えるほど散らかっていて、なおかつカスケードレイヤー統合のさまざまな側面を説明できるくらい幅広いプロジェクトを見つけることでした。</p>
<p>皆さま、Drishtant Ghosh氏が作成した、こちらの<a href="https://github.com/Drix10/discord-bot-web">DiscordボットのWebサイト</a>をご紹介します。<a href="https://github.com/Drix10">Drishtant氏</a>がご自身の作品を事例として使うことを許可してくださったことに、深く感謝します。このプロジェクトは、ナビゲーションバー、ヒーローセクション、いくつかのボタン、モバイルメニューを備えた、典型的なランディングページです。</p>
<p><img src="https://files.smashing.media/articles/integrating-css-cascade-layers-existing-project/1-discord-bot-landing-page.png">
<em></em>（<a href="https://files.smashing.media/articles/integrating-css-cascade-layers-existing-project/1-discord-bot-landing-page.png">拡大プレビュー</a>）</p></p>
<p>外見は完璧に見えるのがお分かりでしょう。しかし、その裏側にあるCSSスタイルを見てみると、事態は面白くなってきます。</p>
<h2>プロジェクトを理解する</h2>
<p>あちこちで<code class="language-text">@layer</code>を使い始める前に、まずは私たちが扱う対象をしっかりと理解しましょう。GitHubリポジトリを<a href="https://codepen.io/vayospot/pen/bNdoYdP">クローン</a>し、今回はCSSカスケードレイヤーを扱うことに主眼を置いているため、<code class="language-text">index.html</code>、<code class="language-text">index.css</code>、<code class="language-text">index.js</code>の3つのファイルで構成されるメインページにのみ焦点を当てます。</p>
<p><strong>注</strong>： このチュートリアルが冗長になりすぎるのを避けるため、プロジェクトの他のページは含めていません。しかし、実験として他のページをリファクタリングしてみるのも良いでしょう。</p>
<p><code class="language-text">index.css</code>ファイルは450行以上のコードがあり、ざっと目を通しただけでも、いくつかの懸念点が見て取れます。</p>
<ul>
<li>同じHTML要素を指す同じセレクタで、多くのコードが重複している。</li>
<li><code class="language-text">#id</code>セレクタがかなり多い。これについてはCSSで使うべきではないと主張する人もいるでしょう（私もその一人です）。</li>
<li><code class="language-text">#botLogo</code>が2回定義されており、その間は70行以上も離れている。</li>
<li><code class="language-text">!important</code>キーワードがコード全体で安易に使われている。</li>
</ul>
<p>それでも、サイトは機能しています。ここには「技術的に」間違っていることは何もありません。これこそが、CSSが巨大で美しいモンスターであるもう一つの理由です。エラーが表に出てこないのです！</p>
<h2>レイヤー構造を計画する</h2>
<p>さて、「全てのスタイルを<code class="language-text">@layer legacy</code>のような単一のレイヤーにまとめてしまえば、それで終わりじゃないか？」と考える人もいるかもしれません。</p>
<p>それでもいいですが…私はそうすべきではないと思います。</p>
<p>考えてみてください。もし<code class="language-text">legacy</code>レイヤーの後にさらにレイヤーが追加された場合、それらの新しいレイヤーは<code class="language-text">legacy</code>レイヤーに含まれるスタイルを上書きするはずです。なぜなら、レイヤーの詳細度は優先順位によって整理されており、後から宣言されたレイヤーほど高い優先順位を持つからです。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token comment">/* newの方が詳細度が高い */</span>
<span class="token atrule"><span class="token rule">@layer</span> legacy<span class="token punctuation">,</span> new<span class="token punctuation">;</span></span>

<span class="token comment">/* legacyの方が詳細度が高い */</span>
<span class="token atrule"><span class="token rule">@layer</span> new<span class="token punctuation">,</span> legacy<span class="token punctuation">;</span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>とはいえ、このサイトの既存スタイルでは<code class="language-text">!important</code>キーワードが多用されていることを忘れてはなりません。そうなると、カスケードレイヤーの順序は逆転します。ですから、レイヤーが次のように定義されていても、</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> legacy<span class="token punctuation">,</span> new<span class="token punctuation">;</span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div>
<p><code class="language-text">!important</code>が宣言されたスタイルがあると、状況は一変します。この場合、優先順位は次のようになります。</p>
<ol>
<li><code class="language-text">legacy</code>レイヤー内の<code class="language-text">!important</code>スタイル（最も強力）</li>
<li><code class="language-text">new</code>レイヤー内の<code class="language-text">!important</code>スタイル</li>
<li><code class="language-text">new</code>レイヤー内の通常スタイル</li>
<li><code class="language-text">legacy</code>レイヤー内の通常スタイル（最も弱い）</li>
</ol>
<p>この点だけは、はっきりさせておきたかったのです。では、続けましょう。</p>
<p>カスケードレイヤーは、各レイヤーが明確な責務を持ち、後のレイヤーが常に勝つという明確な順序を作ることで詳細度を管理します。</p>
<p>そこで私は、5つの異なるレイヤーに分割することにしました。</p>
<ul>
<li><strong>reset</strong>: <code class="language-text">box-sizing</code>やmargin、paddingといったブラウザのデフォルトスタイルのリセット。</li>
<li><strong>base</strong>: <code class="language-text">body</code>、<code class="language-text">h1</code>、<code class="language-text">p</code>、<code class="language-text">a</code>などのHTML要素のデフォルトスタイル。デフォルトのタイポグラフィや色も含む。</li>
<li><strong>layout</strong>: 要素の配置を制御するための、主要なページ構造に関するもの。</li>
<li><strong>components</strong>: ボタン、カード、メニューなど、再利用可能なUIセグメント。</li>
<li><strong>utilities</strong>: 一つのことだけをうまくこなす、単一目的のヘルパー修飾子。</li>
</ul>
<p>これはあくまで私がスタイルを分割し、整理するのが好きな方法です。例えば、Zell Liew氏は、レイヤーとして定義できる<a href="https://css-tricks.com/composition-in-css/">4つの異なる分類を持っています</a>。</p>
<p>さらに、物事を<strong>サブレイヤー</strong>に分割するという考え方もあります。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> components</span> <span class="token punctuation">{</span>
  <span class="token comment">/* sub-layers */</span>
  <span class="token atrule"><span class="token rule">@layer</span> buttons<span class="token punctuation">,</span> cards<span class="token punctuation">,</span> menus<span class="token punctuation">;</span></span>
<span class="token punctuation">}</span>

<span class="token comment">/* or this: */</span>
<span class="token atrule"><span class="token rule">@layer</span> components.buttons<span class="token punctuation">,</span> components.cards<span class="token punctuation">,</span> components.menus<span class="token punctuation">;</span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>これも便利かもしれませんが、物事を過度に抽象化したくはありません。この戦略は、明確に定義されたデザインシステムを対象とするプロジェクトには、より適しているかもしれません。</p>
<p>私たちが活用できるもう一つのことは、<strong>レイヤー化されていないスタイル</strong>と、カスケードレイヤーに含まれないスタイルが最も高い優先順位を持つという事実です。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> legacy</span> <span class="token punctuation">{</span> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
<span class="token atrule"><span class="token rule">@layer</span> reset</span> <span class="token punctuation">{</span> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> orange <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
<span class="token atrule"><span class="token rule">@layer</span> base</span> <span class="token punctuation">{</span> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> yellow <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>

<span class="token comment">/* unlayered */</span>
<span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> green <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* highest priority */</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>しかし、私は全てのスタイルを明確なレイヤーで整理しておくという考え方が好きです。少なくともこの文脈においては、物事を<strong>モジュール化</strong>し、<strong>保守しやすく</strong>保つことができます。</p>
<p>それでは、このプロジェクトにカスケードレイヤーを追加していきましょう。</p>
<h2>カスケードレイヤーを統合する</h2>
<p>まず、ファイルの先頭でレイヤーの順序を定義する必要があります。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> reset<span class="token punctuation">,</span> base<span class="token punctuation">,</span> layout<span class="token punctuation">,</span> components<span class="token punctuation">,</span> utilities<span class="token punctuation">;</span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div>
<p>これにより、どのレイヤーがどのレイヤーよりも優先されるかが簡単に分かります（左から右に行くにつれて優先度が高くなります）。これで、セレクタの詳細度ではなく、レイヤーの責務という観点で考えられるようになります。ここからは、スタイルシートを上から下へと進めていきます。</p>
<p>最初に気づいたのは、<a href="https://fonts.google.com/specimen/Poppins?query=poppins">Poppinsフォント</a>がHTMLとCSSの両方のファイルでインポートされていたことです。フォントを素早く読み込むためには、一般的にHTMLでのインポートが推奨されるため、CSSのインポートを削除し、<code class="language-text">index.html</code>の方を残しました。</p>
<p>次はユニバーサルセレクタ（<code class="language-text">*</code>）のスタイルです。これには<code class="language-text">@layer reset</code>に最適な、<a href="https://css-tricks.com/box-sizing/">古典的なリセットスタイル</a>が含まれています。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> reset</span> <span class="token punctuation">{</span>
  <span class="token selector">*</span> <span class="token punctuation">{</span>
    <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
    <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
    <span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>それが片付いたら、次はbodyセレクタです。これには背景やフォントといったプロジェクトの核となるスタイルが含まれているので、<code class="language-text">@layer base</code>に入れます。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> base</span> <span class="token punctuation">{</span>
  <span class="token selector">body</span> <span class="token punctuation">{</span>
    <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">"bg.svg"</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span> <span class="token comment">/* 分かりやすいようにbg.svgにリネーム */</span>
    <span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">"Poppins"</span><span class="token punctuation">,</span> sans-serif<span class="token punctuation">;</span>
    <span class="token comment">/* ... other styles */</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>ここでの私の方針は、「<code class="language-text">base</code>レイヤーのスタイルは基本的にドキュメント全体に影響を与えるもの」とすることです。今のところ、ページの表示崩れなどは起きていません。</p>
<h2>IDをクラスに置き換える</h2>
<p><code class="language-text">body</code>要素セレクタの次にあるのは、IDセレクタ<code class="language-text">#loader</code>として定義されているページローダーです。</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p>私は、可能な限りIDセレクタよりもクラスセレクタを使うべきだと考えています。デフォルトで詳細度を低く保つことができ、詳細度の競合を防ぎ、<a href="https://css-tricks.com/the-difference-between-id-and-class/">コードをはるかに保守しやすくする</a>からです。</p></div></div>
<p>そこで、<code class="language-text">index.html</code>ファイルを開き、<code class="language-text">id="loader"</code>を持つ要素を<code class="language-text">class="loader"</code>にリファクタリングしました。その過程で、<code class="language-text">id="page"</code>を持つ別の要素を見つけたので、それも同時に変更しました。</p>
<p><code class="language-text">index.html</code>ファイルにいる間に、いくつかの<code class="language-text">div</code>要素で閉じタグが欠けていることに気づきました。ブラウザがそれに対して寛容なのは驚きです。ともかく、それらをクリーンアップし、<code class="language-text">&lt;script></code>タグを.<code class="language-text">heading</code>要素の外に移動させて、<code class="language-text">body</code>の直接の子要素にしました。スクリプトの読み込みをこれ以上困難にするのはやめましょう。</p>
<p>IDをクラスに移行して詳細度の条件をそろえたので、これらを<code class="language-text">components</code>レイヤーに入れることができます。ローダーはまさに再利用可能なコンポーネントですからね。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> components</span> <span class="token punctuation">{</span>
  <span class="token selector">.loader</span> <span class="token punctuation">{</span>
    <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
    <span class="token property">height</span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span>
    <span class="token comment">/* ... */</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.loader .loading</span> <span class="token punctuation">{</span>
    <span class="token comment">/* ... */</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.loader .loading span</span> <span class="token punctuation">{</span>
    <span class="token comment">/* ... */</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.loader .loading span:before</span> <span class="token punctuation">{</span>
    <span class="token comment">/* ... */</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h2>アニメーション</h2>
<p>次はキーフレームです。これは少し厄介でしたが、最終的にアニメーションを独自の新しい5番目のレイヤーに分離し、それを含むようにレイヤーの順序を更新することにしました。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> reset<span class="token punctuation">,</span> base<span class="token punctuation">,</span> layout<span class="token punctuation">,</span> components<span class="token punctuation">,</span> utilities<span class="token punctuation">,</span> animations<span class="token punctuation">;</span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div>
<p>しかし、なぜ<code class="language-text">animations</code>を最後のレイヤーに置くのでしょうか？それは、アニメーションは一般的に最後に実行されるものであり、スタイルの競合の影響を受けるべきではないからです。</p>
<p>プロジェクトのスタイルから<code class="language-text">@keyframes</code>を検索し、それらを新しいレイヤーに放り込みました。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> animations</span> <span class="token punctuation">{</span>
  <span class="token atrule"><span class="token rule">@keyframes</span> loading</span> <span class="token punctuation">{</span>
    <span class="token comment">/* ... */</span>
  <span class="token punctuation">}</span>
  <span class="token atrule"><span class="token rule">@keyframes</span> loading2</span> <span class="token punctuation">{</span>
    <span class="token comment">/* ... */</span>
  <span class="token punctuation">}</span>
  <span class="token atrule"><span class="token rule">@keyframes</span> pageShow</span> <span class="token punctuation">{</span>
    <span class="token comment">/* ... */</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>これにより、静的なスタイルと動的なスタイルが明確に区別され、再利用性が確保できます。</p>
<h2>レイアウト</h2>
<p><code class="language-text">#page</code>セレクタも<code class="language-text">#id</code>と同じ問題を抱えていましたが、先ほどHTMLで修正したので、これを<code class="language-text">.page</code>に変更し、<code class="language-text">layout</code>レイヤーに入れることができます。その主な目的はコンテンツの初期の表示状態を制御することだからです。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> layout</span> <span class="token punctuation">{</span>
  <span class="token selector">.page</span> <span class="token punctuation">{</span>
    <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h2>カスタムスクロールバー</h2>
<p>これらはどこに置くべきでしょうか？スクロールバーはサイト全体で持続するグローバルな要素です。これはグレーゾーンかもしれませんが、グローバルでデフォルトの機能であるため、<code class="language-text">@layer base</code>に完璧に収まると言えるでしょう。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> base</span> <span class="token punctuation">{</span>
  <span class="token comment">/* ... */</span>
  <span class="token selector">::-webkit-scrollbar</span> <span class="token punctuation">{</span>
    <span class="token property">width</span><span class="token punctuation">:</span> 8px<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">::-webkit-scrollbar-track</span> <span class="token punctuation">{</span>
    <span class="token property">background</span><span class="token punctuation">:</span> #0e0e0f<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">::-webkit-scrollbar-thumb</span> <span class="token punctuation">{</span>
    <span class="token property">background</span><span class="token punctuation">:</span> #5865f2<span class="token punctuation">;</span>
    <span class="token property">border-radius</span><span class="token punctuation">:</span> 100px<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">::-webkit-scrollbar-thumb:hover</span> <span class="token punctuation">{</span>
    <span class="token property">background</span><span class="token punctuation">:</span> #202225<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>また、見つけ次第<code class="language-text">!important</code>キーワードも削除していきました。</p>
<h2>ナビゲーション</h2>
<p><code class="language-text">nav</code>要素は非常に分かりやすいです。ナビゲーションバーの位置と寸法を定義する主要な構造コンテナだからです。これは間違いなく<code class="language-text">layout</code>レイヤーに入れるべきです。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> layout</span> <span class="token punctuation">{</span>
  <span class="token comment">/* ... */</span>
  <span class="token selector">nav</span> <span class="token punctuation">{</span>
    <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
    <span class="token property">height</span><span class="token punctuation">:</span> 55px<span class="token punctuation">;</span>
    <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
    <span class="token property">padding</span><span class="token punctuation">:</span> 0 50px<span class="token punctuation">;</span> <span class="token comment">/* 一貫した左右のpadding */</span>
    <span class="token comment">/* ... */</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h2>ロゴ</h2>
<p>ロゴに関連するスタイルブロックが3つあります。<code class="language-text">nav .logo</code>、<code class="language-text">.logo img</code>、<code class="language-text">#botLogo</code>です。これらの名前は冗長であり、継承によるコンポーネントの再利用性の恩恵を受けることができます。</p>
<p>私は次のようにアプローチしています。</p>
<ol>
<li><code class="language-text">nav .logo</code>は、ロゴが他の場所でも再利用できることを考えると、詳細度が高すぎます。<code class="language-text">nav</code>を削除して、セレクタを単に<code class="language-text">.logo</code>にしました。そこには<code class="language-text">!important</code>キーワードもあったので、削除しました。</li>
<li>以前は柔軟性の低い絶対配置で設定されていた<code class="language-text">.logo img</code>を配置しやすくするため、<code class="language-text">.logo</code>をFlexboxコンテナに更新しました。</li>
<li><code class="language-text">#botLogo</code> IDは2回宣言されていたので、2つのルールセットを1つに統合し、<code class="language-text">.botLogo</code>クラスにすることで詳細度を下げました。そしてもちろん、HTMLを更新してIDをクラスに置き換えました。</li>
<li><code class="language-text">.logo img</code>セレクタは<code class="language-text">.botLogo</code>となり、ロゴの全ての要素をスタイリングするためのベースクラスになります。</li>
</ol>
<p>そして、残ったのは次のコードです。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token comment">/* initially .logo img */</span>
<span class="token selector">.botLogo</span> <span class="token punctuation">{</span>
  <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span>
  <span class="token property">height</span><span class="token punctuation">:</span> 40px<span class="token punctuation">;</span>
  <span class="token property">border</span><span class="token punctuation">:</span> 2px solid #5865f2<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">/* initially #botLogo */</span>
<span class="token selector">.botLogo</span> <span class="token punctuation">{</span>
  <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 180px<span class="token punctuation">;</span>
  <span class="token comment">/* ... */</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>違いは、一方がナビゲーションで、もう一方がヒーローセクションの見出しで使われている点です。2つ目の<code class="language-text">.botLogo</code>は、<code class="language-text">.heading</code> <code class="language-text">.botLogo</code>セレクタで少し詳細度を上げることで変換できます。ついでに、重複しているスタイルも整理しておきましょう。</p>
<p>ロゴを再利用可能なコンポーネントにうまく変えることができたので、コード全体を<code class="language-text">components</code>レイヤーに配置しましょう。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> components</span> <span class="token punctuation">{</span>
  <span class="token comment">/* ... */</span>
  <span class="token selector">.logo</span> <span class="token punctuation">{</span>
    <span class="token property">font-size</span><span class="token punctuation">:</span> 30px<span class="token punctuation">;</span>
    <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span>
    <span class="token property">color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span>
    <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
    <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
    <span class="token property">gap</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.botLogo</span> <span class="token punctuation">{</span>
    <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token comment">/* widthに合わせて正方形の寸法を維持 */</span>
    <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span>
    <span class="token property">width</span><span class="token punctuation">:</span> 40px<span class="token punctuation">;</span>
    <span class="token property">border</span><span class="token punctuation">:</span> 2px solid #5865f2<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.heading .botLogo</span> <span class="token punctuation">{</span>
    <span class="token property">width</span><span class="token punctuation">:</span> 180px<span class="token punctuation">;</span>
    <span class="token property">height</span><span class="token punctuation">:</span> 180px<span class="token punctuation">;</span>
    <span class="token property">background-color</span><span class="token punctuation">:</span> #5865f2<span class="token punctuation">;</span>
    <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0px 0px 8px 2px <span class="token function">rgba</span><span class="token punctuation">(</span>88<span class="token punctuation">,</span> 101<span class="token punctuation">,</span> 242<span class="token punctuation">,</span> 0.5<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">/* ... */</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>これは少し手間がかかりました！しかしこれで、ロゴは新しいレイヤーアーキテクチャに完璧にフィットするコンポーネントとして、適切に設定されました。</p>
<h2>ナビゲーションリスト</h2>
<p>これは典型的なナビゲーションのパターンです。非順序リスト（<code class="language-text">&lt;ul></code>）を、全てのリスト項目を同じ行に水平に（折り返しを許可して）表示する柔軟なコンテナに変えます。これは再利用可能なナビゲーションの一種であり、<code class="language-text">components</code>レイヤーに属します。しかし、追加する前に少しリファクタリングが必要です。</p>
<p>既に<code class="language-text">.mainMenu</code>クラスがあるので、これを活用しましょう。<code class="language-text">nav ul</code>セレクタを全てそのクラスに置き換えます。繰り返しますが、これにより詳細度を低く保ちつつ、その要素が何をするのかがより明確になります。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> components</span> <span class="token punctuation">{</span>
  <span class="token comment">/* ... */</span>
  <span class="token selector">.mainMenu</span> <span class="token punctuation">{</span>
    <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
    <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span>
    <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.mainMenu li</span> <span class="token punctuation">{</span>
    <span class="token property">margin</span><span class="token punctuation">:</span> 0 4px<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.mainMenu li a</span> <span class="token punctuation">{</span>
    <span class="token property">color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span>
    <span class="token property">text-decoration</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
    <span class="token property">font-size</span><span class="token punctuation">:</span> 16px<span class="token punctuation">;</span>
    <span class="token comment">/* ... */</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.mainMenu li a:where(.active, .hover)</span> <span class="token punctuation">{</span>
    <span class="token property">color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span>
    <span class="token property">background</span><span class="token punctuation">:</span> #1d1e21<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.mainMenu li a.active:hover</span> <span class="token punctuation">{</span>
    <span class="token property">background-color</span><span class="token punctuation">:</span> #5865f2<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>コードには、小さい画面でナビゲーションが折りたたまれたときに、「開く」状態と「閉じる」状態を切り替えるために使われるボタンも2つあります。これは<code class="language-text">.mainMenu</code>コンポーネントに特有のものなので、全てを<code class="language-text">components</code>レイヤーにまとめておきます。その過程で、セレクタを結合・簡略化して、よりクリーンで読みやすいスタイルにすることができます。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> components</span> <span class="token punctuation">{</span>
  <span class="token comment">/* ... */</span>
  <span class="token selector">nav:is(.openMenu, .closeMenu)</span> <span class="token punctuation">{</span>
    <span class="token property">font-size</span><span class="token punctuation">:</span> 25px<span class="token punctuation">;</span>
    <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
    <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span>
    <span class="token property">color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>また、CSS内の他のいくつかのセレクタがHTMLのどこにも使われていないことに気づきました。そこで、コードをきれいな状態に保つために、それらのスタイルを削除しました。<a href="https://css-tricks.com/how-do-you-remove-unused-css-from-a-site/">これを行うための自動化された方法</a>もあります。</p>
<h2>メディアクエリ</h2>
<p>メディアクエリは専用のレイヤー（<code class="language-text">@layer responsive</code>）を持つべきでしょうか、それとも対象要素と同じレイヤーにあるべきでしょうか？このプロジェクトのスタイルをリファクタリングしている間、私はこの問題に本当に苦労しました。いくつか調査とテストを行った結果、私の判断は後者、つまり<strong>メディアクエリは影響を与える要素と同じレイヤーにあるべきだ</strong>、というものです。</p>
<p>私の理由は、それらを一緒に保つことで、</p>
<ul>
<li>レスポンシブスタイルと、そのベースとなる要素のスタイルを一緒に維持できる。</li>
<li>上書きが予測可能になる。</li>
<li>現代のWeb開発で一般的なコンポーネントベースのアーキテクチャとうまく調和する。</li>
</ul>
<p>しかし、これは<strong>レスポンシブロジック</strong>がレイヤー間に散らばることも意味します。ですが、要素がスタイリングされるレイヤーと、そのレスポンシブな振る舞いが管理されるレイヤーとの間にギャップがあるよりはマシです。私にとって、それは避けたいアプローチです。なぜなら、あるレイヤーでスタイルを更新し、それに対応するレスポンシブスタイルをresponsiveレイヤーで更新し忘れがちだからです。</p>
<p>もう一つの大きなポイントは、同じレイヤー内のメディアクエリは、その要素と<strong>同じ優先順位</strong>を持つということです。これは、CSSカスケードをシンプルで予測可能に保ち、スタイルの競合をなくすという私の全体的な目標と一致しています。</p>
<p>さらに、<a href="https://css-tricks.com/tag/nesting/">CSSネスティング構文</a>は、メディアクエリと要素の関係を非常に明確にします。<code class="language-text">components</code>レイヤーにメディアクエリをネストした場合の見た目の省略例を次に示します。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> components</span> <span class="token punctuation">{</span>
  <span class="token selector">.mainMenu</span> <span class="token punctuation">{</span>
    <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
    <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span>
    <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">max-width</span><span class="token punctuation">:</span> 900px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
    <span class="token selector">.mainMenu</span> <span class="token punctuation">{</span>
      <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
      <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
      <span class="token property">height</span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span>
      <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>これにより、コンポーネントの子要素のスタイル（例：<code class="language-text">nav .openMenu</code>と<code class="language-text">nav .closeMenu</code>）もネストできます。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> components</span> <span class="token punctuation">{</span>
  <span class="token selector">nav</span> <span class="token punctuation">{</span>
    <span class="token selector">&amp;.openMenu</span> <span class="token punctuation">{</span>
      <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
      
      <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">max-width</span><span class="token punctuation">:</span> 900px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
        <span class="token selector">&amp;.openMenu</span> <span class="token punctuation">{</span>
          <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h2>タイポグラフィとボタン</h2>
<p><code class="language-text">.title</code>と<code class="language-text">.subtitle</code>はタイポグラフィのコンポーネントと見なせるので、それらと関連するレスポンシブスタイルは、ご想像の通り、<code class="language-text">components</code>レイヤーに入ります。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> components</span> <span class="token punctuation">{</span>
  <span class="token selector">.title</span> <span class="token punctuation">{</span>
    <span class="token property">font-size</span><span class="token punctuation">:</span> 40px<span class="token punctuation">;</span>
    <span class="token property">font-weight</span><span class="token punctuation">:</span> 700<span class="token punctuation">;</span>
    <span class="token comment">/* etc. */</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.subtitle</span> <span class="token punctuation">{</span>
    <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 0.75<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token property">font-size</span><span class="token punctuation">:</span> 15px<span class="token punctuation">;</span>
    <span class="token comment">/* etc.. */</span>
  <span class="token punctuation">}</span>
  <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">max-width</span><span class="token punctuation">:</span> 420px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
    <span class="token selector">.title</span> <span class="token punctuation">{</span>
      <span class="token property">font-size</span><span class="token punctuation">:</span> 30px<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token selector">.subtitle</span> <span class="token punctuation">{</span>
      <span class="token property">font-size</span><span class="token punctuation">:</span> 12px<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>ボタンについてはどうでしょう？多くのWebサイトと同様に、このサイトにもそのコンポーネントのための<code class="language-text">.btn</code>クラスがあるので、それらもそこに入れてしまいましょう。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> components</span> <span class="token punctuation">{</span>
  <span class="token selector">.btn</span> <span class="token punctuation">{</span>
    <span class="token property">color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span>
    <span class="token property">background-color</span><span class="token punctuation">:</span> #1d1e21<span class="token punctuation">;</span>
    <span class="token property">font-size</span><span class="token punctuation">:</span> 18px<span class="token punctuation">;</span>
    <span class="token comment">/* etc. */</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.btn-primary</span> <span class="token punctuation">{</span>
    <span class="token property">background-color</span><span class="token punctuation">:</span> #5865f2<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.btn-secondary</span> <span class="token punctuation">{</span>
    <span class="token property">transition</span><span class="token punctuation">:</span> all 0.3s ease-in-out<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.btn-primary:hover</span> <span class="token punctuation">{</span>
    <span class="token property">background-color</span><span class="token punctuation">:</span> #5865f2<span class="token punctuation">;</span>
    <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0px 0px 8px 2px <span class="token function">rgba</span><span class="token punctuation">(</span>88<span class="token punctuation">,</span> 101<span class="token punctuation">,</span> 242<span class="token punctuation">,</span> 0.5<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">/* etc. */</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.btn-secondary:hover</span> <span class="token punctuation">{</span>
    <span class="token property">background-color</span><span class="token punctuation">:</span> #1d1e21<span class="token punctuation">;</span>
    <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>88<span class="token punctuation">,</span> 101<span class="token punctuation">,</span> 242<span class="token punctuation">,</span> 0.7<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">max-width</span><span class="token punctuation">:</span> 420px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
    <span class="token selector">.btn</span> <span class="token punctuation">{</span>
      <span class="token property">font-size</span><span class="token punctuation">:</span> 14px<span class="token punctuation">;</span>
      <span class="token property">margin</span><span class="token punctuation">:</span> 2px<span class="token punctuation">;</span>
      <span class="token property">padding</span><span class="token punctuation">:</span> 8px 13px<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
  <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">max-width</span><span class="token punctuation">:</span> 335px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
    <span class="token selector">.btn</span> <span class="token punctuation">{</span>
      <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
      <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h2>最後のレイヤー</h2>
<p>まだ<code class="language-text">utilities</code>レイヤーには触れていませんでしたね！私はこのレイヤーを、特定の目的のために設計されたヘルパークラス用に確保しています。例えばコンテンツを非表示にしたり、このケースでは、ぴったりな<code class="language-text">.noselect</code>クラスがあります。これには、要素の選択を無効にするという単一の再利用可能な目的があります。</p>
<p>というわけで、これが私たちの<code class="language-text">utilities</code>レイヤーにおける唯一のスタイルルールになります。</p>
<div class="gatsby-highlight" data-language="css"><pre style="counter-reset: linenumber NaN" class="language-css line-numbers"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> utilities</span> <span class="token punctuation">{</span>
  <span class="token selector">.noselect</span> <span class="token punctuation">{</span>
    <span class="token property">-webkit-touch-callout</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
    <span class="token property">-webkit-user-select</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
    <span class="token property">-khtml-user-select</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
    <span class="token property">-webkit-user-drag</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
    <span class="token property">-moz-user-select</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
    <span class="token property">-ms-user-select</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
    <span class="token property">user-select</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>以上です！私たちは実在するプロジェクトのCSSを、CSSカスケードレイヤーを使うように完全にリファクタリングしました。<a href="https://codepen.io/vayospot/pen/bNdoYdP">作業開始時点</a>のコードと<a href="https://codepen.io/vayospot/pen/XJbeVdB">最終的なコード</a>を比較することができます。</p>
<h2>全てが簡単だったわけではない</h2>
<p>カスケードレイヤーでの作業が困難だったというわけではありませんが、その過程でいくつかの厄介な点があり、一度立ち止まって自分が何をしているのかを慎重に考えさせられました。</p>
<p>作業中にいくつかのメモを取りました。</p>
<ul>
<li>
<p>既存のプロジェクトでどこから手をつけるべきかを判断するのは難しい。 しかし、最初にレイヤーを定義し、その優先順位レベルを設定することで、既存のCSSに完全には慣れていなくても、特定のスタイルをどのように、どこに移動させるかを決定するためのフレームワークができました。これにより、自分を疑ったり、余分で不要なレイヤーを定義したりする状況を避けることができました。</p>
</li>
<li>
<p>ブラウザサポートは依然として問題！ 私がこれを書いている時点で、カスケードレイヤーは94%のサポート率を誇りますが、あなたのサイトがレイヤー化されたスタイルをサポートできないレガシーブラウザに対応する必要があるかもしれません。</p>
</li>
<li>
<p>メディアクエリがプロセスの中でどこに当てはまるのかが明確ではなかった。 メディアクエリは、それらが最も効果的に機能する場所を見つけるという難題を私に突きつけました。セレクタと同じレイヤーにネストするのか、それとも完全に別のレイヤーにするのか？ご存じの通り、私は前者を選びました。</p>
</li>
<li>
<p><code class="language-text">!important</code>キーワードの扱いは、まるで綱渡りのようです。これはレイヤーの優先順位システム全体を反転させてしまうのですが、このプロジェクトでは至る所で使われていました。これらを一つずつ取り除いていくと、既存のCSSアーキテクチャが崩れていきます。そのため、コードをリファクタリングしつつ既存の挙動を壊さないように修正し、スタイルが最終的にどう適用される（カスケードする）のかを正確に把握する、というバランス感覚が求められます。</p>
</li>
</ul>
<p><strong>全体として、CSSカスケードレイヤーのためにコードベースをリファクタリングすることは、一見すると少し気が遠くなる作業です。しかし重要なのは、物事を複雑にしているのはレイヤーそのものではなく、既存のコードベースであると認識することです</strong>。</p>
<p>新しいアプローチがエレガントであっても、誰かの既存のアプローチを新しいものに完全に刷新するのは難しいものです。</p>
<h2>カスケードレイヤーが役立った点（と、そうでもなかった点）</h2>
<p>レイヤーを確立したことで、コードは間違いなく改善されました。未使用のスタイルや競合するスタイルを削除できたので、<strong>パフォーマンスベンチマーク</strong>にもいくつか改善が見られるはずですが、真の価値は、<strong>より保守しやすくなったスタイル一式</strong>にあります。必要なものを見つけ、特定のスタイルルールが何をしているのかを把握し、今後新しいスタイルをどこに挿入すればよいかが、より簡単になりました。</p>
<p>同時に、カスケードレイヤーが銀の弾丸のような解決策だとは言いません。忘れてはならないのは、CSSはそれがクエリするHTML構造と本質的に結びついているということです。もし扱っているHTMLが構造化されておらず、<code class="language-text">div</code>の乱用に苦しんでいるのであれば、その混乱を解きほぐす努力はより大きくなり、同時にマークアップの書き換えも伴うと見て間違いないでしょう。</p>
<p><strong>カスケードレイヤーのためのCSSリファクタリングは、メンテナンス性の向上だけでも、間違いなくその価値があります</strong>。</p>
<p>ゼロから始めて、作業を進めながらレイヤーを定義していく方が「簡単」かもしれません。なぜなら、整理すべき継承されたオーバーヘッドや技術的負債が少ないからです。しかし、既存のコードベースから始めなければならない場合は、まずスタイルの複雑さを解きほぐし、どの程度のリファクタリングが必要かを正確に判断する必要があるかもしれません。</p>]]></content:encoded></item><item><title><![CDATA[Next.jsのFetch Wrapper：ベストプラクティスを徹底解説]]></title><description><![CDATA[想像してみてください。あなたは夢のNext.jsアプリケーションを構築し、ネイティブの関数を使って楽しくAPIコールを行っています。全てが順調に見えましたが、アプリケーションを本番環境にデプロイした…]]></description><link>https://postd.cc/fetch-wrapper-for-nextjs-a-deep-dive-into-best-practices-53dh/</link><guid isPermaLink="false">https://postd.cc/fetch-wrapper-for-nextjs-a-deep-dive-into-best-practices-53dh/</guid><category><![CDATA[開発手法・プロジェクト管理]]></category><category><![CDATA[React]]></category><category><![CDATA[Next.js]]></category><pubDate>Thu, 25 Sep 2025 00:00:01 GMT</pubDate><content:encoded><![CDATA[<p>想像してみてください。あなたは夢のNext.jsアプリケーションを構築し、ネイティブの<code class="language-text">fetch</code>関数を使って楽しくAPIコールを行っています。全てが順調に見えましたが、アプリケーションを本番環境にデプロイした途端、事態は一変します。反復的なエラー処理のコードに溺れ、コンポーネントのあちこちに散らばった認証トークンと格闘し、なぜ一部のリクエストがサーバーでは成功するのにクライアントでは失敗するのか、そのデバッグに髪をかきむしる日々……。</p>
<p>聞き覚えがありませんか？あなただけではありません。Next.jsは<code class="language-text">fetch</code>を強力なキャッシュ機能や再検証機能で拡張していますが、中心的な課題は残っています。本番環境に対応した堅牢なAPIレイヤーを構築するには、素の<code class="language-text">fetch</code>コールだけでは不十分なのです。</p>
<p>本記事では、あなたのAPIレイヤーを頭痛の種から、扱うのが楽しくなるものへと変える、本番環境対応のfetchラッパーを作成します。その構築方法だけでなく、なぜそれぞれの決定が重要なのか、そしてそれがNext.js特有の実行モデルにどう適合するのかを掘り下げていきましょう。</p>
<h2>素のfetchが抱える問題点（Next.jsでも）</h2>
<p>まず、多くの開発者が最初に<code class="language-text">fetch</code>に出会ったときに書くコードを見てみましょう。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token comment">// ナイーブなアプローチ - 本番環境では使わないでください！</span>
<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getUser</span><span class="token punctuation">(</span><span class="token parameter">id</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/api/users/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>id<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> user<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>これは一見無害に見えますが、時を刻む時限爆弾です。APIが404を返すと、次のようになります。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token comment">// エラーレスポンスからJSONをパースしようとすると、エラーがthrowされます</span>
<span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getUser</span><span class="token punctuation">(</span><span class="token string">'nonexistent-id'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 💥 ドカン！</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span></span></pre></div>
<p>根本的な問題は、<code class="language-text">fetch</code>が<strong>HTTPのエラーステータスを例外として扱わない</strong>点にあります。404、500、その他のエラーステータスも、<code class="language-text">fetch</code>にとっては「成功した」リクエストと見なされます。fetchがrejectするのはネットワークエラーの場合のみです。そのため、Next.jsを使っている場合でも、手動で<code class="language-text">response.ok</code>をチェックし、適切にエラーを処理する必要があります。</p>
<p>しかし、これはほんの始まりに過ぎません。実際のアプリケーションでは、以下のような事柄も処理する必要があります。</p>
<ul>
<li>保護されたルートのための<strong>認証トークン</strong></li>
<li>アプリ全体で<strong>一貫したエラーハンドリング</strong></li>
<li><strong>リクエスト/レスポンスの変換</strong>（JSONの自動パースなど）</li>
<li><strong>ローディング状態とエラーバウンダリ</strong></li>
<li>型安全性のための<strong>TypeScriptサポート</strong></li>
<li>Next.jsにおけるサーバーとクライアントのコンテキストによる<strong>挙動の違い</strong></li>
</ul>
<p>これら全ての問題をエレガントに解決するラッパーを構築していきましょう。</p>
<h2>Fetchラッパーのアーキテクチャ設計</h2>
<p>コードに入る前に、理想的なラッパーが提供すべきものを考えてみましょう。</p>
<ol>
<li>HTTPステータスコードの<strong>自動エラーハンドリング</strong></li>
<li>適切なエラー処理を備えた<strong>組み込みのJSONパース</strong></li>
<li><strong>認証トークンの管理</strong></li>
<li>適切な型付けによる<strong>TypeScriptサポート</strong></li>
<li><strong>Next.jsのコンテキスト認識</strong>（サーバー vs. クライアント）</li>
<li>さまざまな環境に対応できる<strong>拡張可能な設定</strong></li>
<li>自然に使える<strong>一貫したAPI</strong></li>
</ol>
<p>このラッパーを、退屈でエラーを起こしやすいタスクを全て処理しつつ、クリーンで予測可能なインターフェースを提供してくれる、親切なアシスタントだと考えてください。</p>
<h2>基盤の構築：ラッパーのコア構造</h2>
<p>まずは基本的な構造から始めましょう。Next.jsの慣習に従い、<code class="language-text">lib/api.ts</code>というファイルを作成します。</p>
<div class="gatsby-highlight" data-language="ts"><pre style="counter-reset: linenumber NaN" class="language-ts line-numbers"><code class="language-ts"><span class="token comment">// lib/api.ts</span>
<span class="token keyword">interface</span> <span class="token class-name">ApiConfig</span> <span class="token punctuation">{</span>
  baseUrl<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
  defaultHeaders<span class="token operator">?</span><span class="token operator">:</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">string</span><span class="token operator">></span><span class="token punctuation">;</span>
  timeout<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">interface</span> <span class="token class-name">ApiResponse<span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token operator">=</span> <span class="token builtin">any</span><span class="token operator">></span></span> <span class="token punctuation">{</span>
  data<span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">;</span>
  status<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span>
  headers<span class="token operator">:</span> Headers<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">class</span> <span class="token class-name">ApiError</span> <span class="token keyword">extends</span> <span class="token class-name">Error</span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span>
    message<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span>
    <span class="token keyword">public</span> status<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">,</span>
    <span class="token keyword">public</span> response<span class="token operator">?</span><span class="token operator">:</span> Response
  <span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">super</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> <span class="token string">'ApiError'</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">class</span> <span class="token class-name">ApiClient</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> config<span class="token operator">:</span> Required<span class="token operator">&lt;</span>ApiConfig<span class="token operator">></span><span class="token punctuation">;</span>

  <span class="token function">constructor</span><span class="token punctuation">(</span>config<span class="token operator">:</span> ApiConfig <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>config <span class="token operator">=</span> <span class="token punctuation">{</span>
      baseUrl<span class="token operator">:</span> config<span class="token punctuation">.</span>baseUrl <span class="token operator">||</span> <span class="token string">''</span><span class="token punctuation">,</span>
      defaultHeaders<span class="token operator">:</span> <span class="token punctuation">{</span>
        <span class="token string-property property">'Content-Type'</span><span class="token operator">:</span> <span class="token string">'application/json'</span><span class="token punctuation">,</span>
        <span class="token operator">...</span>config<span class="token punctuation">.</span>defaultHeaders<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      timeout<span class="token operator">:</span> config<span class="token punctuation">.</span>timeout <span class="token operator">||</span> <span class="token number">10000</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token generic-function"><span class="token function">makeRequest</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>
    endpoint<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span>
    options<span class="token operator">:</span> RequestInit <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
  <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>ApiResponse<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">>></span> <span class="token punctuation">{</span>
    <span class="token comment">// 次にこれを実装します</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>なぜクラスベースのアプローチなのか？</strong> Reactでは関数型アプローチが人気ですが、クラスにはいくつかの利点があります。</p>
<ul>
<li><strong>状態管理</strong>: 設定や、場合によってはキャッシュデータを保存できます。</li>
<li><strong>メソッドチェーン</strong>: 流れるようなAPIメソッドを後からでも追加できます。</li>
<li><strong>継承</strong>: チームは特定のユースケースのためにベースクラスを拡張できます。</li>
<li><strong>カプセル化</strong>: privateメソッドで実装の詳細を隠蔽できます。</li>
</ul>
<h2>ラッパーの心臓部：リクエストロジック</h2>
<p>では、コアとなるリクエストロジックを実装しましょう。ここが肝心な部分です。</p>
<div class="gatsby-highlight" data-language="ts"><pre style="counter-reset: linenumber NaN" class="language-ts line-numbers"><code class="language-ts"><span class="token comment">// lib/api.ts (続き)</span>
<span class="token keyword">class</span> <span class="token class-name">ApiClient</span> <span class="token punctuation">{</span>
  <span class="token comment">// ... 前のコード</span>

  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token generic-function"><span class="token function">makeRequest</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>
    endpoint<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span>
    options<span class="token operator">:</span> RequestInit <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
  <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>ApiResponse<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">>></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> url <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">buildUrl</span><span class="token punctuation">(</span>endpoint<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> requestOptions <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">buildRequestOptions</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token comment">// タイムアウト用のAbortControllerを作成</span>
      <span class="token keyword">const</span> controller <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AbortController</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">const</span> timeoutId <span class="token operator">=</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> controller<span class="token punctuation">.</span><span class="token function">abort</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>config<span class="token punctuation">.</span>timeout<span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> <span class="token punctuation">{</span>
        <span class="token operator">...</span>requestOptions<span class="token punctuation">,</span>
        signal<span class="token operator">:</span> controller<span class="token punctuation">.</span>signal<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token comment">// ここがキーポイントです。ステータスをチェックし、エラーの場合はthrowします</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>response<span class="token punctuation">.</span>ok<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ApiError</span><span class="token punctuation">(</span>
          <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">HTTP </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>response<span class="token punctuation">.</span>status<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>response<span class="token punctuation">.</span>statusText<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
          response<span class="token punctuation">.</span>status<span class="token punctuation">,</span>
          response
        <span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>

      <span class="token comment">// JSONを安全にパース</span>
      <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token generic-function"><span class="token function">parseResponse</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>response<span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token keyword">return</span> <span class="token punctuation">{</span>
        data<span class="token punctuation">,</span>
        status<span class="token operator">:</span> response<span class="token punctuation">.</span>status<span class="token punctuation">,</span>
        headers<span class="token operator">:</span> response<span class="token punctuation">.</span>headers<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>error<span class="token punctuation">.</span>name <span class="token operator">===</span> <span class="token string">'AbortError'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ApiError</span><span class="token punctuation">(</span><span class="token string">'Request timeout'</span><span class="token punctuation">,</span> <span class="token number">408</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
      <span class="token keyword">throw</span> error<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">buildUrl</span><span class="token punctuation">(</span>endpoint<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">{</span>
    <span class="token comment">// 絶対URLと相対URLの両方を処理</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>endpoint<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'http'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> endpoint<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>config<span class="token punctuation">.</span>baseUrl<span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>endpoint<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span> <span class="token operator">?</span> endpoint <span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>endpoint<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">buildRequestOptions</span><span class="token punctuation">(</span>options<span class="token operator">:</span> RequestInit<span class="token punctuation">)</span><span class="token operator">:</span> RequestInit <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      <span class="token operator">...</span>options<span class="token punctuation">,</span>
      headers<span class="token operator">:</span> <span class="token punctuation">{</span>
        <span class="token operator">...</span><span class="token keyword">this</span><span class="token punctuation">.</span>config<span class="token punctuation">.</span>defaultHeaders<span class="token punctuation">,</span>
        <span class="token operator">...</span>options<span class="token punctuation">.</span>headers<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token generic-function"><span class="token function">parseResponse</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>response<span class="token operator">:</span> Response<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> contentType <span class="token operator">=</span> response<span class="token punctuation">.</span>headers<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'content-type'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>contentType<span class="token operator">?.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'application/json'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ApiError</span><span class="token punctuation">(</span><span class="token string">'Invalid JSON response'</span><span class="token punctuation">,</span> response<span class="token punctuation">.</span>status<span class="token punctuation">,</span> response<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// テキストレスポンスを処理</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">as</span> <span class="token builtin">unknown</span> <span class="token keyword">as</span> <span class="token constant">T</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><strong>ここでの重要な点</strong>は、<code class="language-text">fetch</code>の挙動をより直感的なものに変換していることです。<code class="language-text">response.ok</code>をチェックし、HTTPエラーに対してエラーをthrowすることで、アプリケーション全体でエラーハンドリングが予測可能で一貫したものになります。</p>
<h2>HTTPメソッドの便利なラッパーメソッドを追加する</h2>
<p>次に、開発者が実際に使用するメソッドを追加しましょう。</p>
<div class="gatsby-highlight" data-language="ts"><pre style="counter-reset: linenumber NaN" class="language-ts line-numbers"><code class="language-ts"><span class="token comment">// lib/api.ts (続き)</span>
<span class="token keyword">class</span> <span class="token class-name">ApiClient</span> <span class="token punctuation">{</span>
  <span class="token comment">// ... 前のコード</span>

  <span class="token keyword">async</span> <span class="token generic-function"><span class="token function">get</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>endpoint<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> options<span class="token operator">?</span><span class="token operator">:</span> RequestInit<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>ApiResponse<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">>></span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token generic-function"><span class="token function">makeRequest</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>endpoint<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token operator">...</span>options<span class="token punctuation">,</span> method<span class="token operator">:</span> <span class="token string">'GET'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token generic-function"><span class="token function">post</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>
    endpoint<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span>
    data<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span>
    options<span class="token operator">?</span><span class="token operator">:</span> RequestInit
  <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>ApiResponse<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">>></span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token generic-function"><span class="token function">makeRequest</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>endpoint<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      <span class="token operator">...</span>options<span class="token punctuation">,</span>
      method<span class="token operator">:</span> <span class="token string">'POST'</span><span class="token punctuation">,</span>
      body<span class="token operator">:</span> data <span class="token operator">?</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token generic-function"><span class="token function">put</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>
    endpoint<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span>
    data<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span>
    options<span class="token operator">?</span><span class="token operator">:</span> RequestInit
  <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>ApiResponse<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">>></span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token generic-function"><span class="token function">makeRequest</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>endpoint<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      <span class="token operator">...</span>options<span class="token punctuation">,</span>
      method<span class="token operator">:</span> <span class="token string">'PUT'</span><span class="token punctuation">,</span>
      body<span class="token operator">:</span> data <span class="token operator">?</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token keyword">delete</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">(</span>endpoint<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> options<span class="token operator">?</span><span class="token operator">:</span> RequestInit<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>ApiResponse<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">>></span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token generic-function"><span class="token function">makeRequest</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>endpoint<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token operator">...</span>options<span class="token punctuation">,</span> method<span class="token operator">:</span> <span class="token string">'DELETE'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token generic-function"><span class="token function">patch</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>
    endpoint<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span>
    data<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span>
    options<span class="token operator">?</span><span class="token operator">:</span> RequestInit
  <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>ApiResponse<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">>></span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token generic-function"><span class="token function">makeRequest</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>endpoint<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      <span class="token operator">...</span>options<span class="token punctuation">,</span>
      method<span class="token operator">:</span> <span class="token string">'PATCH'</span><span class="token punctuation">,</span>
      body<span class="token operator">:</span> data <span class="token operator">?</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>POST、PUT、PATCHリクエストのデータを自動的にJSON.stringifyしている点に注目してください。これにより、リクエストボディのstringifyを忘れるという、もう一つのよくあるバグの原因を排除します。</p>
<h2>認証：トークン管理の課題</h2>
<p>Next.jsでは認証が面白くなるところです。従来のSPAとは異なり、サーバーサイドとクライアントサイドの両方のコンテキストを考慮する必要があります。認証サポートを追加しましょう。</p>
<div class="gatsby-highlight" data-language="ts"><pre style="counter-reset: linenumber NaN" class="language-ts line-numbers"><code class="language-ts"><span class="token comment">// lib/api.ts (続き)</span>
<span class="token keyword">interface</span> <span class="token class-name">AuthConfig</span> <span class="token punctuation">{</span>
  tokenProvider<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token builtin">string</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">></span> <span class="token operator">|</span> <span class="token builtin">string</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  tokenHeader<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
  tokenPrefix<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">class</span> <span class="token class-name">ApiClient</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> authConfig<span class="token operator">:</span> AuthConfig<span class="token punctuation">;</span>

  <span class="token function">constructor</span><span class="token punctuation">(</span>config<span class="token operator">:</span> ApiConfig <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> authConfig<span class="token operator">:</span> AuthConfig <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// ... 前のコンストラクタのコード</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>authConfig <span class="token operator">=</span> <span class="token punctuation">{</span>
      tokenHeader<span class="token operator">:</span> <span class="token string">'Authorization'</span><span class="token punctuation">,</span>
      tokenPrefix<span class="token operator">:</span> <span class="token string">'Bearer'</span><span class="token punctuation">,</span>
      <span class="token operator">...</span>authConfig<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token function">buildRequestOptions</span><span class="token punctuation">(</span>options<span class="token operator">:</span> RequestInit<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>RequestInit<span class="token operator">></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> headers <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span><span class="token keyword">this</span><span class="token punctuation">.</span>config<span class="token punctuation">.</span>defaultHeaders <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token comment">// 利用可能な場合は認証トークンを追加</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>authConfig<span class="token punctuation">.</span>tokenProvider<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> token <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>authConfig<span class="token punctuation">.</span><span class="token function">tokenProvider</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>token<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        headers<span class="token punctuation">[</span><span class="token keyword">this</span><span class="token punctuation">.</span>authConfig<span class="token punctuation">.</span>tokenHeader<span class="token operator">!</span><span class="token punctuation">]</span> <span class="token operator">=</span>
          <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>authConfig<span class="token punctuation">.</span>tokenPrefix<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>token<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      <span class="token operator">...</span>options<span class="token punctuation">,</span>
      headers<span class="token operator">:</span> <span class="token punctuation">{</span>
        <span class="token operator">...</span>headers<span class="token punctuation">,</span>
        <span class="token operator">...</span>options<span class="token punctuation">.</span>headers<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// makeRequestを更新して、非同期のbuildRequestOptionsを使用するようにする</span>
  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token generic-function"><span class="token function">makeRequest</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>
    endpoint<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span>
    options<span class="token operator">:</span> RequestInit <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
  <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>ApiResponse<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">>></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> url <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">buildUrl</span><span class="token punctuation">(</span>endpoint<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> requestOptions <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">buildRequestOptions</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// ... メソッドの残りの部分は同じ</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>ここでは<strong>トークンプロバイダーパターン</strong>が非常に重要です。トークンを直接保存する代わりに、それを取得できる関数を提供します。これにより、以下のことが可能になります。</p>
<ul>
<li>さまざまなソース（localStorage、Cookie、メモリ）から<strong>新しいトークンをフェッチする</strong></li>
<li><strong>トークン更新</strong>ロジックを透過的に処理する</li>
<li>異なるストレージメカニズムを持つサーバーとクライアントの<strong>両方のコンテキストで動作する</strong></li>
</ul>
<h2>コンテキストを意識したAPIインスタンスの作成</h2>
<p>ここからがNext.jsならではの魔法の出番です。実行コンテキストごとに異なる設定が必要です。</p>
<div class="gatsby-highlight" data-language="ts"><pre style="counter-reset: linenumber NaN" class="language-ts line-numbers"><code class="language-ts"><span class="token comment">// lib/api.ts (続き)</span>

<span class="token comment">// クライアントサイドのトークンプロバイダー（ブラウザのみ）</span>
<span class="token keyword">const</span> getClientToken <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> window <span class="token operator">===</span> <span class="token string">'undefined'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> localStorage<span class="token punctuation">.</span><span class="token function">getItem</span><span class="token punctuation">(</span><span class="token string">'auth_token'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token comment">// サーバーサイドのトークンプロバイダー（SSR用）</span>
<span class="token keyword">const</span> getServerToken <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token comment">// サーバーコンポーネントでは、Cookieからトークンを取得することがあります</span>
  <span class="token comment">// これは簡略化された例です - 実際にはNext.jsのheaders()を使用します</span>
  <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token comment">// コンテキストごとに異なるインスタンスを作成</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> clientApi <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ApiClient</span><span class="token punctuation">(</span>
  <span class="token punctuation">{</span>
    baseUrl<span class="token operator">:</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NEXT_PUBLIC_API_URL</span> <span class="token operator">||</span> <span class="token string">'/api'</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span>
    tokenProvider<span class="token operator">:</span> getClientToken<span class="token punctuation">,</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> serverApi <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ApiClient</span><span class="token punctuation">(</span>
  <span class="token punctuation">{</span>
    baseUrl<span class="token operator">:</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">API_URL</span> <span class="token operator">||</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NEXT_PUBLIC_API_URL</span> <span class="token operator">||</span> <span class="token string">'http://localhost:3000/api'</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span>
    tokenProvider<span class="token operator">:</span> getServerToken<span class="token punctuation">,</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 利便性のために、コンテキストを意識したAPIをエクスポート</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> api <span class="token operator">=</span> <span class="token keyword">typeof</span> window <span class="token operator">===</span> <span class="token string">'undefined'</span> <span class="token operator">?</span> serverApi <span class="token operator">:</span> clientApi<span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>これは非常に<strong>画期的なアプローチ</strong>です。コンテキストを意識したインスタンスを作成することで、サーバーとクライアントで異なる挙動をさせながら、どこでも同じAPIインターフェースを使用できます。サーバーインスタンスは絶対URLとサーバーサイド認証を使用し、クライアントインスタンスは相対URLとブラウザストレージを使用するといった具合です。</p>
<h2>TypeScript：型安全にする</h2>
<p>ラッパーをさらに堅牢にするために、適切なTypeScriptサポートを追加しましょう。</p>
<div class="gatsby-highlight" data-language="ts"><pre style="counter-reset: linenumber NaN" class="language-ts line-numbers"><code class="language-ts"><span class="token comment">// lib/api.ts - TypeScriptインターフェースの追加</span>
<span class="token keyword">interface</span> <span class="token class-name">User</span> <span class="token punctuation">{</span>
  id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
  name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
  email<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">interface</span> <span class="token class-name">ApiEndpoints</span> <span class="token punctuation">{</span>
  <span class="token comment">// APIの形状を定義</span>
  <span class="token string-property property">'/users'</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token constant">GET</span><span class="token operator">:</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> User<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token constant">POST</span><span class="token operator">:</span> <span class="token punctuation">{</span> body<span class="token operator">:</span> Omit<span class="token operator">&lt;</span>User<span class="token punctuation">,</span> <span class="token string">'id'</span><span class="token operator">></span><span class="token punctuation">;</span> data<span class="token operator">:</span> User <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token string-property property">'/users/:id'</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token constant">GET</span><span class="token operator">:</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> User <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token constant">PUT</span><span class="token operator">:</span> <span class="token punctuation">{</span> body<span class="token operator">:</span> Partial<span class="token operator">&lt;</span>User<span class="token operator">></span><span class="token punctuation">;</span> data<span class="token operator">:</span> User <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token constant">DELETE</span><span class="token operator">:</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> success<span class="token operator">:</span> <span class="token builtin">boolean</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 型安全なラッパーメソッド</span>
<span class="token keyword">class</span> <span class="token class-name">TypedApiClient</span> <span class="token keyword">extends</span> <span class="token class-name">ApiClient</span> <span class="token punctuation">{</span>
  <span class="token keyword">async</span> <span class="token generic-function"><span class="token function">getTyped</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> <span class="token keyword">keyof</span> ApiEndpoints<span class="token punctuation">,</span> <span class="token constant">M</span> <span class="token keyword">extends</span> <span class="token keyword">keyof</span> ApiEndpoints<span class="token punctuation">[</span><span class="token constant">T</span><span class="token punctuation">]</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>
    endpoint<span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">,</span>
    method<span class="token operator">:</span> <span class="token constant">M</span> <span class="token keyword">extends</span> <span class="token string">'GET'</span> <span class="token operator">?</span> <span class="token string">'GET'</span> <span class="token operator">:</span> <span class="token builtin">never</span>
  <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>ApiEndpoints<span class="token punctuation">[</span><span class="token constant">T</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token constant">M</span><span class="token punctuation">]</span> <span class="token keyword">extends</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token keyword">infer</span> <span class="token constant">D</span> <span class="token punctuation">}</span> <span class="token operator">?</span> <span class="token constant">D</span> <span class="token operator">:</span> <span class="token builtin">never</span><span class="token operator">></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>endpoint <span class="token keyword">as</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> response<span class="token punctuation">.</span>data<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// POST、PUT、DELETEなどについても同様のメソッド...</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>これにより複雑さは増しますが、<strong>素晴らしい開発者体験</strong>を提供します。IDEがエンドポイントをオートコンプリートし、タイプミスを検知し、正しいペイロードの型を渡していることを保証してくれます。</p>
<h2>Next.jsコンポーネントでの使用方法</h2>
<p>では、実際のNext.jsのシナリオで、このラッパーがどのように輝くかを見てみましょう。</p>
<h3>サーバーコンポーネントでの使用例</h3>
<div class="gatsby-highlight" data-language="ts"><pre style="counter-reset: linenumber NaN" class="language-ts line-numbers"><code class="language-ts"><span class="token comment">// app/users/page.tsx - サーバーコンポーネント</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> serverApi <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/lib/api'</span><span class="token punctuation">;</span>

<span class="token keyword">interface</span> <span class="token class-name">User</span> <span class="token punctuation">{</span>
  id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
  name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
  email<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">UsersPage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> users <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> serverApi<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">get</span><span class="token generic class-name"><span class="token operator">&lt;</span>User<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token string">'/users'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">(</span>
      <span class="token operator">&lt;</span>div<span class="token operator">></span>
        <span class="token operator">&lt;</span>h1<span class="token operator">></span>Users<span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span>
        <span class="token punctuation">{</span>users<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>user <span class="token operator">=></span> <span class="token punctuation">(</span>
          <span class="token operator">&lt;</span>div key<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>id<span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>name<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
        <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>error <span class="token keyword">instanceof</span> <span class="token class-name">ApiError</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span>Error<span class="token operator">:</span> <span class="token punctuation">{</span>error<span class="token punctuation">.</span>message<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span>Something went wrong<span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h3>Reactフックを使用したクライアントコンポーネント</h3>
<div class="gatsby-highlight" data-language="ts"><pre style="counter-reset: linenumber NaN" class="language-ts line-numbers"><code class="language-ts"><span class="token comment">// components/UserProfile.tsx - クライアントコンポーネント</span>
<span class="token string">'use client'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token punctuation">{</span> useState<span class="token punctuation">,</span> useEffect <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> clientApi<span class="token punctuation">,</span> ApiError <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/lib/api'</span><span class="token punctuation">;</span>

<span class="token keyword">interface</span> <span class="token class-name">User</span> <span class="token punctuation">{</span>
  id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
  name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
  email<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">interface</span> <span class="token class-name">UserProfileProps</span> <span class="token punctuation">{</span>
  userId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">UserProfile</span><span class="token punctuation">(</span><span class="token punctuation">{</span> userId <span class="token punctuation">}</span><span class="token operator">:</span> UserProfileProps<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>user<span class="token punctuation">,</span> setUser<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token generic-function"><span class="token function">useState</span><span class="token generic class-name"><span class="token operator">&lt;</span>User <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>loading<span class="token punctuation">,</span> setLoading<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>error<span class="token punctuation">,</span> setError<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token generic-function"><span class="token function">useState</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token builtin">string</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">fetchUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token function">setLoading</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">const</span> <span class="token punctuation">{</span> data <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> clientApi<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">get</span><span class="token generic class-name"><span class="token operator">&lt;</span>User<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/users/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>userId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">setUser</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>err <span class="token keyword">instanceof</span> <span class="token class-name">ApiError</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token function">setError</span><span class="token punctuation">(</span>err<span class="token punctuation">.</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
          <span class="token function">setError</span><span class="token punctuation">(</span><span class="token string">'An unexpected error occurred'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
        <span class="token function">setLoading</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token function">fetchUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>userId<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span>loading<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span>Loading<span class="token operator">...</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span>Error<span class="token operator">:</span> <span class="token punctuation">{</span>error<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>user<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span>User not found<span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>div<span class="token operator">></span>
      <span class="token operator">&lt;</span>h2<span class="token operator">></span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>name<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>h2<span class="token operator">></span>
      <span class="token operator">&lt;</span>p<span class="token operator">></span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>email<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">></span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h3>ルートハンドラでの使用例</h3>
<div class="gatsby-highlight" data-language="ts"><pre style="counter-reset: linenumber NaN" class="language-ts line-numbers"><code class="language-ts"><span class="token comment">// app/api/users/route.ts - APIルートハンドラ</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> NextRequest <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'next/server'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> serverApi <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/lib/api'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token constant">GET</span><span class="token punctuation">(</span>request<span class="token operator">:</span> NextRequest<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token comment">// 外部APIにリクエストを転送</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span> data <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> serverApi<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/external-api/users'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> Response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>error <span class="token keyword">instanceof</span> <span class="token class-name">ApiError</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> Response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span>
        <span class="token punctuation">{</span> error<span class="token operator">:</span> error<span class="token punctuation">.</span>message <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">{</span> status<span class="token operator">:</span> error<span class="token punctuation">.</span>status <span class="token punctuation">}</span>
      <span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> Response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span>
      <span class="token punctuation">{</span> error<span class="token operator">:</span> <span class="token string">'Internal server error'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token number">500</span> <span class="token punctuation">}</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h2>高度な機能：キャッシュとリクエストの重複排除</h2>
<p>本番アプリケーションでは、キャッシュやリクエストの重複排除を追加したくなるかもしれません。</p>
<div class="gatsby-highlight" data-language="ts"><pre style="counter-reset: linenumber NaN" class="language-ts line-numbers"><code class="language-ts"><span class="token comment">// lib/api.ts - 高度な機能</span>
<span class="token keyword">class</span> <span class="token class-name">ApiClient</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> cache <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">;</span> timestamp<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span><span class="token operator">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> pendingRequests <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">>></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">async</span> <span class="token generic-function"><span class="token function">get</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>
    endpoint<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span>
    options<span class="token operator">?</span><span class="token operator">:</span> RequestInit <span class="token operator">&amp;</span> <span class="token punctuation">{</span> cache<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> cacheTTL<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span>
  <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>ApiResponse<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">>></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> cacheKey <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">GET:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>endpoint<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> now <span class="token operator">=</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// まずキャッシュをチェック</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>options<span class="token operator">?.</span>cache<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> cached <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>cache<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>cacheKey<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">const</span> ttl <span class="token operator">=</span> options<span class="token punctuation">.</span>cacheTTL <span class="token operator">||</span> <span class="token number">60000</span><span class="token punctuation">;</span> <span class="token comment">// デフォルトは1分</span>

      <span class="token keyword">if</span> <span class="token punctuation">(</span>cached <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>now <span class="token operator">-</span> cached<span class="token punctuation">.</span>timestamp<span class="token punctuation">)</span> <span class="token operator">&lt;</span> ttl<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> cached<span class="token punctuation">.</span>data<span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span> headers<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Headers</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// 同時リクエストを重複排除</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>pendingRequests<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>cacheKey<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>pendingRequests<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>cacheKey<span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">const</span> requestPromise <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token generic-function"><span class="token function">makeRequest</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>endpoint<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token operator">...</span>options<span class="token punctuation">,</span> method<span class="token operator">:</span> <span class="token string">'GET'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>pendingRequests<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>cacheKey<span class="token punctuation">,</span> requestPromise<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> requestPromise<span class="token punctuation">;</span>

      <span class="token comment">// 成功したレスポンスをキャッシュ</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>options<span class="token operator">?.</span>cache <span class="token operator">&amp;&amp;</span> response<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token number">200</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>cache<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>cacheKey<span class="token punctuation">,</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> response<span class="token punctuation">.</span>data<span class="token punctuation">,</span> timestamp<span class="token operator">:</span> now <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>

      <span class="token keyword">return</span> response<span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>pendingRequests<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span>cacheKey<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h2>エラーハンドリング戦略</h2>
<p>このラッパーの最大の利点の一つは、エラーハンドリングを一元化できることです。以下にその活用方法を示します。</p>
<div class="gatsby-highlight" data-language="ts"><pre style="counter-reset: linenumber NaN" class="language-ts line-numbers"><code class="language-ts"><span class="token comment">// lib/error-handler.ts</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ApiError <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./api'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">handleApiError</span><span class="token punctuation">(</span>error<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>error <span class="token keyword">instanceof</span> <span class="token class-name">ApiError</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">switch</span> <span class="token punctuation">(</span>error<span class="token punctuation">.</span>status<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">case</span> <span class="token number">401</span><span class="token operator">:</span>
        <span class="token comment">// ログインにリダイレクトするか、トークンを更新</span>
        <span class="token keyword">return</span> <span class="token string">'続行するにはログインしてください'</span><span class="token punctuation">;</span>
      <span class="token keyword">case</span> <span class="token number">403</span><span class="token operator">:</span>
        <span class="token keyword">return</span> <span class="token string">'この操作を実行する権限がありません'</span><span class="token punctuation">;</span>
      <span class="token keyword">case</span> <span class="token number">404</span><span class="token operator">:</span>
        <span class="token keyword">return</span> <span class="token string">'要求されたリソースが見つかりませんでした'</span><span class="token punctuation">;</span>
      <span class="token keyword">case</span> <span class="token number">500</span><span class="token operator">:</span>
        <span class="token keyword">return</span> <span class="token string">'サーバーエラーです。後でもう一度お試しください'</span><span class="token punctuation">;</span>
      <span class="token keyword">default</span><span class="token operator">:</span>
        <span class="token keyword">return</span> error<span class="token punctuation">.</span>message<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">return</span> <span class="token string">'予期しないエラーが発生しました'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// コンポーネントでの使用例</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> handleApiError <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/lib/error-handler'</span><span class="token punctuation">;</span>

<span class="token keyword">try</span> <span class="token punctuation">{</span>
  <span class="token keyword">await</span> api<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/protected-resource'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> errorMessage <span class="token operator">=</span> <span class="token function">handleApiError</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span>
  toast<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>errorMessage<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h2>ファイル構造と整理</h2>
<p>APIレイヤーを整理するため、以下のようなファイル構造をお勧めします。</p>
<div class="gatsby-highlight" data-language="text"><pre style="counter-reset: linenumber NaN" class="language-text line-numbers"><code class="language-text">lib/
├── api/
│   ├── index.ts          # メインのAPIクライアントとエクスポート
│   ├── types.ts          # TypeScriptインターフェース
│   ├── endpoints.ts      # エンドポイントの定義
│   └── error-handler.ts  # エラーハンドリングユーティリティ
├── hooks/
│   ├── useApi.ts         # カスタムReactフック
│   └── useAuth.ts        # 認証フック
└── utils/
    └── api-helpers.ts    # ユーティリティ関数</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>この構造により、APIレイヤーが整理され、チームメンバーが必要なものを簡単に見つけられるようになります。</p>
<h2>ベストプラクティスとよくある落とし穴</h2>
<h3>推奨事項：</h3>
<ul>
<li><strong>常にエラーを明示的に処理する</strong> - 捕捉されないまま放置しない</li>
<li>リクエスト/レスポンスの形状に<strong>TypeScriptインターフェースを使用する</strong></li>
<li>異なる環境やサービスごとに<strong>別のインスタンスを作成する</strong></li>
<li>コンポーネントに<strong>適切なローディング状態を実装する</strong></li>
<li>ネットワークリクエストを減らすため、必要に応じて<strong>レスポンスをキャッシュする</strong></li>
</ul>
<h3>非推奨事項：</h3>
<ul>
<li>本番アプリケーションで<strong>機密性の高いトークンをlocalStorageに保存しない</strong>（httpOnly Cookieを使用する）</li>
<li><strong>HTTPステータスコードを無視しない</strong> - 異なるステータスを適切に処理する</li>
<li><strong>レンダーメソッドでAPIコールを行わない</strong> - useEffectまたはサーバーコンポーネントを使用する</li>
<li><strong>リクエストタイムアウトを忘れない</strong> - ハングするリクエストを防ぐ</li>
<li><strong>URLをハードコードしない</strong> - 環境変数を使用する</li>
</ul>
<h3>よくある落とし穴：</h3>
<ol>
<li><strong>「私のマシンでは動くのに」という罠</strong>: 常に異なるNext.jsのコンテキスト（サーバー、クライアント、APIルート）でテストする。</li>
<li><strong>トークン更新地獄</strong>: 必要になる前に、適切なトークン更新ロジックを実装する。</li>
<li><strong>エラーバウンダリの軽視</strong>: APIを利用するコンポーネントをエラーバウンダリでラップする。</li>
</ol>
<h2>大局的に見る：なぜこれが重要なのか</h2>
<p>堅牢なfetchラッパーを構築することは、単にコードをクリーンにすることだけが目的ではありません。アプリケーションのための<strong>信頼性の高い基盤</strong>を築くことが重要です。APIレイヤーがしっかりしていると、以下のことが可能になります。</p>
<ul>
<li>ネットワーク問題のデバッグではなく、<strong>機能開発に集中できる</strong></li>
<li>リクエスト処理の一貫性が保たれているため、<strong>自信を持ってスケールできる</strong></li>
<li>明確で文書化されたAPIインターフェースにより、<strong>新しい開発者のオンボーディング</strong>が迅速になる</li>
<li>一元化されたエラーハンドリングと型付けにより、<strong>より良いコード品質を維持できる</strong></li>
</ul>
<p>これは未来の自分への投資だと考えてください。このラッパーの構築に費やす1時間は、本番環境の問題をデバッグしたり、反復的なエラー処理コードを書いたり、ネットワーク関連のバグを探したりする何十時間もの時間を節約してくれるでしょう。</p>
<h2>結論：APIレイヤーを競争上の優位性として</h2>
<p>私たちは、素の<code class="language-text">fetch</code>コールの質素な始まりから、認証、エラー、TypeScriptの安全性、そしてNext.js特有の実行コンテキストを処理する、洗練された本番環境対応のAPIクライアントへと旅をしてきました。しかし、重要なのは、これは単により良いコードを書くことだけではないということです。
今日の開発環境において、APIレイヤーの品質は、チームの開発速度とアプリケーションの信頼性に直接影響します。巧みに作られたfetchラッパーは、チームが迅速に動き、自信を持って製品を届けられるようにする、目に見えないインフラとなるのです。
私たちが探求してきたパターン（コンテキスト認識、適切なエラーハンドリング、TypeScriptの統合、そして考え抜かれたアーキテクチャ）は、単なる「あれば良いもの」ではありません。それらは、本番環境で壊れやすい脆弱なアプリケーションと、現実世界の事象を適切に処理する堅牢なシステムの分かれ目となるのです。</p>]]></content:encoded></item><item><title><![CDATA[2025年のReactとコミュニティの現状]]></title><description><![CDATA[Reactがこれまでどのように開発されてきたかについての詳細な考察と、コミュニティでよく見られる混乱や懸念事項についての説明 はじめに 今日、Reactとそのエコシステムの状況は複雑で分裂しており、…]]></description><link>https://postd.cc/react-community-2025/</link><guid isPermaLink="false">https://postd.cc/react-community-2025/</guid><category><![CDATA[開発手法・プロジェクト管理]]></category><category><![CDATA[React]]></category><category><![CDATA[OSS]]></category><category><![CDATA[オープンソース]]></category><pubDate>Thu, 28 Aug 2025 00:00:01 GMT</pubDate><content:encoded><![CDATA[<p>Reactがこれまでどのように開発されてきたかについての詳細な考察と、コミュニティでよく見られる混乱や懸念事項についての説明</p>
<h2>はじめに</h2>
<p>今日、Reactとそのエコシステムの状況は複雑で分裂しており、成功、懐疑、そして論争が入り混じっています。</p>
<p>ポジティブな面として、Reactは最も広く利用されているUIフレームワークであり、そのコンセプトはJSエコシステムの他の部分にも影響を与えてきました。Reactチームは数年にわたる開発の末、先日React 19をリリースしました。これは、公式に安定版となったReact Server Componentsのサポート、Promiseを扱うための新しい<code class="language-text">use</code>フック、複数の新しいフォーム連携、そして長らく非推奨であった多くの時代遅れな機能の削除を含む、大規模なリリースでした。</p>
<p>しかし、私が観察し、また経験してきたところでは、Reactコミュニティでは、Reactの向かう先やその開発方法、推奨される利用アプローチ、そしてReactチームとコミュニティ間の相互作用について、たくさんの意見が交わされています。これは、過去数年間にわたってReactおよびWeb開発コミュニティ全体で飛び交ってきた何十もの異なる議論や、Reactの動作に関する特定の技術的懸念、他の類似JSフレームワークとの比較、そして今後Webアプリをどのように構築すべきかといった問題と重なっています。</p>
<p>事態をさらに悪化させているのは、誰もがこれらの懸念の異なる一部分について議論や討論をしており、表明されている懸念の多くが誤りであるか、完全なFUD<em>であることです。残念ながら、これらの議論と恐怖は、Reactチームと開発者間の適切なコミュニケーションの不足などによって、さらに複雑化しています。これら全てが密接に絡み合っているのです。
</em>不安や懸念、不透明感</p>
<p>私はソーシャルメディア上で関連する多くの議論に参加してきました。Reactチームを擁護し、また批判するコメントを数多く書き、彼らが特定の決定を下した理由や物事の実際の経緯を説明し、ドキュメントや説明の改善を求め、その他多くの活動を行ってきました。
これらのトピックのほんの一部をカバーしようとするだけでも、広範で詳細な記事が必要となります（そして私は、「Reactは悪いのか/Reactは他のツールと比べてどうなのか？」という、さらにスコープ外のトピックには触れるつもりすらありません）。しかし、あまりにも多くの時間を討論と説明に費やしてきた今、これらのトピックの多くをまとめて扱う統合的な記事を書く必要性を感じています。
また、私は過去に<a href="https://blog.isquaredsoftware.com/2025/06/presentations-react-community-2025/">この記事のトピックに基づいた講演</a>も行っています。</p>
<h2>この記事の目的</h2>
<p>この記事における私の目的は以下の通りです。</p>
<ul>
<li>Reactが時間と共にどのように発展してきたかの概要を説明する</li>
<li>Reactの開発を推進する力を説明する</li>
<li>Reactの最近の開発の方向性がなぜ、どのようにして起こったのかを明確にし、その作業の背景にあるアーキテクチャ上の決定のいくつかを説明する</li>
<li>Reactの開発の背後にある動機と意図に関するFUDと混乱を明確にし、払拭する</li>
</ul>
<h2>背景：私のReactコミュニティにおける役割と関与</h2>
<p>（もしくは「私は何者で、なぜ私の説明を信頼すべきで、そしてなぜあなたはこの件に関する私の意見を気にかけるべきなのか？ 😄」）</p>
<p>私は実際のReactチームの一員ではありませんし、これまでもそうであったことはありません。とはいえ、私は2015年からReactエコシステムに深く関わっており、MetaやVercelの外部の人間としては、誰よりも「Reactコミュニティのインナーサークル」の一員であると言って差し支えないでしょう。</p>
<p>要約すると、以下の通りです。</p>
<ul>
<li>2015年からReactを使用</li>
<li>Reduxライブラリファミリーのメンテナーであり、Reactにコントリビュートし、Reactエコシステムの議論に関与</li>
<li>ReactとReduxに関する多数の詳細なチュートリアルを執筆し、ReactとReduxについて頻繁に講演</li>
<li>そのほとんどの期間、主要なReactコミュニティセクションのモデレーターとして関与</li>
</ul>
<p>もし詳細を知りたければ：</p>
<details>
<summary>私の実績、関与、経歴</summary>
<p>私は2015年半ばにReactを学び始めました。それ以前は、Backbone、jQuery、GWTを使って地理空間情報の可視化アプリをいくつか構築していました。それから1年の間に、私は<a href="https://blog.isquaredsoftware.com/2016/09/how-i-got-here-my-journey-into-the-world-of-redux-and-open-source/">いくつかのReactチュートリアルを読むところから、Reactifluxのチャットチャンネルを眺めているうちに、非常に多くの質問に答えるようになってモデレーターになり、ReduxのFAQを自ら執筆し、そして新しいReduxのメンテナーとしてその役割を引き継ぐことになりました</a>。</p>
<p>私は2016年半ばからReduxをメンテナンスしています。現代的なReduxアプリの記述方法として<a href="https://blog.isquaredsoftware.com/2019/10/redux-toolkit-1.0/">Redux Toolkitを作成</a>しました。Reduxに取り組む中で、v5以降のReact-Reduxの全てのバージョンを設計し、リリースしてきました。Reactが外部の状態管理ライブラリとどのように連携すべきかについてReactチームと何度も話し合い、<a href="https://github.com/reduxjs/react-redux/pull/1808">useSyncExternalStoreフックの開発においては、私が唯一のアルファテスターでした</a>。</p>
<p>私は、<a href="https://redux.js.org/tutorials/essentials/part-1-overview-concepts">「Redux Essentials」チュートリアル</a>や、広く称賛されている<a href="https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/">「Reactのレンダリングビヘイビアへのガイド」の記事</a>（この記事は、Reactが実際にどのように動作するかの最良の説明として、コミュニティで常に推奨されています）をはじめ、人々がReactとReduxをどのように使い、それらがどのように動作するかを教えるために、何十万語ものチュートリアルやブログ記事を執筆してきました。私は、Github、Reactiflux Discord、Reddit、Twitter、Hacker News、Stack Overflowにわたって、React、Redux、JSのあらゆることについて、何千ものコメントや質問に回答してきました。<a href="https://blog.isquaredsoftware.com/2025/06/react-community-2025/#:~:text=%22the%20tech%20support%20for%20the%20React%20community%22%20and%20%22the%20master%20archiver%20and%20indexer%20of%20all%20things%20React%22">「Reactコミュニティのテクニカルサポート」、そして「Reactに関する全ての事柄のマスターアーカイバー兼インデクサー」</a>と呼ばれてきました。</p>
<p>私は<a href="https://blog.isquaredsoftware.com/presentations">数多くのReact関連カンファレンス</a>で、<a href="https://blog.isquaredsoftware.com/2023/08/presentations-react-rendering-behavior/">Reactの仕組み</a>から<a href="https://blog.isquaredsoftware.com/2024/11/presentations-maintaining-community/">広く使われているOSSライブラリをメンテナンスする上でのベストプラクティス</a>や教訓に至るまで、<a href="https://blog.isquaredsoftware.com/presentations/">さまざまなトピックについて登壇</a>してきました。<a href="https://www.reactiflux.com/transcripts/tmir-2024-12">Reactiflux Discordが主催する現在も続くポッドキャスト『This Month in React』</a>（このポッドキャストでは、Reactとコミュニティに関する最新のニュース、リリース、知見について話しています）をはじめ、数多くのポッドキャストでReactとReduxについて議論してきました。</p>
<p>私は、React Hooksの初期プライベートプレビューのフィードバックグループや、招待制でありながら公開されていたReact 18ワーキンググループの一員であり、両方のリリースについて、設計、ドキュメント、利用方法の側面でフィードバックを提供しました。</p>
<p>私は<a href="https://www.reactiflux.com/">Reactiflux Discordの管理者</a>であり、数年間にわたり<a href="https://reddit.com/r/reactjs/">the/r/reactjs subreddit</a>の主要な現役モデレーターを務めています。</p>
<p>私は、Reactのリポジトリや内部のコードを深く掘り下げてきました。例えば、<a href="https://github.com/facebook/react/pull/26446">Reactのプロダクションビルド用のソースマップを生成するPR</a>を提出したり、<a href="https://blog.isquaredsoftware.com/2023/10/presentations-react-devtools-replay/">タイムトラベルデバッグを用いてReact DevToolsをReplayのDevToolsに統合</a>したりしました。</p>
</details>
<p>これらの実績を挙げたのは、私がReactの開発方法、Reactコミュニティで何が起こっているか、Reactチームの内部で何が起こっているか、そして人々がReactとReduxを学ぶのを助けることに関して、長い歴史と専門知識を持っていることを示すためです。</p>
<h2>バイアスと注意点</h2>
<p>ここはインターネットなので、私がこれを書くことに対して皆さんが抱くかもしれないいくつかの懸念を先取りしておこうと思います。</p>
<p>私は間違いなく常に正しいわけではありませんし、知識の限界、誤解、あるいは要約のしすぎによって、いくつかの事柄を誤って説明してしまうことは間違いありません。とはいえ、私は歴史、動機、行動をできる限り正直かつ正確に記述するための最善の努力としてこれを書いています。</p>
<p>さらに詳しくは：</p>
<details>
<summary>より詳細なバイアスと注意点</summary>
<p>私がReduxのメンテナーであることは、Reactの使われ方や、私が普段扱う問題の種類に対する私自身の見方に、偏りをもたらしています。</p>
<p>私は2015年から一貫して何らかの形でReactを使い続けてきましたが、私自身のReactを使った経験は、時間と共により限定的でニッチなものになってきました。私が携わったReactアプリのほとんどは、配布先が限定された社内ツールであり、その大半は「ブラウザ上のデスクトップ風アプリ」、つまりルーティングやCRUDのような挙動すらない、真のSPAでした。CRUD/ダッシュボード形式のアプリに1つ携わったことはありますが、その範囲は限定的でした。巨大なオーディエンスを持つアプリを構築したことはなく、SSRやRSCを実際に使ったこともなく、国際化（i18n）やその他の大規模なスケーリングに関する懸念に頭を悩ませる必要もありませんでした。私は時間の多くをライブラリや開発者ツールの作業に費やしており、「典型的な」Reactアプリの日々の開発に費やす時間ははるかに少ないのです。</p>
<p>私は、平均的なReactアプリ開発者が決して耳にしたり気にしたりすることのない、Reactコミュニティの専門的で内輪な議論を読んだり、参加したりしてきました。</p>
<p>私はReactチームとオンラインおよび対面で個人的なやり取りをしてきましたが、それらは非常にポジティブなものもあれば、非常にネガティブで不満のたまるものもありました。これらは私自身の視点を色づけています。しかし、コミュニティの他のメンバーとも十分に議論を重ねてきたので、他の多くの人々も私の懸念や意見を共有していると確信しています。</p>
<p>私の通常の執筆スタイルは、できるだけ多くの参照や情報源へリンクを張ることです。この記事ではそれを試みますが、これらの歴史的な記述や見解の多くは、特定の情報源を見つけるのが難しいでしょう。ですから、要約されているとしても、私がそれらを正確に記述するために最善を尽くしていることを信じてください。</p>
</details>
<p>さて、これらの注意点を全て述べたところで、本題に入りましょう。</p>
<h2>Reactの簡単な歴史</h2>
<p>初期の歴史は、<a href="https://www.youtube.com/watch?v=8pDqJVdNa44">Reactドキュメンタリー</a>など他の場所でより詳しく語られていますが、この記事の残りの部分の文脈を提供するために少し触れておきます。</p>
<p>Reactは2011年から12年頃にFacebook内部で開発され、2013年にオープンソース化されました。外部のコントリビューターも少数集まりましたが、最近まで実際の開発は全てFacebook/Meta内部のReactコアチームによって行われていました。</p>
<p>Reactの核となるコンセプト（コンポーネント、props、state、データフロー）は常に同じですが、実装の詳細、公開API、スコープの広さは時間と共に変化してきました。Reactは<code class="language-text">createClass</code>でコンポーネントを作成する方式から、ES6の<code class="language-text">class MyComponent extends React.Component</code>へ、そして<code class="language-text">function MyComponent()</code>へと移行しました。ReactはWeb専用からReact Nativeでモバイルをサポートするように分離し、その後、カスタマイズ可能な<code class="language-text">react-reconciler</code>コアを介してWebGL（<code class="language-text">react-three-fiber</code>）やCLI（<code class="language-text">ink</code>）など他のプラットフォームにも対応可能になりました。内部は新しい「Fiber」アーキテクチャに完全に書き直され、さらなるアーキテクチャの変更が可能になりました。2018年のフックのリリースにより、関数コンポーネントはstateと副作用を持つ能力を得ました。</p>
<p>長年、ReactはUIに特化した最小限のレンダリングライブラリとして自らを位置づけていました（「MVCのV」、「ユーザーインタフェースを構築するためのJSライブラリ」）。AngularやEmberのような他のフレームワークは完全に「全部入り（batteries included）」で、アプリの構築方法や構造について強い意見を持っていましたが、Reactチームはそれら全てをコミュニティに委ねていました。これは、良くも悪くもエコシステムの大爆発につながりました。考えられるあらゆるライブラリカテゴリで、重複する問題を解決する何十もの競合ツールが存在しました。状態管理（Redux、Mobx、Zustand...）、CSSとスタイル（Styled-Components、Emotion、CSS Modules...）、データフェッチ（React Query、Apollo、SWR、RTK Query...）、ビルドツール（Babel、Webpack、ESBuild、Vite、Parcel...）、その他数百です。</p>
<p>Reactユーザーは常に、プロジェクトを開始する際に、アプリのさまざまなユースケースを解決するための一連のライブラリを選び出す必要がありました。さらに、Reactは多くの方法で使用できました。シングルページアプリ（SPA）でクライアントサイドで全ての要素をレンダリングする、サーバーレンダリングされたページに複数のサブツリーをアタッチする、マルチページアプリ（MPA）の一部としてサーバー上で使用するなど、多岐にわたります。エコシステムのオプションの柔軟性と多様性は、強みであると同時に弱みでもありました。プロジェクトに必要なツールの正確な組み合わせを選ぶことができる一方で、ツールの組み合わせを選ばなければならないのです。それは決定疲れ、プロジェクトのコードベースのばらつき、そして一般的に使用されるツールの絶え間ない変化につながります。</p>
<p>全体として、ReactライブラリとReactチームの両方が意図的に意見を持たない（unopinionated）姿勢を保っていました。彼らはエコシステム内の特定のツールをえこひいきしたくなく、彼らの時間と注意はReact自体の構築に集中しており、Reactのスコープをある程度狭いものと見なしていました。</p>
<p>そのエコシステムの多様性は柔軟性をもたらしましたが、同時に複雑さも生み出しました。これは特にビルドツールで顕著でした。初期のReactチュートリアルでは、最初のReactコンポーネントを書く前に、「WebpackとBabelの設定方法」を説明するために何ページも費やすことがよくありました。このためReactチームは、Create React App（CRA）と呼ばれるパッケージ化済みのビルドツール設定を構築することになりました。CRAは、テンプレートから新しいReactプロジェクトを生成できるCLIツールであり、非常に複雑でカスタマイズされたWebpack + Babel設定で構築されていました。これは、単一のコマンドで新しいプロジェクトを簡単に始められるように設計されていました。その設定は、複雑さを隠すためにブラックボックスとしてカプセル化されていました。</p>
<p>Reactにはデータフェッチの組み込みメソッドがなかったため、データをフェッチしてキャッシュするためにサードパーティのライブラリを使用するのが標準でした。Reactには早くからサーバーレンダリング（SSR）機能があり、ReactコンポーネントツリーをHTML文字列やストリームとしてレンダリングするメソッドがありましたが、これらはサーバーハンドラで手動で使用する必要がありました。時が経つにつれて、他のパッケージ済みのReactビルドシステムや「フレームワーク」が人気を博しました。Next.jsもWebpackをラップしましたが、ファイルシステムベースのルーティング規約による組み込みのサーバーレンダリングやページベースのデータフェッチなど、より多くの機能を追加しました。GatsbyはGraphQLベースのデータソースからサイトを生成しました。後に、Remixはサーバーの「データローダー」を追加し、よりHTML/プラットフォームベースの使用規約を推進しました。</p>
<p>2020年後半、Reactチームは「React Server Components」のプロトタイプを発表しました。これは、非同期Reactコンポーネントがサーバー上で実行されてデータをフェッチし、その後子コンポーネントをレンダリングして子とデータをクライアントで実行されているReactに渡すことを可能にするアーキテクチャ的アプローチでした。これはデータフェッチに対するReactらしい解決策として意図されており、Reactの当初の「クライアント上のビューだけ」という歴史的な売り文句からの大きな転換を示しました。RSCの開発中に、Reactコアチームのメンバーの一部がMetaを去り、Next.jsの開発元であるVercelに参加しました。彼らは初期のRSC実装の構築を続け、Next開発チームと協力して新しい「App Router」でNextを再設計しました。これはRSCの最初の本番実装でした。</p>
<p>Reactチームはまた、2020年から2023年にかけてReactのドキュメントサイトをゼロから完全に書き直しました。<a href="https://react.dev/">react.dev</a>にある新しいReactドキュメントには、Reactのコンセプトを段階的に解説する素晴らしいチュートリアルがあり、実行可能なサンドボックス内に多数のページ内サンプルが含まれているほか、APIリファレンスページも大幅に改善されています。</p>
<p>新しいドキュメントが公開されたとき、ReactチームはReactの推奨される使用方法を大きく転換しました。以前は、古いReactドキュメントは、<a href="https://legacy.reactjs.org/docs/create-a-new-react-app.html#recommended-toolchains">学習中またはクライアントサイドのSPAを構築している場合にはCRAから始める</a>ことを推奨し、SSRや静的サイトが必要な場合にはNextやGatsbyのような他のいくつかのフレームワークを指し示していました。新しいドキュメントで、Reactチームはアプローチを変更し、「Reactアプリを書くためにはフレームワークを使うべきだ - それらにはルーティング、データフェッチ、ビルド機能が組み込まれている」と明確かつ強く推奨し始めました。これはRSCを構築する作業にも関連していました。この一環として、<a href="https://web.archive.org/web/20250125034212/https://react.dev/learn/start-a-new-react-project">「新しいReactプロジェクトを始める」ページ</a>では、フレームワークなしでReactを使用することに対して明確に警告していました。Nextはそのドキュメントページで目立つように記載されていました - 「フレームワーク」リストの最初に（Pagesルーターについて）順序付けられ、さらに下で唯一利用可能なRSC実装として言及されていました。Vercelで働くReactチームのメンバーは、<a href="https://x.com/acdlite/status/1549853625673023488">「来るべきNext.jsのリリースを『本当の』React 18のリリースだと考えている」</a>と述べたと引用されています。</p>
<p>これは、Create React Appが2022年頃に事実上非推奨になったことと関連していました。それ以前からしばらくメンテナンスされていませんでした。「CRAを非推奨としてマークし、Viteを推奨する」というPRが提出されたとき、Dan AbramovはCRAが作成された理由、<a href="https://github.com/reactjs/react.dev/pull/5487#issuecomment-1409720741">CRAの問題点、フレームワークの台頭、そしてReactのビジョンの転換について</a>詳細なコメントを書きました。これに応えて、Reactコミュニティは一斉にCRAから移行し、ドキュメントもそれを推奨するのをやめましたが、公式に「非推奨」とマークするための措置は何も取られませんでした。</p>
<h2>Reactとそのオーナーとの関係</h2>
<p>今日、Reactの開発作業は2つの企業によって後援され、所有されています。Meta（旧Facebook）とVercelです。</p>
<h3>Facebook / Meta</h3>
<p>当初から、ReactはFacebook/Metaが所有するプロジェクトでした。コードはオープンソース化され、誰でもPRを提出できましたが、本質的に開発作業は全てMetaのReactチームによって行われていました。</p>
<p>これはReactの開発方法に大きな影響を与えました。Reactコアチームの会議は通常内部で行われ、ロードマップも同様でした。Reactチームはしばしば問題領域に対して半ダースほどのアイデアをプロトタイプし、それをMetaのアプリチームに試してもらって検証しました。新しいReact機能が発表される頃には、それは多くの内部イテレーションを経て、実際の使用で検証されていました。同時に、ReactチームはMetaのアプリチームのバグ修正やその他のサポートにも責任を負ってきました。</p>
<p>それにもかかわらず、Reactチームはライブラリの開発方法においてかなり自由な裁量を持ってきました。ReactはFacebook内部でFacebookのために作られましたが、Reactチーム自身がライブラリの動作方法に関するロードマップと長期ビジョンを決定してきました。ほとんどの場合、Reactの実際の開発プロセスとロードマップは、Facebook/Meta内部の他の影響力によってではなく、彼ら自身によって推進されてきました。とはいえ、彼らは自分たちの業績とプロジェクトがMetaにどのように利益をもたらすかを正当化する必要もあります。</p>
<p>Meta自身のReactの使用は広範であり、コミュニティの使用方法とは異なります。Metaは独自の巨大なサーバーインフラを持っており、データフェッチ、ルーティング、セキュリティなどのための標準的な技術を含んでいます。MetaはGraphQLプロトコル、およびRelay GraphQLクライアントレイヤーを発明し、それらをReactコードで頻繁に使用しています。これは、MetaのReact使用が、ルーティング、状態管理、スタイリング、またはコミュニティの他の部分で一般的な他の問題に対して、サードパーティのライブラリをほとんど必要としないことを意味します。</p>
<h3>Vercel, Next, and React</h3>
<p>Vercelは主にWebアプリのホスティングプラットフォームです。彼らは主に、より多くの人々が彼らのプラットフォームでアプリをホストすることで収益を上げ、より簡単に使用できるという利便性をもたらすことで課金を促しています。彼らはNext.jsフレームワークの構築（および他のフレームワークのサポート）に膨大なエンジニアリング時間を費やし、Vercelインフラ上でNextアプリを簡単にセットアップしてデプロイできるようにしています。</p>
<p>VercelのCEOであるGuillermo Rauchは、Reactとその能力の長年の信奉者であり、それは彼の2015年のブログ投稿<a href="https://rauchg.com/2015/pure-ui">Pure UI</a>がReactのレンダリングモデルの力について語っていることからも示されています。</p>
<p>2021年後半、<a href="https://vercel.com/blog/supporting-the-future-of-react">ReactチームのリードであったSebastian MarkbageがMetaを去り、Vercelに参加しました</a>。これは、フルタイムのReactコアチームメンバーがMeta以外のどこかで働く初めての事例でした。後に、コアチームメンバーのAndrew Clarkと元React組織リードのTom Occhinoが彼に加わりました。ReactチームはすでにReact内部でRSC機能に関する重要なプロトタイピングを行っており、Next.jsのApp Routerを設計していました。そしてVercelは追加のエンジニアをReactのコアとサーバーレンダリング機能に貢献させ始めました。</p>
<p>今日、<a href="https://react.dev/community/team">ReactチームはMetaとVercelの間で分かれています</a>（大多数はまだMetaにいます）、加えて外部に数人のチームメンバーまたは継続的なコントリビューターがいます。</p>
<h2>Reactの利用パターン</h2>
<h3>標準的なReactのアーキテクチャ</h3>
<p>React（特にReactDOM）ライブラリ自体は、ページ内でどのように使用されるかを気にしません。ほぼ空のHTMLプレースホルダーを提供し、クライアントサイドでページ全体のコンテンツを生成するReactツリーをレンダリングして、シングルページアプリ（「SPA」）として機能させることができます。サーバーが各リクエストに動的に応答するサーバーレンダリング（「SSR」）や、ビルド時に静的なHTMLページを事前に生成する静的サイトジェネレーション（「SSG」）にReactを使用することもできます。任意の言語やフレームワーク（Python + Django、Ruby + Rails、PHP + WordPress、.NETなど）を使用して静的またはサーバーレンダリングされたHTMLページを提供し、ページ全体にReactの断片を散りばめてインタラクティビティを追加することもできます。</p>
<p>とはいえ、2015年までにはReactはクライアントサイドのSPAアーキテクチャで最も一般的に使用されるようになっていました。何事にもトレードオフがあるように、これらにも長所と短所がありました。ページコンテンツの生成が容易になり（全てReactコンポーネント）、ユーザーのインタラクションが高速化し（ページ全体のリフレッシュの代わりにクリックやルート変更で別のコンポーネントを表示）、よりリッチなアプリ体験が可能になりました。バックエンドが何であれ（JS、Java、PHP、.NET、Python）、JSON APIを公開してデータをフェッチするだけでした。しかし、最初のページのバンドルをロードするのが遅く、クライアントサイドのルーティングがネイティブのブラウザの動作とは異なる不自然なインタラクションにつながる可能性もありました。</p>
<p>これらのクライアントでのデータフェッチは、当初はかなり手動であり、副作用における慎重なロジックを必要としました（例えば、コンポーネントが最初にレンダリングされたときにデータをリクエストするために<code class="language-text">componentDidMount</code>でフェッチをトリガーするなど）。これはしばしば、フェッチとキャッシングを処理するためにReduxベースのロジックで行われましたが、そのコードは通常、ボイラープレートと複雑さに満ちていました。後に、React Query、Apollo、SWR、RTK Queryのような専用のデータフェッチライブラリがクライアントでのデータフェッチを大幅に簡素化し、専用の<code class="language-text">useQuery</code>フックと事前構築されたキャッシングメカニズムを提供しました。</p>
<p>NextやRemixのようなフレームワークは、Reactをサーバーレンダリングするための標準化されたアプローチを提供し、組み込みのファイルシステムルーティング規約を備えていました。しかし、サーバーベースのデータフェッチに関する規約はありませんでした。Nextは、コンポーネントと並行してフェッチするための非同期関数を指定する<code class="language-text">getServerSideProps</code>メカニズムを発明しました。Remixは後に同様のアーキテクチャで「ローダー」を発明しました。どちらもReactの思想に合っていない感じがしました。</p>
<p>これは、Reactエコシステムにおける一般的な考え方の転換につながりました。ページの読み込み体験を改善し、ページに必要なJSの量を最小限に抑えるために、SSRベースのアーキテクチャへの推進が強まっています。また、クライアントサイドでデータフェッチライブラリを使用する必要性をなくす動きもあります。Reactチームは、ページの読み込みパフォーマンスを改善するためにデータフェッチにおける「ウォーターフォール」に声高に反対しており、React RouterやTanStack Routerのようなクライアントサイドのルーターでさえ、コンポーネントツリーの奥深くでフェッチをトリガーするのではなく、ルート/ページレベルでデータをプリフェッチする方法を提供しています。</p>
<h2>Reactビルドツールの利用状況</h2>
<p>いくつかの主要なReact関連のビルドツールとフレームワークのダウンロード統計を見て、エコシステムにおける使用規模の経時的な変化と現状を把握する価値があります。
大まかに言うと、今日の主要な選択肢は、マインドシェアおよび／またはダウンロード数の観点から以下の通りです。</p>
<ul>
<li><a href="https://nextjs.org/">Next.js</a> (SSR / SSG / RSC / SPA)</li>
<li><a href="https://remix.run/">Remix</a> / React-Router v7 (SSR / SSG / SPA)</li>
<li><a href="https://vite.dev/">Vite</a> (SPA)</li>
<li><a href="https://create-react-app.dev/">Create React App</a> (SPA)</li>
</ul>
<p>また、Vite自体はVueエコシステムから始まったものの、多くの異なるクライアントサイドフレームワークをサポートする標準化された広く使用されるビルドツールになったことにも注目する価値があります。また、Reactサポート用のプラグインを含むプラグインエコシステムもあり、さまざまなフレームワークのビルドツールとして選ばれるようになっています。これにはRemix / React-Routerも含まれており、最近、内部でESBuildを使用するのをやめ、SSRとSSGのサポートを有効にするためのViteプラグインを提供するように切り替えました。</p>
<p>以下は、<a href="https://npm-stat.com/charts.html?package=react-dom&#x26;package=next&#x26;package=%40vitejs%2Fplugin-react&#x26;package=%40remix-run%2Freact&#x26;package=react-scripts&#x26;from=2018-01-01&#x26;to=2025-05-31">過去数年間のこれらのツールのNPMダウンロード数のグラフ</a>です。ReactDOMをスケールとして示しています。</p>
<p><img src="https://blog.isquaredsoftware.com/images/2025-06-react-community-2025/react-frameworks-usage.png"></p>
<p>最近まで、ReactドキュメントはGatsbyをWeb向けの推奨フレームワークとして、ExpoをReact Native向けの唯一の推奨フレームワークとしてリストアップしていました。GatsbyはNetlifyに買収された後、事実上その役目を終えました。一方、AstroはReactを含む多くのフレームワークをサポートする、より汎用的な静的サイト指向のツールです。比較のために彼らのダウンロード統計を以下に示します。</p>
<p><img src="https://blog.isquaredsoftware.com/images/2025-06-react-community-2025/react-frameworks-usage-2.png"></p>
<h3>これらの統計からのいくつかの要点：</h3>
<ul>
<li>
<p>Next.jsが最も広く使用されている</p>
</li>
<li>
<p>ViteのReactプラグインは着実に成長しており、現在では2番目に広く使用されている</p>
</li>
<li>
<p>React関連のビルドツールである</p>
</li>
<li>
<p>CRAの使用は2023年半ばにピークに達し、それ以降減少しているが、依然としてかなりの使用量がある</p>
</li>
<li>
<p>Remixは現時点で口コミでの評価は高いものの、規模は比較的小さめです。React Routerは非常に広く使われていますが、「フレームワークモード」に対応した新しいバージョンは、まだそれほど普及していません。</p>
</li>
<li>
<p>GatsbyはNext、CRA、Viteほどの勢いはなく、最近Astroに追い越された</p>
</li>
<li>
<p>AstroはReact固有ではないが、Remixとほぼ同じダウンロード数がある</p>
</li>
<li>
<p>ViteとCRAを合わせるとNextの使用量に匹敵し、Reactエコシステムには依然としてプレーンなSPAプロジェクトに対する強い需要があることを示している</p>
</li>
</ul>
<h2>React Server Componentsの内側</h2>
<h3>React Server Componentの開発とVercel</h3>
<p>Reactチームは、Metaのインフラストラクチャからのサーバーベースのデータフェッチや、JSバンドルの自動分割とコードの遅延読み込みの経験がありました。しかし、以前のReactの機能開発とは異なり、Meta内部でRSCをイテレーションして出荷するための選択肢は限られていました。Metaの既存のサーバーインフラと技術は、コミュニティの他の人々が実際に使用して恩恵を受けられるような方法でRSCを完全に設計することを困難にしていました。</p>
<p>注目すべきは、React Server Componentsは、将来Reactアプリを記述する方法に関するReactチームのビジョンであったことです。私の知る限り、これはMetaやVercelが考案したり、Reactチームに構築を強要したりしたものではありません。</p>
<p>長年にわたり、「Reactの開発は全てMetaの従業員によって行われている」という断続的な議論や不満があり、「『React財団』のようなものや、Metaの外部の人々が直接Reactに取り組んでいればいいのに」というコメントが時折ありました。関係する実際の議論は知りませんが、外部からの私の理解では、ReactチームがVercelにアプローチし、RSCのビジョンを提案し、VercelがRSCが開発される新しい実験場となることに同意した、ということです。これにより、Sebastian Markbage（そして後にAndrew Clark）がMetaからVercelに移り、NextチームはRSCの最初の実用的な実装としてNext App Routerを設計・構築するために膨大な時間、資金、エンジニアリングの労力を費やしました。
<a href="https://www.reddit.com/r/reactjs/comments/1kg3yqh/rsc_for_astro_developers_overreacted/mr2bl62/">Dan Abramovが説明した</a>ように：</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p>Vercelチームは、Reactチームが望んでいたことを実装するために、何人ものエンジニアを何年も稼働させてきました。また、Reactチームから技術的な方向性を直接受け入れたことも事実です。Next.jsは多かれ少なかれゼロから書き直されました。
現在Next.jsを設計している人物は、Reactフックを発明した人物です。ですから、どちらかと言えば、ReactチームがNext.jsの方向性を「引き継いだ」ケースであり、「ひいきにした」わけではありません。</p></div></div>
<p>そのプロセスに他のフレームワークや企業を関与させようとする努力はありました。ShopifyのHydrogenフレームワークはRSCの非常に初期のテストとフィードバックを行いましたが、最終的に彼らにとっては合わないと結論付けました。Remixチームは何度か関与を打診されましたが、当初は独自のアプローチに集中することを選択しました。</p>
<p>その結果、Nextは最初の（そして事実上今でも唯一の）「本番環境対応」のRSC実装となりました。今日、他のいくつかのフレームワーク（Parcel、React Router、Wakuを含む）がRSC統合に取り組んでいますが、NextのApp Routerが現在RSCを使用するための唯一の広範な選択肢です。</p>
<h3>RSC、フレームワーク、バンドラ</h3>
<p>「なぜRSCはバンドラやフレームワークを必要とするのですか？なぜReactに組み込むだけではだめなのですか？」といった質問をよく目にします。</p>
<p>Reactの既存のサーバーレンダリングメソッド、例えば<code class="language-text">renderToString</code>や<code class="language-text">renderToPipeableStream</code>は、Expressのルートハンドラなど、どこでも呼び出すことができました。しかし、アーキテクチャ的にRSCはずっと複雑です。RSCの機能は、<code class="language-text">'use client'</code>と<code class="language-text">'use server'</code>ディレクティブを探すためにコードを解析し、そのコードを変換してRSCのコア機能にクライアントコンポーネントとサーバー関数を登録するための適切な呼び出しを挿入する必要があります。これにはバンドラとの緊密な統合が必要で、それによってサーバーとクライアントの両方の全てのモジュールグラフを正しく決定し、適切にコンパイルすることができます。</p>
<p>さらに、Reactコアが実際のRSC機能を実装し、シリアライズされたコンポーネントとデータをクライアントとサーバー間で送信するためのプリミティブを提供しますが、それらのプリミティブを実際に呼び出し、適切な場所で使用するのはフレームワーク次第です。これは主にルーターとの統合に関わることで、クライアントアプリが適切な遅延読み込みされたクライアントコンポーネントを受け取り、アクションとデータを含む適切なエンドポイントを呼び出すようにするためです。</p>
<p>これはまた、各フレームワークのRSCの使用と実装が異なることを意味します。NextはApp Routerでレイアウトとルーティングを処理するために非常に具体的な実装決定を行いました。Waku、Parcel、React Routerのような他のフレームワークやビルドツールは、すでにいくつかの非常に異なる設計決定を行っています。</p>
<p>全体として、RSCは珍しいハイブリッド機能です。主要な機能はReactコアパッケージに直接組み込まれていますが、その機能はバンドラ／ルーター／フレームワークの何らかの組み合わせに統合されるまで使用できません。</p>
<h2>コミュニティの懸念と混乱</h2>
<p>これら全ての歴史と文脈を念頭に置いて、頻繁に繰り返されるいくつかの懸念事項（混乱しているか、FUDであるか、あるいは完全な陰謀論であるか）に答えていきましょう（そして、うまくいけば明確にし、払拭していきたいです）。</p>
<h3>懸念：Vercel、Next、そしてReact</h3>
<p>「VercelがReactの開発を主導しており、その目的はサイトをホスティングしてより多くの収益を上げることだ」という見解が、今や非常に一般的になっています。一例として、これは今年の初めにあったRedditのスレッドで最も多く支持されたコメントです：</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p>"Vercelは事実上Reactを乗っ取り、ユーザーをNextJSに誘導し、Vercelでデプロイさせることで、Vercelの株主をより豊かにすることに主な関心を持っている。" <a href="https://www.reddit.com/r/react/comments/1iarj85/xbluesky_react_recently_feels_biased_against_vite/m9cb51h/">https://www.reddit.com/r/react/comments/1iarj85/xbluesky_react_recently_feels_biased_against_vite/m9cb51h/</a></p></div></div>
<p>これは、他のいくつかの関連する懸念点と結びついています：</p>
<ul>
<li>NextはReactのドキュメントで最初に推奨されており、Next App Routerも「Reactチームのフルスタックアーキテクチャビジョンを構成する機能は何か？」の下で主な例として言及されている</li>
<li>Nextは依然としてRSCの唯一の本番実装である</li>
<li>Reactチームのメンバーは<a href="https://x.com/acdlite/status/1549853625673023488">「このNextのリリースが本当のReact 18だ」</a>と述べたと引用されている</li>
</ul>
<p>人々がこの結論に至る理由は理解できます。VercelはReactチームのメンバーを何人か雇用し、彼らはReact自体とNextの両方に取り組んでいます。時系列的には、これは自然にRSCの開発と、ユーザーにフレームワークの使用を促すという新しいシフトと一致しました...そして見てください、その「明白な」フレームワークはまさにNextです！一方、VercelはNextをVercel上で簡単にデプロイしてホストするために、莫大な資金と労力を投入してきました。Nextを他の場所でホストすることは可能ですが、機能しなかったり、設定が難しかったりする機能が常にありました。</p>
<p>とはいえ、私が見てきた全てのことから、この見方は一般的に因果関係を逆転させており、ほとんどがFUDです。はい、もちろんVercelは最終的に自分たちに利益をもたらすと考える取り組みに投資してきました。しかし、前述の通り、SebとAndrewがVercelに移り、RSCの開発プロセスについて私が目にした全ての記述は、この一連の変更を推進したのはReactチームであったことを示しています。</p>
<p>RSCはReactチームのビジョンでした。Metaの内部では効果的にプロトタイプを作成してテストすることができなかったため、初めて、開発に投資し、設計を繰り返すための環境を提供する他のスポンサーが必要になりました。具体的な経緯は分かりませんが、私の理解では、ReactチームがVercelを説得してReactチームのビジョンに賛同させ、そしてそのビジョンに合致するApp Routerのアプローチを設計するためにNextを再設計することを彼らに任せた、ということです。</p>
<p>確かに、「Nextはこれを必要としている」といった類のReactへの変更はいくらかありました。あるNextConfsの前夜、重要なNextバージョンの発表があった際（おそらく13.4での安定版App Router？）、ReactリポジトリでいくつかのPRがマージされるのを見た記憶があります。しかし、その作業を行っていたのは依然としてReactチームのメンバーでした。</p>
<p>現時点で、Reactチームは分裂しています。大多数はまだMetaにいますが、Vercelのメンバーはコア実装の鍵を握っています。「Reactチームのミーティングは相変わらずだ」と読んだことがありますが、そのように分裂することから何らかの追加の複雑さが生じていても驚きません。</p>
<p>とはいえ、「VercelがReactを乗っ取った」というのは間違っており、「Reactコアの一部がVercelに移り、Vercelを説得してReactのビジョンに同調させた」という方が近いと私は考えています。また、「Vercel」がReactの設計を主導している、あるいはフレームワークとRSCへの重点化がVercelの収益を上げるという特定の意図を持っているという証拠も見当たりません。</p>
<h2>懸念：ReactはNextでしか動かない</h2>
<p>「Reactは今やNextでしか動かない」と、真剣に、あるいは不思議そうに言う人々のコメントをオンラインでいくつか見てきました。</p>
<p>これは簡単に反論できます。<a href="https://react.dev/learn/start-a-new-react-project">「新しいReactプロジェクトを始める」ページ</a>を見るだけでも、Nextではない他のフレームワークや、やや悪名高い「フレームワークなしでReactを使えますか？」セクションが表示されています。</p>
<p>明らかに、これはReactの仕組みに関する完全な誤解です。</p>
<p>しかし、これは同時に、React、「フレームワークを使え」というメッセージ、そしてVercelの影響力をめぐるメッセージングがいかに混乱しているかを物語っています。</p>
<p>これに非常に関連した「NextとReact、どちらを使うべきか？」という質問も付け加えておきます。これも頻繁に目にします。文字通りに読めば、「Next」と「React」は別物だということです。これも明らかに間違いです。NextはReactライブラリを使用するフレームワークです。それはスーパーセットであり、競合相手ではありません。ほとんどの人が意図しているのは、「Nextを使うべきか、それともCRAやViteのようなクライアントサイドSPAを使うべきか？」ということだと思いますが、それを明確に述べる語彙を持っていないのです。</p>
<p>これもまた、ここでの境界線についてどれほどの混乱があるかを示しています。</p>
<h3>懸念：Reactがいつかクライアントアプリで動かなくなるかもしれない</h3>
<p>この懸念もよく目にします。懸念は、「もしReactチームがサーバーを必要とする機能にこれほどの重点と労力を注いでいるなら、それはいつかクライアントサイドの機能が変わったりなくなったりする可能性があるということか？」というものです。</p>
<p>人々がこの恐怖を抱く理由は理解できます。それは、Reactチームからのトーンと重点の大きな変化、そして彼らがサーバーサイドの機能に注いできた労力の量によって引き起こされています。</p>
<p>しかし、これが決して起こりえないことも明らかです。Reactのクライアントレンダリング機能は、決してなくなりません！ Meta自身が何百万行もの既存のReactコードを持っているという事実だけでも、それが分かります。その上、Reactチームは常にコードレベルの後方互換性に関して非常に優れてきました - 破壊的変更を記述し、非推奨の機能を最終的に削除するまで何年も保持し、移行ガイドやcodemodを提供してきました。</p>
<p>また、React 19と19.1の機能の多くはクライアント専用であることにも注目する価値があります。どちらかと言えば、コミュニティはサーバーサイドの機能に注がれた労力を過大評価し、クライアントサイドの機能に注がれた労力を見逃してきました。</p>
<h3>懸念：なぜReactはフレームワークを推すのか</h3>
<p>そして、次の議論につながります。なぜReactチームは、「そうしないと間違いだ」と言ってまで、全てのReactユーザーにフレームワークを使わせることにそれほど固執するのでしょうか？</p>
<p>これは、私がより多くの共感と同感を持つ懸念です。私は彼らが「Nextを使わせてVercelでホスティングさせよう」という目標で「フレームワークを使え」と言っているのではないと述べましたが、ではその理由は何なのでしょうか？</p>
<h4>Reactチームのフレームワークに対する見解</h4>
<p>Andrew Clarkの<a href="https://x.com/acdlite/status/1617667097424986114">2023年1月のツイートは、その意図をかなり明確に述べています</a>。</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p>Reactを使うなら、Reactフレームワークを使うべきだ。既存のアプリがフレームワークを使っていなければ、段階的に移行すべきだ。新しいReactプロジェクトを作成するなら、最初からフレームワークを使うべきだ。</p><p>あなたが選ぶReactフレームワークには、データフェッチ、ルーティング、サーバーレンダリングのための組み込みソリューションがあるべきだ。フレームワークはこれらを独立した懸念として扱わない — 深く統合されたソリューションを提供し、使いやすく、箱から出してすぐに優れたパフォーマンスが得られる。</p><p>フレームワークを放棄することを選ぶなら、あなたが本当に選んでいるのは、自分専用のフレームワークを構築することであり、それはおそらく既製品よりもはるかに悪いものになるだろう。たとえあなたの特注フレームワークが今日の代替品より優れていると思っても、一年後もそうだろうか？</p><p>最新技術に追いつくには多くの時間とエネルギーがかかる。あなたはフレームワークのメンテナーになる準備ができているか？それとも、あなたの時間をもっと有効に使うべきことがあるのではないか？</p><p>それ以来変わった主なことは、フレームワークが本当に、本当に良くなったことだ。かつてはプレーンなReact + Webpackが、オールインワンのフレームワークと同等かそれ以上だった。私の意見では、もはやそうではない。</p><p>問題は、フレームワークを使わずにReactアプリを構築することが可能かどうかではない。その選択肢は常に存在する。問題は、あなたの自家製のセットアップが、機能、パフォーマンス、DXにおいて業界のリーダーと競争できるかどうかだ。そのハードルは常に上がっている。</p></div></div>
<p>同様に、Dan AbramovがCRAを<a href="https://github.com/reactjs/react.dev/pull/5487#issuecomment-1409720741">非推奨と見なす理由を説明した</a>際にも：</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p>Create React Appは問題の一側面しか解決しなかった。それは（当時としては！）良い開発体験を提供したが、Webの強みを活かして良いユーザー体験を得るための十分な構造を課さなかった。これらの問題を自分で解決しようとすることもできたが、そのためには「eject」してセットアップを大幅にカスタマイズする必要があり、それではCreate React Appの趣旨が損なわれてしまう。本当に効率的なReactのセットアップは全てカスタムで、異なり、Create React Appでは達成不可能だった。</p><p>これらのユーザー体験の問題はCreate React Appに特有のものではない。Reactに特有でさえない。例えば、Viteのホームページテンプレートから作成されたPreact、Vue、Lit、Svelteのアプリは全て同じ問題に苦しんでいる。これらの問題は、静的サイト生成（SSG）やサーバーサイドレンダリング（SSR）のない、純粋にクライアントサイドのアプリに固有のものである。</p><p>Reactでアプリ全体を構築する場合、SSG/SSRを使用できることは重要だ。Create React Appにそれらのサポートがないことは 目に余る欠点だ。しかし、Create React Appが遅れているのはそれだけではない。</p><p>React自体はただのライブラリだ。ルーティングやデータフェッチの方法を規定しない。Create React Appも同様だ。残念ながら、これはReact単体でも、元々設計されたCreate React Appでも、これらの問題を解決できないことを意味する。ご覧の通り、これは単一の欠落した機能に関する話ではない。これらの機能 — サーバーサイドレンダリングと静的生成、データフェッチ、バンドリング、そしてルーティング — は相互に関連している。</p><p>時代は変わった。今では、これらの機能を持たないことに固定されるソリューションを推奨することはますます困難になっている。たとえすぐに全てを使わなくても、必要なときには利用可能であるべきだ。それらを利用するために別のテンプレートに移行し、全てのコードを再構築する必要はないはずだ。同様に、全てのデータフェッチやコード分割がルートベースである必要はない。しかし、それはほとんどのReactアプリで利用可能であるべき良いデフォルトだ。</p></div></div>
<p>私はReactチームとの会話で、Reactアプリの読み込み時間が遅く、全体的なパフォーマンスが悪いという外部からの多くの不満を聞いていると直接言われたこともあります。ですから、フレームワークの重視はそれに対する直接的な反応であり、デフォルトでより多くのアプリがまともなパフォーマンスを持つようにするという目標があります。</p>
<p>それに基づくと、Reactチームのスタンスは次のように要約できます：</p>
<ul>
<li>フレームワークには、データフェッチ、ルーティング、サーバーレンダリングのための組み込みのアプローチがあり、それらは連携して動作するように設計されている</li>
<li>それらは全て箱から出してすぐに提供されるため、部品を選んで組み合わせる（うまく連携しないかもしれない）時間を費やす必要がない</li>
<li>フレームワークは、ビルド設定やより良いデータフェッチパターンにより、デフォルトでより良いパフォーマンスにつながる</li>
<li>さらに、React Server Componentsは、正しく動作するためにフレームワークへの統合が必要である</li>
</ul>
<p>これらの懸念は、Reactチームが今日Reactがどのように使用されるべきだと感じているかにとって極めて重要である</p>
<p>言い換えれば、それはイデオロギー的なスタンス、「これはあなたの時間と労力を節約する」という信念、そして「ほとんどのReactアプリは同様のパターンに従い、同様のソリューションを必要とする」という信念の組み合わせなのです。</p>
<h3>フレームワーク、SSR、そしてSPA</h3>
<p>また、「サーバーレンダリング」という特定の言及に注意してください。一般的に、Reactチームとエコシステムの他の主要メンバー（Remix / React RouterのRyan FlorenceとMichael Jacksonなど）の両方が、クライアントでのデータフェッチの「ウォーターフォール」（例えば、<code class="language-text">&lt;List></code>がアイテムのセットをフェッチし、その子をレンダリングし、<code class="language-text">&lt;ListItem></code>がマウント後に独自のフェッチを行うなど）を避けるためにサーバーレンダリングを行う必要性を強く強調してきました。NextやRemixのようなフレームワークは、箱から出してすぐにサーバーレンダリングをサポートするように特別に設計されています。</p>
<p>「より速い初期ページロード」、「クライアントのJSを少なくする」、「ウォーターフォールを避ける」といったSSRの正当化は、全て理にかなっています。私も、クライアントサイドのSPAがおそらく使われるべきでなかった多くの場所で使用されてきたこと（Reduxと本当に同じです）、そして、たとえすぐに全ての機能が必要でなくてもフレームワークから始めることが理にかなっていること、そうすれば後でそれらの機能が必要になったときに利用できるということに同意します。</p>
<p>NextとRemixには、静的なJS/HTMLアセットを出力するだけの<a href="https://nextjs.org/docs/pages/building-your-application/deploying/static-exports">「SPA / エクスポート」モード</a>があるので、それらを使ってNodeサーバープロセスを必要としない純粋なクライアントサイドアプリを作成することは可能です。しかし、それらはデフォルトではなく、コミュニティのほとんどがそれがオプションであることすら知らないだろうと私は推測します。</p>
<h3>フレームワーク推奨にはニュアンスが欠けている</h3>
<p>これら全てを踏まえて、私はReactチームがなぜデフォルトとしてフレームワークを推奨することに決めたのかを理解しています。それは合理的な意見であり、平均的なReactアプリを改善するための正当な意図だと思います。</p>
<p>しかし、私はまた、その「推奨」が、エコシステム全体でReactが実際に使用されている多様な方法を充分に評価しない、過度に広範な処方箋に変わってしまったと感じています。</p>
<p>フルスタックのReactフレームワークを使用する多くの正当な理由がありますが、使用しない理由もたくさんあります：</p>
<ul>
<li>フレームワークは多くの追加機能を追加しますが、それは学習する複雑さも追加し、Reactの使い方を把握しようとしている初心者にはあまり適していません。</li>
<li>追加された複雑さは、誤ってサーバーコンポーネントでContextやフックを使用してしまう（エラーをスローする）など、混乱につながる罠にもなり得ます。</li>
<li>多くの企業はJSバックエンドを運用していないかもしれず、それに対する規則や制限さえあるかもしれません。</li>
<li>サーバー機能を持つフレームワークは実行に特定のホスティングを必要としますが、純粋なSPAは静的なHTMLとJSを提供する場所ならどこでも（Github PagesやAmazon S3を含む）簡単にホストできます。</li>
<li>ライブラリを選び出す必要性は、しばしばReactユーザーにとって不満の源でしたが、それはプロジェクトを特定のニーズに合わせてカスタマイズすることを可能にします。意見の強いフレームワークは、それらの決定のほとんどを行う必要性をなくしますが、後で挙動をカスタマイズする能力を制限することもあります。</li>
<li>「サーバーレンダリング」への重点化は、一部の種類のアプリには明らかに役立ちますが、全てではありません - クライアントサイドだけで生きる必要があるアプリもたくさんあります。</li>
</ul>
<h3>ドキュメントの推奨事項は重要</h3>
<p>Reactチームは「ドキュメントは初心者を対象としている」と述べており、そのため推奨されるツールやオプションのリストを混乱を避けるためにかなりシンプルに保ちたいと考えています。これは非常に合理的なスタンスです。</p>
<p>彼らはまた、「Viteを使うべきだと知っているほど経験豊富な人は、どのみちドキュメントにそれがリストされているのを見る必要はない」（意訳）とも述べています。それは技術的には真実です。</p>
<p>しかし、ドキュメントを読むのは初心者だけではありませんし、ドキュメントが推奨するものは公式の承認の印としての影響力を持ちます。それが、Reactチームが歴史的に特定のライブラリや技術を推奨することを避けてきた大きな理由の一部です。</p>
<p>フレームワーク／ビルドツールの使用統計を振り返ると、今日、Vite ReactとCRAの使用量はNextを上回っており、SPAの使用が依然としてReactエコシステムの少なくとも半分を占めていることは明らかです。ドキュメントの推奨事項はその使用状況を反映すべきです。</p>
<h3>エコシステムの軽視</h3>
<p>新しいReactドキュメントが公開されたとき、フレームワーク以外のオプションについての唯一の言及は、<a href="https://web.archive.org/web/20230318003653/https://react.dev/learn/start-a-new-react-project#can-i-use-react-without-a-framework">「フレームワークなしでReactを使えますか？」</a>と題された展開可能な詳細セクションで、「はい、しかし私たちはそれが良い考えだとは思いません」と述べるいくつかの段落がありました。</p>
<p>そして、そのセクションの最後で語られていたのがこちらの声明でした：</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p>それでも納得できない場合、またはあなたのアプリがこれらのフレームワークではうまく対応できない珍しい制約を持っている場合、そして独自のカスタムセットアップを構築したい場合は、私たちはあなたを止めることはできません—どうぞ！npmからreactとreact-domを入手し、ViteやParcelのようなバンドラでカスタムビルドプロセスを設定し、ルーティング、静的生成またはサーバーサイドレンダリングなどのために必要に応じて他のツールを追加してください。</p></div></div>
<p>つまり、新しいセットアップページはCreate React Appをリストアップしなかっただけでなく、最も近い同等のツール（Vite）はページの外部セクションで意味のあるオプションとしてさえリストされていませんでした。代わりに、それはこの展開可能な説明の最後に単なる言及として記されていました。</p>
<p>また、「珍しい制約」というフレーズはここでは非常に不適切だと言いたいです。SPAはReactコミュニティで当初から標準的なアーキテクチャでした。CRAとViteは一般的に使用されています。SPAを構築することが、どうして突然「珍しい制約」になるのでしょうか？アーキテクチャ的な理由からビジネス上の理由、学習の単純さまで、SPAを望む理由はたくさんあります。それらは「珍しい」ものではありません - それらは全て一般的なユースケースです。</p>
<p>そしてそれを超えて...「私たちはあなたを止めることはできません」というフレーズに注目してください。そのフレーズは、多くの人々の目に、コミュニティを軽視する不適切な表現として、そしてReactチームが一夜にしてSPAをサポートされていないアプローチとして格下げしていることのしるしとして映りました。</p>
<p>この例として、2023年半ばのドキュメントにおける「フレームワーク」重視に関する議論からの引用を以下に示します：</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p><em>ある意味で、ReactはAngularJSの瞬間を迎えています。チームはこの不安定で不確実な時期を利用して、代替案を探しています。私たちは確かにそうです。
Reactは素晴らしいです。コミュニティにとって懸念なのは、サーバー＆フレームワークへの強い同調というトーンの変化です。今日ある姿にしたサーバーニュートラルな側面が、突然二流に感じられます。（SPAのルーター＆ローダーは混乱しており、十分なサービスが提供されていません！）
それはReactやViteについてではありません。エコシステムについてです。Reactが伝統的な「非フレームワーク」を「新しい方法」ほど強く奨励しないことに気づくのは辛いです。「フレームワークなしのReact」セクションはドキュメントに隠されており、憂鬱です。
Node以外のバックエンドを持つ企業として、私たちはそれらのドキュメントを、もはやReactの主要な方向性と一致しないというサインと見ています。フレームワークの支持は、チームにスタック全体を再評価させるほどの大きな変化です。Reactは素晴らしいですが、その影響力はアーキテクチャに限界があります。Reactがこの方向に行くことが「間違っている」わけではありません。しかし、非フレームワークのReactの使用が今や二流の懸念事項であると偽ることはできません。それが実行不可能な選択肢になるまで、あとどれくらいでしょうか？それは不快です。だから、私たちはより安全な賭けを探します。Node以外のバックエンドを持つ企業に何を勧めますか？ <a href="https://x.com/vyrotek/status/1649097699696992256">https://x.com/vyrotek/status/1649097699696992256</a></em></p></div></div>
<h3>ドキュメントの推奨事項の修正</h3>
<p>Reactチームが最終的に<a href="https://react.dev/blog/2025/02/14/sunsetting-create-react-app">2025年初頭にCRAを非推奨と発表</a>したとき（私がそれを修正し、その非推奨を公式にするように彼らを後押しした後）、<a href="https://github.com/reactjs/react.dev/pull/7495">最初のドキュメント更新</a>には新しい「独自のフレームワークを構築する」ページが含まれていました。そのコンセプトは、独自のルーターとデータフェッチのアプローチを選ぶことは、「本物の」フレームワークほど良くない特注のフレームワークを寄せ集めることと同等である、というものでした。</p>
<p>ここでのメンタルモデルは理解できますが、これもまた、エコシステムがアプリを構築してきた方法と、人々が独自のオプションを選択する能力の両方をかなり軽視しています。</p>
<p>一例として、Viteの作者である<a href="https://x.com/youyuxi/status/1892000761778929931">Evan Youは彼の見解を投稿</a>しました：</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p>ReactチームのVite（およびビルドツール全般）に対するためらいは、それが彼らのReactのビジョン（つまり、RSCが推奨されるパラダイムであること）とどのように一致するか、そして彼らが設計通りに統合が機能し、設計のイテレーションに適応できるようにするために、言及されたツールとどれだけの影響力／つながりを持っているか、という点にあるように感じます。...</p><p>これがReactチームが考えていることと違うのであれば謝罪しますが、これは私の正直な推測です。なぜなら、Viteが推奨ツールとしてより良く認識されるまでにこれほど時間がかかったことに、私も多くのReactユーザーと同じくらい戸惑っているからです。</p></div></div>
<p>私は最終的に、トーンを改善し、プロジェクトセットアップツールの推奨にニュアンスを加え、アプローチを選択する際のアーキテクチャ的なガイダンスを提供するために、<a href="https://github.com/reactjs/react.dev/pull/7618">セットアップページを刷新するドラフトPRを提出</a>しました。そのPRはクローズされましたが、Reactチームはいくつかのアイデアと言い回しを新しいPRにチェリーピックしました。</p>
<p>最終的に、彼らはかなり合理的な<a href="https://web.archive.org/web/20250526084038/https://react.dev/learn/build-a-react-app-from-scratch">「Reactアプリをゼロから構築する」セットアップページ</a>に落ち着きました。それはSPAと独自のツールを選ぶことを有効なアプローチとして認識し、Vite / Parcel / RSPackを推奨ビルドツールとしてリストアップし、いくつかのルーターとデータフェッチライブラリを指し示し、プロジェクトを設定しようとしているユーザーに実際に役立つガイダンスを提供しています。</p>
<p>ドキュメントがその点に達するまでに数年かかったのは残念です:( 新しいドキュメントがリリースされた直後に、Reactチームがコミュニティから推奨された表現の変更のいくつかを適用していれば、混乱と苦悩の多くは避けられたかもしれません。</p>
<h2>懸念：サーバーコンポーネントのドキュメントと説明</h2>
<p>React Server Componentsに関する大きな問題点と混乱の原因の1つは、公式のドキュメントと情報が散在しており、残念ながら不足していることです。</p>
<p>Reactチームは<a href="https://react.dev/blog/2020/12/21/data-fetching-with-react-server-components">2020年12月に説明ビデオ付きでRSCを発表</a>し、それに続いて<a href="https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md">RSCを説明する詳細なRFCドキュメント</a>が公開しました。これらは背景と主な動機を説明する上で、まずまずの内容でした。</p>
<p>しかし、RSCの開発プロセスと、設計上の選択を実装するのはフレームワーク次第であるという事実が相まって、実際のReactドキュメントはRSCを有意義に文書化することはありませんでした。</p>
<p>新しいApp Routerの形でRSCの最初の公式ベータ版が<a href="https://nextjs.org/blog/next-13">Next 13.0</a>で公開されるまでにほぼ2年かかり、さらに6ヶ月後、2023年5月に<a href="https://nextjs.org/blog/next-13-4">Next 13.4が公式にApp Routerを「安定版」であり本番環境対応</a>であると宣言しました。VercelとNextには小規模ながら非常に活発なdevrelチームがあり、<a href="https://web.archive.org/web/20230502235021/https://beta.nextjs.org/docs">Next 13.4のリリースの頃には、当時新しかったApp RouterをカバーするためにNextのドキュメントを大幅に書き直していました</a>（「ベータ版」ドキュメントとして利用可能）。それには、RSCの使用について語るいくつかのドキュメントページが、「データフェッチ」カテゴリの下に含まれていました。また、後には<a href="https://vercel.com/blog/understanding-react-server-components">「React Server Componentsを理解する」</a>のような詳細なブログ投稿もありました。これらは、アーキテクチャとコンセプトの非常に役立つ説明を含む、優れたリソースでした。</p>
<p>それにもかかわらず、RSCの機能はReact自体の一部であったにもかかわらず、ReactのドキュメントにはRSCに関する情報が一切ありませんでした。「RSCとは何か」もなく、「RSCをどう使うか」もなく、そして間違いなく「自分のライブラリをRSCと統合するにはどうすればよいか」もありませんでした。Vercelのチームは実際の製品とReactとの重複部分を文書化する良い仕事をしましたが、実際のReactドキュメント自体にRSCに関する情報や説明がないことは人々にとって非常に混乱を招きました（時系列的には、それは数ヶ月前に公式にローンチされたばかりでした）。</p>
<p>ソーシャルメディア上では、個々のReactチームメンバーによる多くの議論があり、質問に答えたり、概念としてのRSCを擁護したり、RSCのセールスポイントを具体化しようとしたりしてきました。しかし、現在、RSCに関する唯一の公式コアドキュメントページは<a href="https://react.dev/reference/rsc/server-components">APIリファレンス：サーバーコンポーネント</a>です。しかしこのページは正直なところ混乱を招くだけで特に役立つようには思えません。それは内容的に間違いなく「APIリファレンス」ではなく、その内容にはRSCを紹介したり、いつ、どのように使用するかを説明したりする実際の文脈がありません - それはランダムなトピックのごちゃまぜです。</p>
<p>RSCのさまざまな側面を説明する優れた外部ブログ投稿がいくつかありました：</p>
<ul>
<li>Dan Abramovは<a href="https://overreacted.io/">RSCのメンタルモデルとそれらが解決する問題を説明する数多くの素晴らしい投稿</a>を書いています。</li>
<li>Vercelは素晴らしい<a href="https://vercel.com/blog/understanding-react-server-components">「React Server Componentsを理解する」</a>紹介ブログ投稿を公開しました。</li>
<li>Daniel Saewitzは<a href="https://saewitz.com/server-components-give-you-optionality">「サーバーコンポーネントは選択肢を与える」</a>を書き、それらがツールボックスへの追加であることを説明しました。</li>
</ul>
<p>これこそが、Reactのコアドキュメントにあるべき情報です - RSCのコンセプトと、なぜそれらを使いたいかの説明であり、特定のフレームワークがRSCをどのように実装したかとは無関係です。</p>
<p>これに関連して、コミュニティは「RSCはReactの未来である」、「Reactチームは私たちにRSCを常にどこでも使ってほしいと思っている」という考えを持ち帰ってしまいました。実際には、Reactチームのメンバーがソーシャルメディアで「RSCはオプションであり、私たちはただ人々にそれを正しく理解してほしいだけだ」と明言しているのを見てきました。その混乱を考えると、その点を特にドキュメントで指摘することが重要だと思われます。</p>
<p>個人的には、ReactのコアドキュメントにRSCに関するセクション全体が追加されるのを見たいです。例えば、以下のようなページです：</p>
<ul>
<li>「RSC入門」</li>
<li>メンタルモデル</li>
<li>ユースケース / RSCを選択するタイミング</li>
<li>技術的なデータフロー / アーキテクチャ</li>
<li>採用 / 移行</li>
<li>FAQ</li>
<li>フレームワーク実装者ガイド</li>
</ul>
<p>これらのドキュメントページがあっても、全ての疑問がなくなるわけではありません。そもそもドキュメントを読まない人もたくさんいます:) しかし、これらのトピックが公式にカバーされることで可視化され、ソーシャルメディアでこれらの質問が持ち上がるたびに答えるために使用できるリソースとして機能するでしょう。</p>
<h2>懸念からの教訓</h2>
<p>私は、Reactチームがインターネット上のFUDを払拭するために彼らの時間の全てを費やすのが彼らの仕事だとは思いません。多くの人が意見を持つでしょうし、その多くは異なるか間違っているでしょう。</p>
<p>とはいえ、「フレームワークとサーバーへのこの全ての強調は、Vercelにお金を稼がせるために私たちに強制されている」という一貫した強いテーマがあることは驚くべきことです。それは間違っていますが、人々をその信念に導くのに十分な状況証拠があり、Reactチームの行動と声明はある程度、それを反証するのではなく、その信念を強化してしまいました。残念ながら、現時点ではそれはコミュニティに永久に埋め込まれたミームのようになっており、私たちはそれをなくすことはできないと思います。</p>
<p>また、ユーザーがReactが将来どのように変化したり動作しなくなったりする可能性があるかについて心配したり、想定したりしていることも、本当に懸念すべきことです。特に、それらの懸念が明らかに間違っており、簡単に反証可能である場合はなおさらです。あなたはこれまで通りにReactを使い続けることができ、RSC / サーバーの機能は全て、完全に付加的でオプションです。</p>
<p>インターネットがそういうものである以上、実際のReactブログにそれらの声明を明確に反論するブログ投稿があったとしても、多くの人々の心を変えることはないでしょう。しかし、React組織からのより良いデベロッパーリレーションズ活動 - 懸念を読み、それらが存在することを認め、どこから来ているのかを理解し、それらを明確にして答えようとすることにもっと時間を費やすこと - で、その最悪の事態の一部は避けられたかもしれないと感じます。</p>
<p>「フレームワーク」への推進は本当に善意から出たものだと感じますが、同時にあまりにも広範で、最終的にはエコシステムにおける多様な使用パターンを軽視しているとも思います。はい、ニュアンスを提供するドキュメントを書き、推奨事項を提供するのは難しいですし、初心者を対象とし、エコシステムをより良い方向に導くために物事をシンプルに保とうとすることは理解できます...しかし、メンテナーであることの一部は、コミュニティによってあなたのツールが使用される多様な方法を認識し、サポートすることです。</p>
<p>私たちは最終的に、プロジェクトを自分でセットアップするための有用な指示を持ち、それを有効なアプローチとして認識する、合理的なReactプロジェクトセットアップドキュメントのセットにたどり着きました。その時点に到達するまでに数年かかったこと、そしてReactチームがそのセクションを改善する方法に関するコミュニティからの非常に声高なフィードバックを本質的に無視したことは残念です。ドキュメントは、アプリに適したツールとアーキテクチャを選択する方法に関するアドバイスがもっとあれば、依然として大いに恩恵を受けるでしょう。</p>
<p>同様に、RSCのコンセプトとトレードオフに関する公式のコアドキュメント情報の欠如が混乱を助長してきました。これらのトピックをカバーすることが、人々のRSCに対する理解とそれに伴う言説を大きく改善すると強く感じています。</p>
<h2>最後に</h2>
<p>この記事が、Reactがどのように、そしてなぜこのように発展してきたのか、Reactの開発プロセスにどのような影響があるのか、Reactチームの主な目標は何か、そして今日のReactの使用パターンはどのような状況にあるのか、といった多くの疑問に答えるものであれば幸いです。</p>
<p>また、Reactチームの動機や特定の方向に推進する理由に関する混乱やFUDの一部を払拭できたことを願っています。人々が技術的な方向性に同意しないこと、あるいはReact Server Componentsやより大きなフレームワークへの移行が必要ないと判断することは問題ありません。しかし、Reactチームの意図はここでも正当で純粋なものです。</p>
<p>広く使われているライブラリを維持し、コミュニティの多様なニーズや使用パターンを満たすことは、本当に難しいことです。私はReactチームが全体として良い仕事をしてきたと思います。残念ながら、コミュニケーションが不十分であった点やドキュメントの問題が、コミュニティにおける多くの不満や苦悩の大きな要因となってきました。</p>
<p>今後、私たちはそのコミュニケーションを改善する方法を見つけ、さらにはドキュメントの改善にもっと多くのコミュニティが関わってくれるようになることを期待しています。</p>]]></content:encoded></item><item><title><![CDATA[Reactの差分検出処理：コンポーネントの背後に隠れたエンジン]]></title><description><![CDATA[差分検出エンジン 以前投稿した記事（1、2）で、の仕組みと、コンポジションを通じてよりスマートにパフォーマンスを最適化する方法について説明しました。しかし、Reactのパフォーマンスを完全にマスター…]]></description><link>https://postd.cc/react-reconciliation-deep-dive/</link><guid isPermaLink="false">https://postd.cc/react-reconciliation-deep-dive/</guid><category><![CDATA[開発手法・プロジェクト管理]]></category><category><![CDATA[React]]></category><pubDate>Fri, 25 Jul 2025 00:00:01 GMT</pubDate><content:encoded><![CDATA[<h2>差分検出エンジン</h2>
<p>以前投稿した記事（<a href="https://cekrem.github.io/posts/beyond-react-memo-smarter-performance-optimization/">1</a>、<a href="https://cekrem.github.io/posts/react-memo-when-it-helps-when-it-hurts/">2</a>）で、<code class="language-text">React.memo</code>の仕組みと、コンポジションを通じてよりスマートにパフォーマンスを最適化する方法について説明しました。しかし、Reactのパフォーマンスを完全にマスターするには、すべてを動かすエンジンである、Reactの差分検出アルゴリズムを理解する必要があります。
差分検出は、ReactがDOMをアップデートし、コンポーネントツリーと一致させるプロセスです。Reactの宣言的プログラミングを可能にしているのが差分検出です。ユーザーが求めるものを説明すると、Reactがそれを効率的に実現する方法を探します。</p>
<h2>コンポーネントの識別情報とstateの永続性</h2>
<p>技術的な内容について話し始める前に、Reactがコンポーネントの識別情報（そのコンポーネントらしさを形成する独自性）についてどのように考えているかを示す驚くべき動作を見てみましょう。
以下の簡単なテキスト入力のトグル例をご覧ください。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">UserInfoForm</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>isEditing<span class="token punctuation">,</span> setIsEditing<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>form-container<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setIsEditing</span><span class="token punctuation">(</span><span class="token operator">!</span>isEditing<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">
        </span><span class="token punctuation">{</span>isEditing <span class="token operator">?</span> <span class="token string">"Cancel"</span> <span class="token operator">:</span> <span class="token string">"Edit"</span><span class="token punctuation">}</span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token plain-text">

      </span><span class="token punctuation">{</span>isEditing <span class="token operator">?</span> <span class="token punctuation">(</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span>
          <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span>
          <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Enter your name<span class="token punctuation">"</span></span>
          <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>edit-input<span class="token punctuation">"</span></span>
        <span class="token punctuation">/></span></span>
      <span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token punctuation">(</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span>
          <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span>
          <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Enter your name<span class="token punctuation">"</span></span>
          <span class="token attr-name">disabled</span>
          <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>view-input<span class="token punctuation">"</span></span>
        <span class="token punctuation">/></span></span>
      <span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>このフォームを操作すると、興味深い動作がみられます。編集画面で入力欄に任意のテキストを入力して「キャンセル」ボタンをクリックすると、再度「編集」ボタンをクリックしたとき、入力したテキストが残った状態になります。この現象は、2つの<code class="language-text">input</code>要素が異なるprops（1つは無効化され、クラスが異なります）を持っているにもかかわらず起こります。</p>
<p>ReactはDOM要素とそのstateを保持しますが、これはどちらの要素も同じタイプ（<code class="language-text">input</code>）であり、要素ツリーの同じ位置にあるためです。Reactは要素を作り直すのではなく、単純に既存の要素のpropsを更新します。</p>
<p>しかし、実装を以下のように変更するとどうでしょうか。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token punctuation">{</span>
  isEditing <span class="token operator">?</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Enter your name<span class="token punctuation">"</span></span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>edit-input<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
  <span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>view-only-display<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">Name will appear here</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>そうすると、編集モードを切り替えることで全く異なる要素がマウントおよびアンマウントされ、ユーザーの入力は失われます。</p>
<p>この動作は、Reactの差分検出の基本的側面に焦点を当てます。<strong>すなわち、要素タイプは識別情報を決める主要な要因である</strong>ということです。この概念を理解することが、Reactのパフォーマンスをマスターする上でカギとなります。</p>
<h2>仮想DOMではなく要素ツリー</h2>
<p>Reactがアップデートを最適化するために「仮想DOM」を使用するということはおそらくご存知でしょう。これは有益なメンタルモデルではありますが、Reactの内部を要素ツリー（画面上に表示される内容を簡単に表したもの）として考えた方が正確です。</p>
<p>以下のようなJSXを書くとします。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Component</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">Hello</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">World</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>ReactはこれをJavaScriptのプレーンオブジェクトからなるツリー構造に変換します。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token punctuation">{</span>
  <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'div'</span><span class="token punctuation">,</span>
  <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token punctuation">[</span>
      <span class="token punctuation">{</span>
        <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'h1'</span><span class="token punctuation">,</span>
        <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">{</span>
          <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token string">'Hello'</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">{</span>
        <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'p'</span><span class="token punctuation">,</span>
        <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">{</span>
          <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token string">'World'</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">]</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><code class="language-text">div</code>や<code class="language-text">input</code>のようなDOM要素では、「type」は文字列です。一方、カスタムReactコンポーネントでは、「タイプ」は実際の関数への参照です。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token punctuation">{</span>
  <span class="token literal-property property">type</span><span class="token operator">:</span> Input<span class="token punctuation">,</span> <span class="token comment">// Reference to the Input function itself</span>
  <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">"company-tax-id"</span><span class="token punctuation">,</span>
    <span class="token literal-property property">placeholder</span><span class="token operator">:</span> <span class="token string">"Enter company Tax ID"</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<h2>差分検出の仕組み</h2>
<p>ReactがUIをアップデートする必要がある場合（stateの変更または再レンダリング後）以下のことを行います。</p>
<ol>
<li>コンポーネントを呼び出し、新しい要素ツリーを作成する</li>
<li>以前のツリーと新しいツリーを比較する</li>
<li>実際のDOMを新しいツリーと一致させるために必要なDOM操作を割り出す</li>
<li>それらの操作を効率的に実行する
比較アルゴリズムは以下の基本原則に従います。</li>
</ol>
<h3>1. 要素タイプが識別情報を決める</h3>
<p>Reactはまず要素の「タイプ」を確認します。タイプが変わる場合、Reactはサブツリー全体を再構築します。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token comment">// From this (first render)</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><span class="token plain-text">
  </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Counter</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>

<span class="token comment">// To this (second render)</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span><span class="token plain-text">
  </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Counter</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><code class="language-text">div</code>が<code class="language-text">span</code>に変わったため、Reactは古いツリーの全体（<code class="language-text">Counter</code>を含む）を破壊し、新しいツリーをゼロから構築します。</p>
<h3>2. ツリー内の位置は重要</h3>
<p>Reactの差分検出アルゴリズムは、ツリー構造内におけるコンポーネントの位置に大きく依存します。位置は、差分を比較する際に識別情報を示す主な要因となります。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token comment">// Let's pretend showDetails is true: Render UserProfile</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span></span><span class="token punctuation">></span></span><span class="token plain-text">
  </span><span class="token punctuation">{</span>showDetails <span class="token operator">?</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">UserProfile</span></span> <span class="token attr-name">userId</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token number">123</span><span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token operator">:</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">LoginPrompt</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span></span><span class="token punctuation">></span></span>

<span class="token comment">// Let's pretend showDetails is false: Render LoginPrompt instead</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span></span><span class="token punctuation">></span></span><span class="token plain-text">
  </span><span class="token punctuation">{</span>showDetails <span class="token operator">?</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">UserProfile</span></span> <span class="token attr-name">userId</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token number">123</span><span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token operator">:</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">LoginPrompt</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span></span><span class="token punctuation">></span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>この条件式の例では、Reactはフラグメントの最初の子の位置を一つの「スロット」として扱います。<code class="language-text">showDetails</code>が<code class="language-text">true</code>から<code class="language-text">false</code>に変わると、Reactはそれぞれのレンダー結果の同じ位置にある情報を比較し、そこには異なるコンポーネントタイプ（<code class="language-text">UserProfile</code> vs <code class="language-text">LoginPrompt</code>）があります。ポジション1のコンポーネントタイプが変わったため、Reactは古いコンポーネントをすべて（stateも含めて）アンマウントし、新しいコンポーネントをマウントします。</p>
<p>このポジションベースの識別情報は、もっとシンプルなケースでコンポーネントがstateを保持する理由でもあります。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token comment">// Before</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span></span><span class="token punctuation">></span></span><span class="token plain-text">
  </span><span class="token punctuation">{</span>isPrimary <span class="token operator">?</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">UserProfile</span></span> <span class="token attr-name">userId</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token number">123</span><span class="token punctuation">}</span></span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>primary<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
  <span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">UserProfile</span></span> <span class="token attr-name">userId</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token number">456</span><span class="token punctuation">}</span></span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>secondary<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span></span><span class="token punctuation">></span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>この例では、<code class="language-text">isPrimary</code>の値にかかわらず、同じ位置に同じコンポーネントタイプ（<code class="language-text">UserProfile</code>）があります。Reactはコンポーネントを再マウントするのではなく、シンプルにpropsを更新し、インスタンスを保持します。</p>
<p>このポジションベースのアプローチは、ほとんどのシナリオで有効ですが、以下の場合には問題が生じます。</p>
<ol>
<li>コンポーネントの位置が動的に移動する（リストのソート時など）</li>
<li>コンポーネントの位置が変わる場合にstateを保持する必要がある</li>
<li>コンポーネントを再マウントするタイミングを正確に制御したい</li>
</ol>
<p>ここで活躍するのがReactのkeyシステムです。</p>
<h3>3. keyはポジションベースの比較をオーバーライドする</h3>
<p>デベロッパーは、<code class="language-text">key</code>属性によりコンポーネントの識別情報を明確に制御し、Reactのデフォルト動作である位置に基づく識別をオーバーライドすることができます。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">TabContent</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> activeTab<span class="token punctuation">,</span> tabs <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>tab-container<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token punctuation">{</span>tabs<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">tab</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>
        <span class="token comment">// Key overrides position-based comparison</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>tab<span class="token punctuation">.</span>id<span class="token punctuation">}</span></span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>tab-content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">
          </span><span class="token punctuation">{</span>activeTab <span class="token operator">===</span> tab<span class="token punctuation">.</span>id <span class="token operator">?</span> <span class="token punctuation">(</span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">UserProfile</span></span>
              <span class="token attr-name">key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>active-profile<span class="token punctuation">"</span></span>
              <span class="token attr-name">userId</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>tab<span class="token punctuation">.</span>userId<span class="token punctuation">}</span></span>
              <span class="token attr-name">role</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>tab<span class="token punctuation">.</span>role<span class="token punctuation">}</span></span>
            <span class="token punctuation">/></span></span>
          <span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token punctuation">(</span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>placeholder<span class="token punctuation">"</span></span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>placeholder<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">
              Select this tab to view </span><span class="token punctuation">{</span>tab<span class="token punctuation">.</span>userId<span class="token punctuation">}</span><span class="token plain-text">'s profile
            </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
          <span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
      <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>条件式のレンダー結果として<code class="language-text">UserProfile</code>コンポーネントが異なる位置にくる場合でも、Reactは同じkeyを持つコンポーネントを同じコンポーネントとして扱います。タブが有効になると、「active-profile」keyが変わらないためReactはコンポーネントのstateを保持し、その結果、タブの切り替えをスムーズに行うことができます。</p>
<p>この例は、レンダーツリー構造上の位置にかかわらず、keyを用いることでコンポーネントの識別情報をいかにして維持できるかを示しています。keyは、Reactによるコンポーネント階層構造の差分検出を制御する強力な手段を与えてくれます。</p>
<h2>keyの魔法</h2>
<p>keyは主にリストにおける役割で知られていますが、Reactの差分検出プロセスに与える影響はそれだけにとどまりません。</p>
<h3>keyがリストに必要である理由</h3>
<p>リストをレンダリングする際、Reactはkeyを用いて項目の追加、削除、並べ替えを把握しています。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span><span class="token plain-text">
  </span><span class="token punctuation">{</span>items<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>item<span class="token punctuation">.</span>id<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>item<span class="token punctuation">.</span>text<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>keyがなければ、Reactは配列上の要素の位置だけに頼らなくてはなりません。先頭に新しい項目を挿入した場合、Reactはすべての要素の位置が変わったとみなし、リスト全体を再レンダリングします。</p>
<p>keyがあれば、Reactは位置にかかわらずレンダー間の要素を照合することができます。</p>
<h3>配列以外のkey？</h3>
<p>Reactは、静的要素に対してkeyの追加を強要しません。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token comment">// No keys needed</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span></span><span class="token punctuation">></span></span><span class="token plain-text">
  </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Input</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
  </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Input</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span></span><span class="token punctuation">></span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>これは、Reactがこれらの要素が静的であることを知っているからです。つまり、ツリー上の要素の位置が予測可能だということです。</p>
<p>しかし、keyはリストの外でも強力なツールになり得ます。以下の例をご覧ください。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Component</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>isReverse<span class="token punctuation">,</span> setIsReverse<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span></span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Input</span></span> <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>isReverse <span class="token operator">?</span> <span class="token string">"some-key"</span> <span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Input</span></span> <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token operator">!</span>isReverse <span class="token operator">?</span> <span class="token string">"some-key"</span> <span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">	
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span></span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><code class="language-text">isReverse</code>が切り替わると、2つの入力の間で<code class="language-text">'some-key'</code>が移動するため、Reactはコンポーネントのstateを2つの位置の間で「移動」させることができます。</p>
<h3>動的要素と静的要素を混在させる</h3>
<p>動的リストに項目を追加することで、リストの後の静的要素の識別情報が変わるのではないかという懸念がよく聞かれます。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span></span><span class="token punctuation">></span></span><span class="token plain-text">
  </span><span class="token punctuation">{</span>items<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ListItem</span></span> <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>item<span class="token punctuation">.</span>id<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">
  </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">StaticElement</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"> </span><span class="token punctuation">{</span><span class="token comment">/* Will this re-mount if items change? */</span><span class="token punctuation">}</span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span></span><span class="token punctuation">></span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Reactはこの問題にスマートに対処します。動的リスト全体を最初の位置でひとまとまりとして扱うため、リストに変更があっても<code class="language-text">StaticElement</code>は常に同じ位置と識別情報が保たれます。</p>
<p>React内部では以下のように表現されます。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token punctuation">[</span>
  <span class="token comment">// The entire dynamic array becomes a single child</span>
  <span class="token punctuation">[</span>
    <span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> ListItem<span class="token punctuation">,</span> <span class="token literal-property property">key</span><span class="token operator">:</span> <span class="token string">"1"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> ListItem<span class="token punctuation">,</span> <span class="token literal-property property">key</span><span class="token operator">:</span> <span class="token string">"2"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> StaticElement <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// Always maintains its second position</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>リストに項目を追加または削除しても、<code class="language-text">StaticElement</code>は親配列のポジション2のまま変わりません。つまり、リストに変更が加えられても再マウントされないということです。このスマートな仕組みにより、隣接する動的リストの変更によって静的要素が不必要に再マウントされることがなく、処理が最適化されます。</p>
<h3>3. DOMを戦略的に制御するためのkey</h3>
<p>keyの用途はリストに限りません。React上でコンポーネントやDOM要素の識別情報を制御するのに有益なツールです。Reactコンポーネントのstateを異なるビュー上で保持する際、keyとコンポーネントタイプが使われるということを覚えておきましょう。つまり、keyは同じでもタイプが異なれば、コンポーネントはアンマウントおよび再マウントされます。これらの場合、一般的にはstateのリフトアップの方が良い方法です。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token comment">// State lifting approach for preserving state across different views (keys are no good here...)</span>
<span class="token keyword">const</span> <span class="token function-variable function">TabContent</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> activeTab <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token comment">// State that needs to be preserved across tab changes</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>sharedState<span class="token punctuation">,</span> setSharedState<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token comment">/* initial state */</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token punctuation">{</span>activeTab <span class="token operator">===</span> <span class="token string">"profile"</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ProfileTab</span></span> <span class="token attr-name">state</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>sharedState<span class="token punctuation">}</span></span> <span class="token attr-name">onStateChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setSharedState<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span>
      <span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">
      </span><span class="token punctuation">{</span>activeTab <span class="token operator">===</span> <span class="token string">"settings"</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">SettingsTab</span></span> <span class="token attr-name">state</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>sharedState<span class="token punctuation">}</span></span> <span class="token attr-name">onStateChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setSharedState<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span>
      <span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text">
      </span><span class="token punctuation">{</span><span class="token comment">/* Other tabs */</span><span class="token punctuation">}</span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>この場合、タブ間でタイプ（および参照先）が異なるため、keyを保持するだけでは不十分です。</p>
<p>しかし、<code class="language-text">key</code>と非制御コンポーネントを用いた以下の例をご覧ください。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">UserForm</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> userId <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token comment">// No React state here - using uncontrolled inputs</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span>
        <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>userId<span class="token punctuation">}</span></span>
        <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>username<span class="token punctuation">"</span></span>
        <span class="token comment">// Uncontrolled input with defaultValue instead of value</span>
        <span class="token attr-name">defaultValue</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">"</span></span>
      <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token punctuation">{</span><span class="token comment">/* Other form inputs */</span><span class="token punctuation">}</span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>userIdに基づくkeyを非制御入力に与えることで、ReactはuserIdが変わるたびに全く新しいDOM要素を作成するようになります。非制御入力のstateがReactのstateではなくDOM自体の中に存在するため、ユーザーを切り替える際に入力が効果的にリセットされます。この場合、必要なのは<code class="language-text">key</code>だけです。</p>
<p>非常に秀逸です。</p>
<h2>stateコロケーション：強力なパフォーマンスパターン</h2>
<p>stateコロケーションは、stateを使用場所のできるだけ近くにとどめておくパターンです。このアプローチでは、state変更の影響を直接受けるコンポーネントだけがアップデートされるようにすることで、不要な再レンダリングを最小限にとどめます。</p>
<p>以下の例をご覧ください。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token comment">// Poor performance - entire app re-renders when filter changes</span>
<span class="token keyword">const</span> <span class="token function-variable function">App</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>filterText<span class="token punctuation">,</span> setFilterText<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> filteredUsers <span class="token operator">=</span> users<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">user</span><span class="token punctuation">)</span> <span class="token operator">=></span> user<span class="token punctuation">.</span>name<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>filterText<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span></span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">SearchBox</span></span> <span class="token attr-name">filterText</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>filterText<span class="token punctuation">}</span></span> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setFilterText<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">UserList</span></span> <span class="token attr-name">users</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>filteredUsers<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ExpensiveComponent</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span></span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><code class="language-text">filterText</code>が変わると、フィルターの影響を受けない<code class="language-text">ExpensiveComponent</code>も含めて<code class="language-text">App</code>コンポーネント全体が再レンダリングされます。
では、フィルターのstateを、それを使用するコンポーネントとだけコロケーションしてみましょう。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">UserSection</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>filterText<span class="token punctuation">,</span> setFilterText<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> filteredUsers <span class="token operator">=</span> users<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">user</span><span class="token punctuation">)</span> <span class="token operator">=></span> user<span class="token punctuation">.</span>name<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>filterText<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span></span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">SearchBox</span></span> <span class="token attr-name">filterText</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>filterText<span class="token punctuation">}</span></span> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setFilterText<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">UserList</span></span> <span class="token attr-name">users</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>filteredUsers<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span></span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token function-variable function">App</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span></span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">UserSection</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ExpensiveComponent</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span></span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>そうすると、フィルターが変わっても、<code class="language-text">UserSection</code>だけが再レンダリングされます。このパターンは、パフォーマンスを改善するだけでなく、各コンポーネントが保持するstateだけを管理するようにすることで、より良いコンポーネント設計を可能にします。</p>
<h2>コンポーネント設計：変更の最適化</h2>
<p>パフォーマンスの最適化は、コンポーネント設計においてしばしば課題となります。コンポーネントの機能が多すぎると、不要な再レンダリングが行われる可能性が高くなります。</p>
<p><code class="language-text">React.memo</code>に頼る前に、以下の点を検討してみましょう。</p>
<ol>
<li><strong>コンポーネントに複数の責任が与えられていないか</strong>。複数の関心事を処理するコンポーネントは、頻繁に再レンダリングする可能性が高くなります。</li>
<li><strong>stateをリフトアップしすぎていないか</strong>。stateをツリー上で必要以上にリフトアップすると、より多くのコンポーネントが再レンダリングされるようになります。</li>
</ol>
<p>以下の例をご覧ください。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token comment">// Problematic design - mixed concerns</span>
<span class="token keyword">const</span> <span class="token function-variable function">ProductPage</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> productId <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>selectedSize<span class="token punctuation">,</span> setSelectedSize<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">"medium"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>quantity<span class="token punctuation">,</span> setQuantity<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>shipping<span class="token punctuation">,</span> setShipping<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">"express"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>reviews<span class="token punctuation">,</span> setReviews<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// Fetches both product details and reviews</span>
  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">fetchProductDetails</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">fetchReviews</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>setReviews<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>productId<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ProductInfo</span></span>
        <span class="token attr-name">selectedSize</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>selectedSize<span class="token punctuation">}</span></span>
        <span class="token attr-name">onSizeChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setSelectedSize<span class="token punctuation">}</span></span>
        <span class="token attr-name">quantity</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>quantity<span class="token punctuation">}</span></span>
        <span class="token attr-name">onQuantityChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setQuantity<span class="token punctuation">}</span></span>
      <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ShippingOptions</span></span> <span class="token attr-name">shipping</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>shipping<span class="token punctuation">}</span></span> <span class="token attr-name">onShippingChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setShipping<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Reviews</span></span> <span class="token attr-name">reviews</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>reviews<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>サイズ、数量、または配送オプションが変わるたびに、無関係なレビューセクションも含めてページ全体が再レンダリングされます。</p>
<p>より良い設計は、以下のようにこれらの関心事を分けます。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">ProductPage</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> productId <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ProductConfig</span></span> <span class="token attr-name">productId</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>productId<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ReviewsSection</span></span> <span class="token attr-name">productId</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>productId<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token function-variable function">ProductConfig</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> productId <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>selectedSize<span class="token punctuation">,</span> setSelectedSize<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">"medium"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>quantity<span class="token punctuation">,</span> setQuantity<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>shipping<span class="token punctuation">,</span> setShipping<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">"express"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// Product-specific logic</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span></span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ProductInfo</span></span>
        <span class="token attr-name">selectedSize</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>selectedSize<span class="token punctuation">}</span></span>
        <span class="token attr-name">onSizeChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setSelectedSize<span class="token punctuation">}</span></span>
        <span class="token attr-name">quantity</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>quantity<span class="token punctuation">}</span></span>
        <span class="token attr-name">onQuantityChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setQuantity<span class="token punctuation">}</span></span>
      <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ShippingOptions</span></span> <span class="token attr-name">shipping</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>shipping<span class="token punctuation">}</span></span> <span class="token attr-name">onShippingChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setShipping<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span></span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token function-variable function">ReviewsSection</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> productId <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>reviews<span class="token punctuation">,</span> setReviews<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">fetchReviews</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>setReviews<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>productId<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Reviews</span></span> <span class="token attr-name">reviews</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>reviews<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>この構造では、製品サイズを変えてもレビューが再レンダリングされることはありません。メモ化も不要です。コンポーネントの境界を明確にするだけです。</p>
<h2>差分検出とクリーンアーキテクチャ</h2>
<p>差分検出に関するこうした理解は、クリーンアーキテクチャの原則とも完全に一致します。</p>
<ol>
<li><strong>単一責任の原則</strong>：コンポーネントを変更する理由は1つに限定します。各コンポーネントが1つの責任に徹することで、不要な再レンダリングがトリガーされる可能性が低くなります。</li>
<li><strong>依存関係逆転</strong>：コンポーネントは具体的な実装ではなく、抽象化に依存するべきです。そうすることで、コンポジションを通じてパフォーマンスを最適化しやすくなります。</li>
<li><strong>インターフェース分離</strong>：コンポーネントには、最低限の特化したインターフェースを与えます。そうすることで、propsの変更が不要な再レンダリングをトリガーする可能性が低くなります。</li>
</ol>
<h2>実践的ガイドライン</h2>
<p>差分検出に関する深掘りに基づく実践的なアドバイスをいくつか紹介します。</p>
<ol>
<li><strong>コンポーネントの定義は親コンポーネントの外に置き</strong>、再マウントを回避する。</li>
<li><strong>stateをリフトダウンし</strong>、再レンダリングの境界を分離する。</li>
<li><strong>同じ位置のコンポーネントタイプの一貫性を保ち</strong>、アンマウントを回避する。</li>
<li><strong>keyを戦略的に利用する</strong>。リストに限らず、コンポーネントの識別情報を制御したい場合に使用する。</li>
<li><strong>再レンダリング問題をデバッグする際</strong>、要素ツリーとコンポーネントの識別情報の観点で考える。</li>
<li><strong>React.memoは差分検出の制約の範囲内で有効なツール</strong>にすぎないことを念頭に置く。基本的なアルゴリズムは変わらない。</li>
</ol>
<h2>おわりに</h2>
<p>Reactの差分検出アルゴリズムを理解すると、Reactのパフォーマンスに関するさまざまなパターンの仕組みが見えてきます。コンポジションがなぜこれほどまでに有効なのかや、リストにkeyが必要である理由、他のコンポーネント内でコンポーネントを定義するのがなぜ問題なのかがわかります。</p>
<p>この知識を身につけることで、アーキテクチャに関してより良い判断ができるようになり、Reactアプリケーションのパフォーマンスも自然と向上します。Reactの差分検出アルゴリズムに過剰なメモ化で対抗するのではなく、Reactがコンポーネントを識別し、更新する方法に合わせたコンポーネント構造を設計することで、うまくReactを使いこなせるようになるはずです。</p>
<p>次にReactアプリケーションを最適化する際には、コンポーネント構造が差分検出プロセスにどのような影響を及ぼしているのかを考えてみてください。Reactがコンポーネントを識別し、更新する方法を踏まえた、よりシンプルで特化したコンポーネントツリーが最も効果的な最適化である場合もあります。</p>]]></content:encoded></item><item><title><![CDATA[ポストデベロッパー時代]]></title><description><![CDATA[今から2年前の2023年3月に“The End of Front-End Development”（フロントエンド開発の終焉）という記事を投稿しました。ちょうどOpenAIがGPT-4を発表した後で…]]></description><link>https://postd.cc/the-post-developer-era/</link><guid isPermaLink="false">https://postd.cc/the-post-developer-era/</guid><category><![CDATA[キャリア・働き方]]></category><category><![CDATA[生成AI]]></category><pubDate>Thu, 26 Jun 2025 00:00:01 GMT</pubDate><content:encoded><![CDATA[<p>今から2年前の2023年3月に“<a href="https://www.joshwcomeau.com/blog/the-end-of-frontend-development/">The End of Front-End Development</a>”（フロントエンド開発の終焉）という記事を投稿しました。ちょうどOpenAIがGPT-4を発表した後で、多くの人が近い将来、ソフトウェアはすべて機械が作るようになり、人間のソフトウェア開発者は不要になるだろうという考えに傾いていました。</p>
<p>筆者はこうした論調には懐疑的で、当面の間、ソフトウェア開発は依然として人間の助けを必要とするだろうと前述の記事の中で主張し、その理由を論じました。筆者が立てた仮説は、大規模言語モデル（LLM）は人間の開発者を置き換えるのではなく、補強するというものです。</p>
<p>当時、Twitter上ではAIの登場により数カ月、長くても1、2年で人間のフロントエンド開発者の需要は無くなるとの意見が大勢を占めていました。あれから2年以上が経ちますが、現実はどうでしょうか。当時言われていたような「ポストデベロッパー」時代は到来しているでしょうか。</p>
<p>この記事では、現在の状況を新たな視点から捉え直し、何がどう変わったのか、今後どのような進化の過程を辿るのかを検討してみたいと思います。この記事が、開発者を志しながらも今後のキャリアに不安を覚えている読者の参考になれば幸いです。❤️</p>
<h2>企業によるAIの利用状況</h2>
<p>ここ数年、AIツールを導入する企業がますます増えています。最近、フォーブス誌に“<a href="https://www.forbes.com/sites/jackkelly/2024/11/01/ai-code-and-the-future-of-software-engineers/">AI Writes Over 25% Of Code At Google</a>”（Google社では25%以上のコードをAIが書いている）という記事が掲載されました。</p>
<p>この記事のタイトルは、AIが25％、人間が残りの75%の仕事を行っているように読めますが、実際はそうではありません。これは誤解を招くタイトルだと思います。</p>
<p>Google社でコミットされるコードの25%をAIが生成していたとしても、AIが単独で作業を行っているわけではありません。熟練した人間の開発者が運転席に座り、知識と経験をもとにAIを操り、生成物を編集したり形成したりした上で、自分が書いたコードに組み込んでいるのです。筆者の知る限り、Google社では今もコードは100%「開発者」が作成しています。AIは彼らが仕事で使う多くのツールの一つに過ぎないのです。</p>
<p>つまり、Google社は製品チームの開発者の25％を解雇し、疑似知性を持つAIロボットがその代わりを務め、プロダクトマネージャー直属の部下として自律的に作業を行っているわけではないということです。大手ハイテク企業でそのようなことが起きているという話は聞いたことがありません。</p>
<p>一方で、自社のAIが人間の開発者を完全に置き換えることができると主張しているスタートアップ企業はあります。中でも最もよく知られているのは、1年前の2024年3月にCognition社が発表したDevinという製品です。しかし、実際に企業が使おうとすると、さまざまな問題に直面します。例えば、あるチームの報告によると、Devinは割り当てられた<a href="https://www.answer.ai/posts/2025-01-08-devin.html">20件のタスクのうち3件しか完了できず</a>、結局は手間がかかり導入する価値はないと判断されたようです。このチームは1カ月で使うのをやめました。</p>
<p>一部のメンバーの声を紹介します。</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p>AIは十分に定義された小さなタスクはこなせますが、自分でやった方が早く、好きなやり方でできるので、その方がいいです。時間を節約できそうな大きなタスクは失敗する可能性が高いと思います。結局、使いたい場面があまりないという印象です。</p><p>-- ジョノ・ウィテカー<br></p></div></div>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p>自分で手を加えられるので、最初はすごく身近に感じられて感動したのですが、使っているうちに変更が必要な点がどんどん出てきて、次第に不満が溜まるようになりました。最終的には一から順番にやり直した方がいいという結論に達しました。</p><p>-- アイザック・フラス<br></p></div></div>
<p>これらの意見はAI懐疑論者のものではなく、AI関連のスタートアップ企業に在籍し、熱意と善意を持って製品を使ってみた技術者のものです。また、彼らの体験は例外的ではありません。他にもいくつか実際にAIを使用した人の体験談・ブログなどを読みましたが、どれもあまり役に立たないという結論で一致していました。</p>
<p><strong>筆者の知る限り、AI導入の成功事例には必ず熟練した人間の開発者が関わっています</strong>。したがって、ポストデベロッパー時代は到来していないと言えるでしょう。</p>
<h2>逸脱するAI</h2>
<p>筆者自身、数年前から多数のAIツールを試してみています。数カ月前、AI駆動の統合開発環境（IDE）である<a href="https://www.cursor.com/">Cursor</a>に乗り換えました。Claude SonnetでCursorの「エージェント」モードを使っていますが、率直に言ってかなり素晴らしいです。特定の種類のタスクでは、コンテキスト情報を与えて正しい方向を示せば、実行可能なソリューションを一発で生成してくれます。</p>
<p>TypeScriptの型のエラーやリンターエラーを検出し、多くの場合、修正することもできます。新たな発見や学びが得られたことも何度かありました。筆者が知らないAPIを使ったソリューションをAIが提案してくれたおかげで、当初検討していたものよりも良いコードが書けました。</p>
<p><strong>しかし、AIは完璧ではありません</strong>。誘導する必要があります。</p>
<p>感覚的には、”クルーズコントロール”を使って高速道路を走るのに近いかもしれません。車は概ね進行方向に沿って走りますが、フラフラしないようハンドルを握る必要があります。そうしないと、車が徐々に車線からはみ出してしまいます。時折車線内に戻してあげないと、側溝に落ちてしまいます。</p>
<p><strong>開発者不要論の問題点はまさにそこにあります</strong>。コードの書き方を知らなければ、モデルが生成したコードに重大な問題が潜んでいたとしても、気づくことができません。軌道修正の図り方も、軌道修正が必要であることも分からないでしょう。</p>
<p>LLMを使ってノーコードでプロジェクトを構築した人の話を聞くと、みな同様の経験をしています。出だしは好調でも、やがてどうAIを促してもこれ以上は進めないという局面に行き着きます。コードはちぐはぐで混沌とし、ある一定の線を越えるといくら応急処置を施しても維持できなくなります。そうなると、プログラム全体が破綻してしまいます。</p>
<p>また、LLMが不得意なタスクもたくさんあります。10分かけてClaudeに意図を理解させようとするもできず、諦めて自分で実装したら5分でできたというようなストレスの溜まる経験をしたことも何度かあります。次第にどのタスクをAIに任せ、どのタスクは昔ながらの方法で処理するのがいいか直感で分かるようになってきました。</p>
<p>総合すると、LLMはかなりの時間を節約してくれます。自分でやると30分かかる作業をLLMが30秒で終わらせてくれたこともあります。そういう時は本当に爽快です。しかし、実際のところ、自らコードを書いている時間の方がまだ圧倒的に多いです。</p>
<p>プロレスのタッグマッチのように、Claudeが得意なタスクではタッチして交代し、処理してもらいます。しかし、まだ自分でやるほうが速く簡単なので、ほとんどのコードは自分が書いています。</p>
<h2>現在の就職市場</h2>
<p>何年か前にこの記事を書いたとき、就職市場はかなり厳しい状況にありました。残念ながら、現在もまだ厳しい状況は変わっていません。</p>
<p>求職中の読者は、以前ほど質の高い求人がなく、良い求人には応募が殺到することをご存知でしょう。面接の機会を得るのも非常に難しく、内定をもらうのがいかに難しいかは言うまでもありません。</p>
<p>しかし、この状況は企業が開発者を自律型AIエージェントに置き換えているからではないと思います。すでに述べたとおり、筆者がこれまで読んできた実際の体験談はその仮説を裏付けるものではありません。では何が起きているのでしょうか。なぜこれほど厳しい状況が続いているのでしょうか。</p>
<p>これにはいくつかの要因が絡んでいると思います。</p>
<ol>
<li><strong>マクロ経済的要因</strong>。金利がまだ比較的高く、スタートアップ企業は事業を拡大し開発者を雇うのに必要な資金を調達するのが難しい状況にあります。ここ数年、景気後退が間近に迫っているという景況感が続いています。</li>
<li><strong>レイオフ</strong>。大手ハイテク企業は、ここ数年の間に数十万人の労働者をさまざまな理由で一時的に解雇しています。これはつまり、多くの有能な開発者が仕事を探しているということです。</li>
<li><strong>AI神話</strong>。一部の企業は、いまだにAIが近い将来開発者を不要にすると考えており、積極的な雇用を行っていません。</li>
</ol>
<p>3つ目に挙げた点については特にもどかしい思いがします。AGI* が実用化直近であり、実用化されれば人間の開発者は一切不要になると信じて企業は必要な開発者の雇用を控えているのです。「じきにそうなります」と言い始めてもう数年が経ちます。</p>
<p>*汎用人工知能: 人間のように学習・推論したり、まだ学んでいないこともこなすことができるAI。</p>
<h2>今後の展望</h2>
<p>2023年に“<a href="">The End of Front-End Development</a>”を執筆した当時、筆者は開発者を目指しキャリアをスタートさせたばかりで、コーディングを勉強中の読者にメッセージを届けたいと考えていました。世間の見通しがあまりに暗かったため、ネット上に渦巻く FUD* を少しでも解消したいと思っていました。</p>
<p>*不安や懸念、不透明感</p>
<p>過去2年間でさまざまな変化がありましたが、変わっていないことが2つあります。</p>
<ol>
<li>企業は製品を作るためにまだ人間の開発者を必要としています。</li>
<li>AIエバンジェリストは、もうじき企業は製品を作るのに人間の開発者が不要になるといまだに主張しています。</li>
</ol>
<p>開発者を目指し大学やブートキャンプ、または独学で勉強中の読者もいると思いますが、<strong>皆さんが就職するチャンスはあると確信しています</strong>。ソフトウェア開発が完全に自動化されるのはまだかなり先の話だというのは明らかです。開発者の代わりではなく、開発者の生産性を高めるツールとしてAIを活用した方が遥かに効果的であることに企業が気づけば、自らの成長を妨げるような行為をやめ、より積極的に雇うようになると思います。</p>
<p>AIモデルは間違いなく改善し続けるでしょう。毎週のように新しいモデルがリリースされてはベンチマークの記録を更新しています。最近では、Google社が<a href="https://deepmind.google/technologies/gemini/">Gemini 2.0 Flashと2.5 Pro</a>モデルを発表しました。</p>
<p><img src="https://www.joshwcomeau.com/images/the-post-developer-era/chart-speed.png"></p>
<p><img src="https://www.joshwcomeau.com/images/the-post-developer-era/chart-intelligence.png">
出典：<a href="https://artificialanalysis.ai/">Artificial Analysis</a></p>
<p>テクノロジーは進化の曲線がより緩やかになるポイントに達したように筆者は感じています。ゲームチェンジャーと呼べるような発表はしばらく出ていません。新たに出てくるモデルは少しずつ改良されてはいますが、全く新しい課題を解決するというより、すでに優れている点をさらに向上させるものです。</p>
<p>現在の就職市場は見通しが厳しいと感じますが、少なくとも米国では正しい方向に進んでいます。</p>
<p><img src="https://www.joshwcomeau.com/images/the-post-developer-era/yoy-graph.jpg">
出典：<a href="https://bsky.app/profile/josephpolitano.bsky.social/post/3ljs5yzcg3c2k">Joey Politano</a>のラブリーなグラフ！</p>
<p>AIによって本当にソフトウェア開発者が不要になっているのであれば、ハイテク系の雇用数は急速に減少しているはずだと思いますが、この1年の雇用数は増えています。この傾向が続けば、近い将来市場の見通しはかなり明るくなるでしょう。</p>
<h2>懸念点</h2>
<p>2023年の時点で、AIがすぐにソフトウェア開発者の仕事を奪うことにはならないという自信がそれなりにありました。それから2年が経ち、その自信は一層深まりました。コーディングは依然として極めて貴重なスキルであり、それがすぐに変わるとは思えません。</p>
<p>とは言え、何もかも順調で誰も心配する必要はないと言っているわけでもありません。</p>
<p>昨今の世界情勢は、経済をはじめ、あらゆる面で広範な影響と不確実性をもたらしているように思います。これらがハイテク業界に与える影響については予測が難しいものの、私としては挑戦的な時期となるのではないかと考えています。</p>
<p>次の世代の開発者について少し心配しています。LLMエージェントを使っているとトランス状態に陥り、生成されるコードを理解しないまま、あるいは見ることさえしないまま、機械的に変更を受け入れるようになりがちです。<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>筆者も、<a href="https://whimsy.joshwcomeau.com/">新しいコースのランディングページ</a>をビルドしている際にその罠にはまりそうになっていることに気づきました。ハンドルから長く手を離し過ぎてしまったため、奇妙なジャンクコードのリファクタリングに多くの時間を費やすはめになりました。</p>
<p>最も楽な道は、何もせず機械に任せることですが、そうすると機械が行き詰まったときにコードを修正したり、デバッグしたりするのに必要なスキルが身につきません。</p>
<p>一方で、LLMを積極的に利用するのであれば、今がコーディングを学ぶ絶好の機会です。筆者の場合、よく分からないTypeScriptエラーが出たときに、AIが理解を助けてくれたり、適切な資料を見つけるために必要なキーワードを示してくれたりすることが多々あります。まるで自分だけの個人講師が不明点を理解する手助けをしてくれているような感覚です。<sup id="fnref-2"><a href="#fn-2" class="footnote-ref">2</a></sup></p>
<p>今後数年間がどうなるかは誰にも分かりませんが、1、2年後に企業がようやく人間の開発者がまだ必要であることを受け入れ、熟練した人間が強力なLLMを使うことで驚くべきことを成し遂げられることに気づき、ちょっとした「開発者ルネサンス」が起きても驚きません。✨</p>
<p>ソフトウェア開発に情熱を持ち、この仕事が高い給料を稼いでアッパーミドルクラスの暮らしを手に入れられる見込みが最もありそうだと考えているのであれば、AIの誇大宣伝に惑わされ、自信を失わないでいただきたいと思います。企業は今も雇用し続けていますし、それは当分変わらないと思います。💖</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><h2>就職活動のヒント</h2><p>現在の就職市場で仕事を勝ち取るチャンスを最大限に高める方法について話したいと思います。</p><p>最初に理解しておくべきことは、AIの登場により企業の雇用プロセスが悲惨なものになっているということです。求人を出すと、AIが生成した質の低い応募書類が何千通も届く場合があり、採用担当者にとってもすべての書類に目を通すのは困難です。求人に応募しても、他の応募書類の山に埋もれてしまう可能性が高いでしょう。</p><p>この状況に対する解決策は2つあります。</p><ul>
<li>求人が出てから数日以内の早い段階で応募する。</li>
<li>コネを頼る。</li>
</ul><p>ハイテク業界に入ってまだ日が浅い場合、おそらく人脈と呼べるようなものはないでしょう。でも大丈夫。今日から築き始めればいいのです。 😄</p><p>方法はいくつもあります。一番手っ取り早いのが、地元で開催されているオフ会を探すことです。こうしたイベントに参加すれば、自分が就職したいと思っている会社で働いている開発者に会える可能性があります。あなたの応募書類が埋もれてしまわないよう手助けしてくれるかもしれません。</p><p>しかし正直なところ、これは筆者のように内向的な読者にとっては最適な方法ではないかもしれません。筆者が駆け出しの頃は、地元のオフ会に参加しても気後れして初対面の人に話しかける勇気がありませんでした。結局コネを作ることはできず、何の役にも立ちませんでした。 😅</p><p>幸い、他にも多くの選択肢があります。筆者の場合は、自分がビルドしたプログラムをネット上に公開することでネットワークを築きました。オープンソースプロジェクトに貢献したり、Discordコミュニティで交流したりしてもいいでしょう。筆者のブログ記事“<a href="https://www.joshwcomeau.com/career/no-cs-degree-required/">Becoming a Software Developer Without a CS Degree</a>”（CSの学位がなくてもソフトウェア開発者になれる方法）に書いたように、自分の強みを生かすことが成功への近道になります。</p><p>また、コミュニティもチャンスを見つける上で助けになる場合があります。腹立たしいことに、ネット上には単にデータを集めることだけを目的とした、架空の会社の求人が無数にあります。自分のLinkedInの連絡先に登録されているユーザーの勤務先をチェックし、注目すべき実在する企業のリストを作成するのも良いでしょう。</p><p>厳しい世の中ですが、これらのヒントが時間と労力の節約になれば幸いです。❤️</p></div></div>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">イケてる若者の間では"vibe coding"と呼ばれている<a href="#fnref-1" class="footnote-backref">↩</a></li>
<li id="fn-2">ただし、その家庭教師は時々夢想的になるので、提案には少し用心しなければならない😂<a href="#fnref-2" class="footnote-backref">↩</a></li>
</ol>
</div>]]></content:encoded></item><item><title><![CDATA[React Server Componentsを理解する]]></title><description><![CDATA[React Server Componentsを理解する React Server Components（RSC）の登場により、Reactエコシステムにおけるサーバーレンダリングの重要性が高まりまし…]]></description><link>https://postd.cc/understanding-react-server-components/</link><guid isPermaLink="false">https://postd.cc/understanding-react-server-components/</guid><category><![CDATA[開発手法・プロジェクト管理]]></category><category><![CDATA[React]]></category><pubDate>Thu, 22 May 2025 00:00:01 GMT</pubDate><content:encoded><![CDATA[<h2>React Server Componentsを理解する</h2>
<p>React Server Components（RSC）の登場により、Reactエコシステムにおけるサーバーレンダリングの重要性が高まりました。RSCを使用することで、デベロッパーは一部のコンポーネントをサーバー側でレンダリングしつつ、抽象化によりクライアントとサーバーの隔たりを感じさせないユーザビリティを実現することができます。Client ComponentsとServer Componentsをコード内に混在させることで、すべてのコードが1カ所で実行されているように見せることができます。</p>
<p>しかし、抽象化には常にコストが伴います。そのコストとはどのようなものでしょうか。RSCはいつ「使える」のでしょうか。バンドルサイズが小さくなると、帯域幅も狭まるのでしょうか。RSCを「使うべき」ときはいつでしょうか。RSCを適切に使う上でデベロッパーが従うべきルールは何でしょうか。そのルールが存在する理由は何でしょうか。</p>
<p>これらの問いに答えるため、RSCの仕組みを詳しく見ていきましょう。React本体と、ReactのメタフレームワークというRSCの2つの側面について考察します。特に、RSCに関する正確なメンタルモデルを構築できるよう、ReactとNext.jsの内部構造について説明します。</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p>この記事は、Reactを使い慣れたデベロッパーを対象としています。読者には、コンポーネントやフックに関する予備知識が前提条件として求められます。</p><p>また、JavaScriptのPromise、async、awaitについても熟知していることを想定しています。これらに関する知識がない方は、<a href="https://youtu.be/fyGSyqEX2dw?si=MkRII6BoKW8Dm-Ml">Promise、async、await</a>の仕組みに関する筆者のYouTube動画をご覧ください。</p><p>Reactのあらゆる側面についてゼロから詳しく知りたい方は、筆者の「<a href="https://understandingreact.com/">Understanding React</a>（Reactを理解する）」コースをぜひチェックしてみてください。このコースでは、Reactのソースコードを掘り下げることで、JSX、Fiber、コンポーネント、フック、フォームなどの仕組みを理解していただくことができます。</p></div></div>
<p>まずは、RSCの仕組みを理解するために必要な基本を見ていきましょう</p>
<h2>DOMとクライアントレンダリング</h2>
<p>Reactでは「レンダリング」という表現を固有の意味で使っています。一般的にはブラウザがページを「レンダリング」するという場合、DOMを画面に描画する処理をいいます。ブラウザは、DOM（要素のツリー構造）とCSSOM（計算済みスタイルのツリー構造）をもとに、各要素の配置を計算し、適切なピクセルを画面に描画します。</p>
<p>一方、Reactでは「DOMの見た目を計算すること」を「レンダリング」といいます。関数コンポーネントが返す値はReactに対し、DOMの見た目に関する情報を伝えます。</p>
<p>したがって、React（およびReactに追随する形で登場した他のフレームワーク）の世界では、「クライアントレンダリング」という場合、<strong>ブラウザ上で関数コンポーネントを実行すること</strong>をいいます。</p>
<p>Reactでいう「レンダリング」は、必ずしもブラウザによる実際のレンダリングを伴いません。なぜなら、DOMの見た目がすでにReactがこうあるべきと考える見た目になっている場合があるからです。</p>
<p>実際、Reactのコアアーキテクチャ（および他のすべてのJSフレームワーク）における重要な点は、内部コードが更新するDOMの量を制限することにあります。</p>
<h2>ツリーの差分検出処理</h2>
<p>Reactのソースコードの中には、<code class="language-text">appendChild</code>などのブラウザDOM APIを呼び出し、クライアント上のDOMを更新するコールがあります。Reactは、ツリーの差分検出処理（reconciliation）や差分計算（diff）によって、ブラウザDOM APIを実行するタイミングを決めます。</p>
<p><a href="https://tonyalicea.dev/blog/understanding-react-compiler">React Compiler</a>に関する記事に書いたように、ReactはDOMの現在の見た目と、見た目がどうあるべきかをJavaScriptオブジェクトツリーで管理しており、各ノードはFiberと呼ばれます。</p>
<p>Reactは、JavaScriptオブジェクトツリーの2つの枝でDOMの見た目がどうあるべきか（「WORK-IN-PROGRESS」）を計算し、DOMの現在の見た目（「CURRENT」）と比較します。</p>
<p>次に、2つのツリーの差分を検出し、currentツリーをworkInProgressツリーに変換するために必要なステップを計算します。そのステップは「diff」と「patch」（パッチ処理）です。</p>
<p><img src="https://tonyalicea.dev/assets/blogimages/ReactCompiler_Reconciliation.png"></p>
<p>単純なJavaScriptオブジェクトとの比較計算が終わると、実際のDOMにおいて取るべきステップが明らかになります。DOMの更新はコストがかかり、ブラウザの再レンダリング（要素の配置とピクセルの描画）を伴うため、最低限取るべきステップの数を特定することで、DOMに対して必要な更新を最小限にとどめることができます。</p>
<p>クライアント上でDOMを更新すれば、UIを更新する際にstateを保持できるメリットがあります。例えば、ユーザーがフォームに情報を入力し、Reactが何らかのイベントをもとにUIを更新しても、入力したテキストはフォーム上に保持されます（ページが再読み込みされた場合は保持されません）。</p>
<p>したがって、Reactではクライアント上でDOMを更新することを重視しており、最初に偽のDOMに対して処理を行うことで可能な限り効率的に更新しようと努めています。このようなJavaScriptのDOM構造の偽コピーを一般的に「仮想DOM」と呼びます。</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p><strong>「仮想DOM」は適切な表現か？</strong></p><p>ReactにおけるDOMと似た構造のJavaScriptオブジェクトの集まりを以前は「仮想DOM」と呼んでいました。しかし、実際はiOSやAndroidのネイティブアプリ（React Native）などへのレンダリングが可能なため、Reactは今ではこの呼称を好んでいません。</p><p>実際、Reactは複数のツリーを扱います。関数コンポーネントが返すReact要素（JSオブジェクト）のツリーや、React要素を変換したもので、stateなどを格納するために使用される Fiber（同じくJSオブジェクト）のツリーなどです。 </p><p>普段はより具体的な名前でこれらのツリーを呼びますが、この記事では、RSCの仕組みを理解するうえで便利なので、昔から日常会話でよく使われている「仮想DOM」という呼称を使いたいと思います。</p></div></div>
<p>Reactが「レンダリング」と呼ぶのは、仮想DOMの計算処理です。具体的には、関数コンポーネントを実行し、リアルDOMの見た目がどうあるべきかを決めることをいいます。この処理はすべて関数を実行するJavaScriptエンジンの中で、リアルDOMに反映される差分が全く検出されなくなるまで行われます。</p>
<p><strong>Reactが「レンダリング」という表現を使う理由</strong>を理解すると、RSCを正確に説明するうえで非常に役立ちます。</p>
<p>RSCを理解するためには、Web開発におけるクライアントレンダリングおよびサーバーレンダリングと、Reactが重点を置く仮想DOMの生成の違いを明確に理解する必要があります。</p>
<p>Reactでいう「レンダリング」では、実際には必ずしも何かが目に見える形で起こるわけではありません。</p>
<p><img src="https://tonyalicea.dev/assets/blogimages/tonyalicea_cartoon1_dark.png"></p>
<p>「レンダリング」という言葉のさまざまな意味については、確認しながら説明を進めたいと思います。一般的な（React以外での）定義を「古典的」と呼び、Web開発の用語として定義してみましょう。</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p>レンダー【render】
【動詞】 /réndər/</p><ol>
<li>（古典的クライアントサイド）DOMとCSSOMをもとにレイアウトを計算し、画面にピクセルを描画すること。</li>
<li>（Reactクライアントサイド）仮想DOMを構築して更新するために関数コンポーネントを実行すること。 </li>
</ol></div></div>
<h2>DOMとサーバーレンダリング</h2>
<p>RSCについて説明を進めるには、時間を遡る必要があります。長年、インターネットの中心概念の一つとして、サーバーからHTMLを配信するという考え方があります。</p>
<p>サーバー上にHTMLファイルを（NodeJSやPHPなどのサーバー技術を使って）作成することを、古典的な「サーバーサイドレンダリング」や「サーバーレンダリング」と呼びます。これは、先ほど説明した古典的なクライアントレンダリングの意味とも違います。従来、サーバーレンダリングとはサーバー上で「HTMLの文字列を生成する」ことを意味しました。</p>
<p>これにはいくつかの利点があります。ブラウザはHTMLを素早くDOMに変換することができます。そのため、HTMLは「ブラウザ上で素早くレンダリング」されます。JavaScriptでDOMを更新すると、もっと時間がかかります。また、サーバーの方がデータベースやファイルストレージに近いため、これらの処理をより効率的に行うことができます。</p>
<p>デメリットとしては、クライアントは再度HTMLを要求することができますが、stateが失われてしまいます（ページが再読み込みされる）。</p>
<p>Web開発においては、そのバランスをどう取るかが長年課題となっていました。サーバー側でレンダリングされたHTMLは素早く表示されますが、クライアント側のJavaScriptを通じてDOMを更新すると、ページのstateを維持したまま変更を加えることができます。</p>
<p>Reactでは両方行えますが、それは何も新しいことではありません。Reactはクライアント側のJavaScriptを使ってDOMを更新しますが、デベロッパーはかなり前からReactのコンポーネントをサーバー側でレンダリング（SSR）することができていました。</p>
<p>サーバー（NodeJSなどを通じて独自のJavaScriptエンジンを実行）は、コンポーネントを実行し、クライアントに送信するHTML文字列を生成します。ただし、大きな注意点があります。同じコンポーネントのJavaScriptコードもすべてクライアントに送信して実行する必要があったのです。</p>
<p>それはなぜでしょうか。関数コンポーネントが返す値をもとに仮想DOMを構築できるようにするためです。仮想DOMはリアルDOMを「ハイドレート」するために使用します。これは例えば、ボタンがクリックされたときに、どの関数コンポーネントの中のどのクリックイベントを実行するべきかがわかるということです。覚えておかなくてはならないのは、Reactが機能するためには、クライアント上に両方のツリー（DOMと仮想DOM）が存在する必要があるということです。したがって、ReactにおけるSSRでは、関数を2回実行する必要があります（1回はサーバー上でHTMLを生成するため、もう1回はクライアント上で仮想DOMを作成するため）。</p>
<p>参考までに、SSR／ハイドレーションプロセスを以下に可視化しています。</p>
<div class="video"><video loop autoplay muted playsinline aria-labelledby="video-label" src="https://tonyalicea.dev/assets/blogvideos/RSC_SSRHydration.mp4"></video></div>
<p>ここでReact Server Componentsの登場です。RSCは、サーバー上で実行されるReactコンポーネントとクライアント上で実行されるReactコンポーネントを、サ<strong>ーバーコンポーネントのJavaScriptコードの送信と再実行を行うことなく</strong>混在させることができます。さらに、ブラウザ上でDOMの更新を始める前に、最初にサーバー上でHTMLをレンダリングすることも可能です。</p>
<p>なぜ可能なのでしょうか。</p>
<p>まずは用語の定義を更新しましょう。</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p>レンダー【render】
【動詞】 /réndər/レンダリング</p><ol>
<li>（古典的クライアントサイド）DOMとCSSOMをもとにレイアウトを計算し、画面にピクセルを描画すること。</li>
<li>（Reactクライアントサイド）仮想DOMを構築して更新するために関数コンポーネントを実行すること。 </li>
<li>（古典的サーバーサイド）DOMを構築するためにクライアントに送信するHTMLを生成すること。</li>
<li>（ReactサーバーサイドSSR）DOMを構築するためにクライアントに送信するHTMLを生成するために関数コンポーネントを実行すること。</li>
</ol></div></div>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p><strong>サーバーサイド生成はどうか?</strong></p><p>ここで取り上げていないものの一つがSSG（サーバーサイド生成）です。これは、アプリをビルドする（デプロイ可能にする）際に、HTMLをあらかじめ生成することを意味します。これは、Client ComponentsとServer Componentsの両方に対して行えます。</p><p>SSGの定義は、作成中の辞書収録項目におけるSSRの定義と同じです。この記事では、SSGとSSRの違いを明らかにしてもあまり役に立たないので詳しくは述べませんが、SSGもサポートされています。</p></div></div>
<p>先ほど、Reactが機能するためには、DOMと仮想DOMの両方のツリー全体がブラウザのメモリ上に存在しなくてはならないとお話ししました。では、React Server Componentsがサーバー上でのみ実行されればよく、クライアントがJavaScriptコードをダウンロードして実行する必要がないのはなぜでしょうか。</p>
<p>つまり、Reactは「サーバー」上で実行された関数によってレンダリングされた部分について、どのようにして「ブラウザ」上に仮想DOMを構築するのでしょうか。</p>
<h2>Flight</h2>
<p>サーバー上で関数コンポーネントを実行し、その結果をもとにクライアント上に仮想DOMを構築できるようにするために、Reactはサーバー上で実行された関数が返したReact要素ツリーを「シリアライズ」する機能を追加しました。</p>
<p>多くの場合、シリアライズは「コンピューターのメモリ上のオブジェクトを文字列に変換する」こと、デシリアライズは「文字列をコンピューターのメモリ上のオブジェクトに復元する」ことを意味します。</p>
<p>この場合、関数コンポーネントの結果はシリアライズしてクライアントに送信する必要があります。</p>
<p>筆者のReactコースに登録した生徒の数を把握するための簡単なアプリを作成するとします。まずはNext.jsで基本的なRSCを作成します。これはサーバー上で実行されます。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Home</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">understandingreact.com</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p><strong>同型コンポーネント</strong></p><p>サーバーとクライアントの両方で実行可能なコンポーネントを「同型（isomorphic）」と呼びます。上の関数はサーバーに特化したこと（データベースに直接接続する、サーバー上のファイルを読むなど）を何も行わないため、クライアント上で実行することも可能であり、Reactは通常どおり直接その結果をもとに仮想DOMを構築することができます。</p><p>関数が同型である場合、共有することができます。Server ComponentsとClient Componentsのどちらもそれをインポートして使用することができます。</p></div></div>
<p>この関数を実行するためにクライアントに送信しなくてもいいように、その結果をシリアライズする必要があります。Reactのコードベースの中では、このシリアライズ形式を「Flight」と呼び、送信したデータの総和を「RSC Payload」と呼びます。</p>
<p>筆者の簡単な関数の結果をシリアライズしたものが以下です。</p>
<div class="gatsby-highlight" data-language="text"><pre style="counter-reset: linenumber NaN" class="language-text line-numbers"><code class="language-text">"[\"$\",\"main\",null,{\"children\":[\"$\",\"h1\",null,{\"children\":\"understandingreact.com\"},\"$c\"]},\"$c\"]"</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div>
<p>分析しやすいようにフォーマットしてみましょう（Alvar Lagerlöf氏が作成した<a href="https://github.com/alvarlagerlof/rsc-parser">RSCパーサー</a>を使用）。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token punctuation">{</span>
  <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"main"</span><span class="token punctuation">,</span>
  <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
  <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"h1"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
    <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token string">"understandingreact.com"</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>仮想DOMの構造が見えるでしょうか。<code class="language-text">main</code>要素と<code class="language-text">h1</code>要素、プレーンテキストノードもあります。props、特にReact特有の標準のchildren propsとして渡されているものも確認できます。</p>
<p>ここでは単純化した例を用いて説明していますが、フォーマットの要素はこれだけではありません。また、メタフレームワークを使用するとさらに多くの要素が追加されます。例えば、「Flight」を表す「f:」など、ツリーに追加するものを表す識別子などです。しかし、ここで必要な理解を得るためには単純化した例で十分です。</p>
<p>シリアライズのフォーマットはReactが提供していますが、Payloadを作成し、クライアントに送信する作業はメタフレームワーク（この場合はNext.js）が行う必要があります。</p>
<p>例えば、Next.jsはコードベースに<code class="language-text">generateDynamicRSCPayload</code>という関数があります。</p>
<p>メタフレームワークは、Payloadが確実に生成され、クライアントに送信されるようにします。Payloadのおかげで、Reactはクライアント上で正確な仮想DOMを構築し、差分検出処理を行うことができます。</p>
<h2>メタフレームワークとサーバーレンダリング</h2>
<p>先ほど、RSCからHTMLのレンダリングを行うことは「可能」だと話しました。そのように言ったのは、それが任意だからです。つまり、RSCからHTMLをレンダリングするかどうかはメタフレームワーク次第です。とは言え、そうすることは理にかなっています。</p>
<p>後で話すように、「体感パフォーマンス」は重要な指標です。すでにサーバー上でコードを実行していて、HTMLをストリーミングにより返せる場合、そうするべきです。なぜなら、ブラウザがそのHTMLを素早くレンダリングし、ユーザーの体感パフォーマンスが向上するからです。</p>
<p>メタフレームワークが遅いと感じられれば、誰も使いません。したがって、RSCを実装するReactのメタフレームワークは、古典的サーバーレンダリングとReactサーバーレンダリングの両方を行う必要があります。</p>
<p>古典的なサーバーレンダリング（HTMLを生成）ではページのレンダリング（ブラウザによる描画）が速く、Reactスタイルのサーバーレンダリング（RSC Payload）では後で行われるステートフルな更新のための仮想DOMが得られます。</p>
<p>したがって、実際にはRSCは「二重データ問題」を引き起こします。サーバーから同じ情報をHTMLとPayloadという2つの異なる形式で同時に送ることになります。これらはDOM（HTML）と仮想DOM（Payload）を即座に構築するために必要な情報です。</p>
<p>以下の図をご覧ください。</p>
<div class="video"><video loop autoplay muted playsinline aria-labelledby="video-label" src="https://tonyalicea.dev/assets/blogvideos/RSC_RSC.mp4"></video></div>
<p>例では、Next.jsがHTMLを返し、ブラウザはそれをもとにDOMを構築します。</p>
<div class="gatsby-highlight" data-language="html"><pre style="counter-reset: linenumber NaN" class="language-html line-numbers"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span>understandingreact.com<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">></span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span></span></pre></div>
<p>さらにPayloadも返し、Reactはそれをもとに仮想DOMを構築します。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token punctuation">{</span>
  <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"main"</span><span class="token punctuation">,</span>
  <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
  <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"h1"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
    <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token string">"understandingreact.com"</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>HTMLを送ることで、ブラウザがページを素早くレンダリングすることができます。ユーザーの画面には、ページが直ちに表示されます。また、Payloadを送ることで、Reactがページをインタラクティブにするために必要な作業を完了することができます。</p>
<p>このデータの重複に伴うコストは、サーバーがレスポンスを送る前に使う圧縮アルゴリズム（gzipなど）によって相殺できるという意見もあります。しかし、HTMLとJSONのようなPayloadは形式が異なるため、繰り返しの部分はあいまいになってしまい、帯域幅には二重データによる明らかな影響が生じます。</p>
<p>抽象化にはコストが伴います。ここでのコストは同じ情報を2回送る必要があることです。</p>
<p>これらのことを踏まえると、「レンダリング」の定義は全部で5つになります。</p>
<div class="admonition admonition-none alert alert--info"><div class="admonition-heading"><h5><span class="admonition-icon"><svg></svg></span>none</h5></div><div class="admonition-content"><p>レンダー【render】
【動詞】 /réndər/レンダリング</p><ol>
<li>（古典的クライアントサイド）DOMとCSSOMをもとにレイアウトを計算し、画面にピクセルを描画すること。</li>
<li>（Reactクライアントサイド）仮想DOMを構築して更新するために関数コンポーネントを実行すること。 </li>
<li>（古典的サーバーサイド）DOMを構築するためにクライアントに送信するHTMLを生成すること。</li>
<li>（ReactサーバーサイドSSR）DOMを構築するためにクライアントに送信するHTMLを生成するために関数コンポーネントを実行すること。</li>
<li>（ReactサーバーサイドRSC）仮想DOMを構築し、更新するためにクライアントに送るFlight（Payload）データを生成するために関数コンポーネントを実行すること。</li>
</ol></div></div>
<p>Reactの定義に共通する類似点にお気づきでしょうか。Reactでは、レンダリングは常に「関数コンポーネントの実行」を意味し、Client ComponentsとServer Componentsはいずれも仮想DOMの構築と更新に必要なものを提供します。</p>
<h2>Streams、Suspense、RSC</h2>
<p>アプリケーションを構築する際にはパフォーマンスが常に懸念点となります。しかし、パフォーマンスには2種類あります。実際のパフォーマンスと体感パフォーマンスです。</p>
<p>HTTPとブラウザは、両方のパフォーマンスを改善する方法として、長年ストリーミングをサポートしてきました。NodeJSの<a href="https://nodejs.org/en/learn/modules/how-to-use-streams">Stream API</a>や、ブラウザの<a href="https://developer.mozilla.org/en-US/docs/Web/API/Streams_API">Streams API</a>（特に、ブラウザの<a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream">ReadableStream</a>オブジェクト）などです。</p>
<p>React（およびRSCをサポートしたいメタフレームワーク）は、これらのコアテクノロジーを利用してHTMLとPayload両方のデータをストリーミングします。ストリーミングとは、「チャンク」と呼ばれる少量のデータに分けて送信することを意味します。クライアントは、その少量のデータを受け取ったものから順に処理することができます。
したがって、ストリーミングの場合は「何を送ったか」ではなく、「一定時間内に何を送ったか」が問題となります。</p>
<p>ブラウザは、ネットワークを介したHTMLのストリーミングに対応できるよう設計されています。ストリーミングで送られてくるHTMLを受け取りながらページのレンダリング（配置と描画）を行います。</p>
<p>同様に、Reactは後に解決してRSC PayloadデータになるPromiseを受け入れます。例えば、Next.jsはクライアント上にReadableStreamを設定し、サーバーからのストリームを読み込み、受け取ったものからReactに渡します。サーバーレンダリングに対するReactの全体的なアプローチとしては、必要な場所にコンテンツをストリーミングで送る方式が中心となっています。</p>
<p>実際、Flight形式自体に未完了の処理を示すマーカーが含まれています。Promiseや遅延読み込みなどです。</p>
<p>例えば、サーバーコンポーネントをasync関数として設定し、タイマーを待つとします。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token comment">// components/Delayed.js</span>
<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">delay</span><span class="token punctuation">(</span><span class="token parameter">ms</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token parameter">resolve</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> ms<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">DelayedMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token function">delay</span><span class="token punctuation">(</span><span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 2 second delay</span>
    
    <span class="token keyword">return</span> <span class="token punctuation">(</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">This message was loaded after a 5 second delay!</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// page.js</span>
<span class="token keyword">import</span> DelayedMessage <span class="token keyword">from</span> <span class="token string">"./components/DelayedMessage"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Home</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">understandingreact.com</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">DelayedMessage</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>async関数はPromiseを返します。その結果、Payloadは以下のようになります。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token punctuation">{</span>
  <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"main"</span><span class="token punctuation">,</span>
  <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
  <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
   <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
     <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"h1"</span><span class="token punctuation">,</span>
     <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
     <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token string">"understandingreact.com"</span>
     <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
     <span class="token string-property property">"$$type"</span><span class="token operator">:</span> <span class="token string">"reference"</span><span class="token punctuation">,</span>
     <span class="token string-property property">"id"</span><span class="token operator">:</span> <span class="token string">"d"</span><span class="token punctuation">,</span>
     <span class="token string-property property">"identifier"</span><span class="token operator">:</span> <span class="token string">"L"</span><span class="token punctuation">,</span>
     <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"Lazy node"</span>
    <span class="token punctuation">}</span>
   <span class="token punctuation">]</span>
  <span class="token punctuation">}</span> 
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><code class="language-text">DelayedMessage</code>コンポーネントがあるべき場所が、特別な識別子”L”でマーキングされている点に注目してください。これは、後でコンテンツが挿入される場所を示しているのです。</p>
<p>このコードを実行すると、遅延メッセージだけでなく、ページ全体が読み込まれるのに5秒かかります。</p>
<p>これは、Reactがクライアント向けに設計された特別な<code class="language-text">Suspense</code>機能を使用してPromiseや遅延読み込みに対処するためです。コンポーネントを更新して<code class="language-text">Suspense</code>を使うようにすると、以下のようになります。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">import</span> DelayedMessage <span class="token keyword">from</span> <span class="token string">"./components/DelayedMessage"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Suspense <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Home</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">understandingreact.com</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Suspense</span></span> <span class="token attr-name">fallback</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">Loading...</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">DelayedMessage</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Suspense</span></span><span class="token punctuation">></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>この状態でページを実行すると、最初にフォールバックが表示され、遅れているメッセージが5秒後に表示されます。しかし、このコンポーネントがまだサーバー上で実行されていることに注目してください。どうすればサーバー上で<code class="language-text">Suspense</code>を使えるのでしょうか。使えません。関数から返されたPayloadをクライアント上で処理すると、境界を含む仮想DOMが構築されます。</p>
<p>Payloadは以下のようになります。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token punctuation">{</span>
  <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"main"</span><span class="token punctuation">,</span>
  <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
  <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
   <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
     <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"h1"</span><span class="token punctuation">,</span>
     <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
     <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token string">"understandingreact.com"</span>
     <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
     <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"$$type"</span><span class="token operator">:</span> <span class="token string">"reference"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"id"</span><span class="token operator">:</span> <span class="token string">"d"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"identifier"</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
      <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"Reference"</span>
     <span class="token punctuation">}</span><span class="token punctuation">,</span>
     <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
     <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"fallback"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
       <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"p"</span><span class="token punctuation">,</span>
       <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
       <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
        <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token string">"Loading..."</span>
       <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
       <span class="token string-property property">"$$type"</span><span class="token operator">:</span> <span class="token string">"reference"</span><span class="token punctuation">,</span>
       <span class="token string-property property">"id"</span><span class="token operator">:</span> <span class="token string">"e"</span><span class="token punctuation">,</span>
       <span class="token string-property property">"identifier"</span><span class="token operator">:</span> <span class="token string">"L"</span><span class="token punctuation">,</span>
       <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"Lazy node"</span>
      <span class="token punctuation">}</span>
     <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>フォールバック（props）と、Promiseが解決した後で読み込まれるもの（「遅延ノード（Lazy node）」、この場合は<code class="language-text">DelayedMessage</code>）がいずれも含まれていることに注目してください。</p>
<p>Payloadは、チャンク化してストリーミングするとともに、Promiseが後で解決される仮想DOMの場所を参照します。そうすることで、Reactと、RSCをサポートするメタフレームワークは、最短時間でUIを表示し、実際のパフォーマンスとユーザーの体感パフォーマンスのいずれも改善しようとしています。</p>
<p>しかし、ストリーミングされたFlightデータはどこに送られるのでしょうか。Reactコードベース内のどこかであるはずです。</p>
<h2>ReactにPayloadを渡す</h2>
<p>RSCをサポートするために、ReactはFlight形式（文字列）を受け取り、<code class="language-text">parseModelString</code>などの関数でReact要素に変換する機能をコードベースに追加しました。</p>
<p>適切なデータを送信し、これらのReact APIを実行するかどうかはRSCをサポートするメタフレームワーク次第です。</p>
<p>例えば、Next.jsの場合は、アプリにラッピングコンポーネントを追加し、そこにPayloadデータをストリーミングにより送信します。</p>
<p>これは以下のようになります。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ServerRoot</span></span><span class="token punctuation">></span></span><span class="token plain-text">
  </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">AppRouter</span></span>
      <span class="token attr-name">actionQueue</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>actionQueue<span class="token punctuation">}</span></span>
      <span class="token attr-name">globalErrorComponentAndStyles</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>initialRSCPayload<span class="token punctuation">.</span><span class="token constant">G</span><span class="token punctuation">}</span></span>
      <span class="token attr-name">assetPrefix</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>initialRSCPayload<span class="token punctuation">.</span>p<span class="token punctuation">}</span></span>
  <span class="token punctuation">/></span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">ServerRoot</span></span><span class="token punctuation">></span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Next.jsは、コンポーネントツリーの<code class="language-text">AppRouter</code>の上に、<code class="language-text">ServerRoot</code>というコンポーネントを追加します。そこから<code class="language-text">AppRouter</code>にRSC Payloadデータをストリーミングします。</p>
<p>そのデータは、最終的にReactのPromiseベースのAPI（Flight形式を受け取るためのAPI）にストリーミングされます。</p>
<p>このように、ReactはPayloadをもとに仮想DOMを構築するためのAPIを提供し、Next.js（またはRSCをサポートするメタフレームワーク）には、サーバー上でコンポーネントが実行された後、そのデータをReactに渡す独自の仕組みが備わっています。</p>
<h2>順不同ストリーミング</h2>
<p>ストリーミングについて話すべき点は他にもあります。異なるコンポーネントの実行が異なるタイミングで完了する場合があります。チャンク化されたPayloadがストリーミングされてきたとき、Reactは仮想DOM（およびDOM）のどこにデータを置くべきかをどのようにして判断しているのでしょうか。</p>
<p><code class="language-text">DelayedMessage</code>コンポーネントを使用したDOMを再び見てみると、最初は以下のようになっています。</p>
<div class="gatsby-highlight" data-language="html"><pre style="counter-reset: linenumber NaN" class="language-html line-numbers"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span>understandingreact.com<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span>
  <span class="token comment">&lt;!--$?--></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>B:0<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Loading...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
  <span class="token comment">&lt;!--/$--></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">></span></span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Reactは、特殊なIDが設定された<code class="language-text">template</code>のようなプレースホルダーと、<code class="language-text">Suspense</code>が待っているPromiseが解決した時点でのコンテンツの挿入先を記載したHTMLコメントを残します。</p>
<p>フォールバックはDOMの中にありますが、Promiseが解決すると新しいJavaScriptがページにストリーミングされてきます。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token function-variable function">$RC</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">b<span class="token punctuation">,</span> c<span class="token punctuation">,</span> e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  c <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>
  c<span class="token punctuation">.</span>parentNode<span class="token punctuation">.</span><span class="token function">removeChild</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">var</span> a <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>a<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      b <span class="token operator">=</span> a<span class="token punctuation">.</span>previousSibling<span class="token punctuation">;</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span>
          b<span class="token punctuation">.</span>data <span class="token operator">=</span> <span class="token string">"$!"</span><span class="token punctuation">,</span>
          a<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">"data-dgst"</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">else</span> <span class="token punctuation">{</span>
          e <span class="token operator">=</span> b<span class="token punctuation">.</span>parentNode<span class="token punctuation">;</span>
          a <span class="token operator">=</span> b<span class="token punctuation">.</span>nextSibling<span class="token punctuation">;</span>
          <span class="token keyword">var</span> f <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
          <span class="token keyword">do</span> <span class="token punctuation">{</span>
              <span class="token keyword">if</span> <span class="token punctuation">(</span>a <span class="token operator">&amp;&amp;</span> <span class="token number">8</span> <span class="token operator">===</span> a<span class="token punctuation">.</span>nodeType<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                  <span class="token keyword">var</span> d <span class="token operator">=</span> a<span class="token punctuation">.</span>data<span class="token punctuation">;</span>
                  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token string">"/$"</span> <span class="token operator">===</span> d<span class="token punctuation">)</span>
                      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token number">0</span> <span class="token operator">===</span> f<span class="token punctuation">)</span>
                          <span class="token keyword">break</span><span class="token punctuation">;</span>
                      <span class="token keyword">else</span>
                          f<span class="token operator">--</span><span class="token punctuation">;</span>
                  <span class="token keyword">else</span>
                      <span class="token string">"$"</span> <span class="token operator">!==</span> d <span class="token operator">&amp;&amp;</span> <span class="token string">"$?"</span> <span class="token operator">!==</span> d <span class="token operator">&amp;&amp;</span> <span class="token string">"$!"</span> <span class="token operator">!==</span> d <span class="token operator">||</span> f<span class="token operator">++</span>
              <span class="token punctuation">}</span>
              d <span class="token operator">=</span> a<span class="token punctuation">.</span>nextSibling<span class="token punctuation">;</span>
              e<span class="token punctuation">.</span><span class="token function">removeChild</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span>
              a <span class="token operator">=</span> d
          <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span>
          <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span> c<span class="token punctuation">.</span>firstChild<span class="token punctuation">;</span> <span class="token punctuation">)</span>
              e<span class="token punctuation">.</span><span class="token function">insertBefore</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>firstChild<span class="token punctuation">,</span> a<span class="token punctuation">)</span><span class="token punctuation">;</span>
          b<span class="token punctuation">.</span>data <span class="token operator">=</span> <span class="token string">"$"</span>
      <span class="token punctuation">}</span>
      b<span class="token punctuation">.</span>_reactRetry <span class="token operator">&amp;&amp;</span> b<span class="token punctuation">.</span><span class="token function">_reactRetry</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">;</span>
$<span class="token constant">RC</span><span class="token punctuation">(</span><span class="token string">"B:0"</span><span class="token punctuation">,</span> <span class="token string">"S:0"</span><span class="token punctuation">)</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>このコードは、Promiseの解決後に新たに作成されたDOMのコンテンツを、プレースホルダーが残されていた適切な場所に挿入し、プレースホルダーとフォールバックを削除します。</p>
<p>このDOM操作コードが実行された後、DOMは以下のようになります。</p>
<div class="gatsby-highlight" data-language="jsxx"><pre style="counter-reset: linenumber NaN" class="language-jsxx line-numbers"><code class="language-jsxx">&lt;main&gt;
  &lt;h1&gt;understandingreact.com&lt;/h1&gt;
  &lt;!--$--&gt;
  &lt;p&gt;This message was loaded after a 5 second delay!&lt;/p&gt;
  &lt;!--/$--&gt;
&lt;/main&gt;</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>これを順不同ストリーミングといいます。これは単に、ストリーミングされてきたコンテンツを、先に完了する他のコンポーネントより前の場所であっても、仮想DOM／DOMツリーの所定の場所に挿入することを意味します。</p>
<p>そうすることで、特定のコンポーネントの実行に時間がかかったとしても、その完了を待たずにUIを更新し、他のコンポーネントの結果を反映することができます。</p>
<p>しかし、ここまではServer Components についてしか見ていません。デベロッパーが何年も前から書いてきたコンポーネントについてはどうでしょうか。ブラウザ上で実行される関数であるClient Componentsはどうでしょうか。</p>
<p>それについて語るうえで欠かせないのが、RSCを支える縁の下の力持ちとも言えるバンドラです。</p>
<h2>バンドラとインターリービング</h2>
<p>昔から、Reactの基本的な考え方の一つとしてコンポーネントコンポジションが挙げられていました。DOMの構造を決める作業はさまざまな関数に分けて行い、各コンポーネントを親子関係によって組み合わせることができます。</p>
<p>RSCがこの基本的な考え方から大きく離れないようにするためには、Server ComponentsとClient Componentsをインターリーブ（混在させる）できなくてはいけません。<strong>Client Componentsは、Server Componentsの子になれる必要</strong>があります。これには、props（関数の引数）を渡せることも含みます。</p>
<p>実際これが何を意味するかというと、コンポーネント階層の中には、サーバー上で実行される関数と、クライアント上で実行される関数があるということです。しかし最終的には、どの関数も自らが生成するDOMコンテンツの構造と中身を計算します。</p>
<p>これを実現する役割を担うのがメタフレームワークとバンドラです。しかし、抽象化はコストを伴うことを忘れてはいけません。抽象化を利用するために特別なルールを学ばなくてはいけないことがコストであることも少なくありません。この場合、サーバーとクライアントの隔たりをある程度抽象化し、気にならないようにするには、抽象化の制限を破らないようルールに従う必要があるということです。</p>
<p>RSCのケースでは、考慮すべきインターリービングのシナリオが3つあります。ルールは、コンポーネントが実行される場所に応じて何を「インポート」できるかに関わるもので、指示やインポートを分析してまとめるバンドラとRSCがどのように連携するかに基づいています。</p>
<h3>Client Components を Server Components にインポート</h3>
<p>このインポートは可能です。それが可能であるのは理にかなっています。バンドラはインポートステートメントを見て、どのコードをバンドルに含め、どのコードがクライアントによってダウンロードされるかを判断します。</p>
<p>RSCも仮想DOMの構築に参加します。Client Componentコードがバンドルに含まれブラウザに渡されるため、ツリーに含まれるClient Componentsを参照することができます。</p>
<p>Reactコースへの登録ページにステートフルな<code class="language-text">Counter</code>を追加してみましょう。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token comment">// components/Counter.js</span>
<span class="token string">'use client'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useState <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Counter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> <span class="token punctuation">[</span>count<span class="token punctuation">,</span> setCount<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">(</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span><span class="token punctuation">></span></span><span class="token plain-text">
            </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token punctuation">{</span>count<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">
            </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setCount</span><span class="token punctuation">(</span>count <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">
                Enroll
            </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">></span></span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// page.js</span>
<span class="token keyword">import</span> Counter <span class="token keyword">from</span> <span class="token string">"./components/Counter"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> DelayedMessage <span class="token keyword">from</span> <span class="token string">"./components/DelayedMessage"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Suspense <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Home</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">understandingreact.com</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Counter</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Suspense</span></span> <span class="token attr-name">fallback</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">Loading...</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">DelayedMessage</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Suspense</span></span><span class="token punctuation">></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>ファイル上部に<code class="language-text">use client</code>という指示があります。これはReactの機能ではありません。コンポーネントツリーの一部をクライアント上で実行する場合は、マーキングすることがデベロッパーの間では暗黙の了解になっています。</p>
<p>そのコンポーネントと、それにインポートされるコンポーネントが、Client Componentsとしてバンドルされます。</p>
<p>バンドラは<code class="language-text">use client</code>の指示を確認し、そのコンポーネント（およびインポートされるコンポーネント）のコードをブラウザがダウンロードするバンドルに含めます。</p>
<p><code class="language-text">Home</code> RSCおよび<code class="language-text">DelayedMessage</code> RSCはサーバー上で実行されるため、これらのコードはバンドルに含まれません。サーバーから送られるPayloadは以下のようになります。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token punctuation">{</span>
  <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"main"</span><span class="token punctuation">,</span>
  <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
  <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
   <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
     <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"h1"</span><span class="token punctuation">,</span>
     <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
     <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token string">"understandingreact.com"</span>
     <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
     <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"$$type"</span><span class="token operator">:</span> <span class="token string">"reference"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"id"</span><span class="token operator">:</span> <span class="token string">"d"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"identifier"</span><span class="token operator">:</span> <span class="token string">"L"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"Lazy node"</span>
     <span class="token punctuation">}</span><span class="token punctuation">,</span>
     <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
     <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
     <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"$$type"</span><span class="token operator">:</span> <span class="token string">"reference"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"id"</span><span class="token operator">:</span> <span class="token string">"e"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"identifier"</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
      <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"Reference"</span>
     <span class="token punctuation">}</span><span class="token punctuation">,</span>
     <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
     <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"fallback"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
       <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"p"</span><span class="token punctuation">,</span>
       <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
       <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
        <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token string">"Loading..."</span>
       <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
       <span class="token string-property property">"$$type"</span><span class="token operator">:</span> <span class="token string">"reference"</span><span class="token punctuation">,</span>
       <span class="token string-property property">"id"</span><span class="token operator">:</span> <span class="token string">"f"</span><span class="token punctuation">,</span>
       <span class="token string-property property">"identifier"</span><span class="token operator">:</span> <span class="token string">"L"</span><span class="token punctuation">,</span>
       <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"Lazy node"</span>
      <span class="token punctuation">}</span>
     <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
   <span class="token punctuation">]</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Client Componentが挿入される場所に「遅延読み込み（Lazy node）」のリファレンスが新たに追加されていることに注目してください。仮想DOMの当該部分は、そのClient Componentが実行されるときに分かります。つまり、Client Componentがサーバー上でレンダリング（フレームワークが対応している場合）されるか、ブラウザ上で実行されるときです。</p>
<p>もう一つ述べておきたいのが、Server ComponentからClient Componentにpropsを渡す場合、そのpropsは<a href="https://react.dev/reference/rsc/use-server#serializable-parameters-and-return-values">Reactによってシリアライズする必要</a>があるということです。</p>
<p>すでに見てきたように、propsはネットワークを介して送信されるPayloadに含まれます。つまり、データは全て文字列として渡し、クライアントのメモリ上でオブジェクトに復元する必要があるということです。</p>
<h3>Client ComponentsへのServer Componentsのインポート</h3>
<p>このインポートは認められません。サーバー上で実行されるコンポーネントを、ブラウザ上で実行されるコンポーネントにインポートすることはできません。</p>
<p>なぜかと言うと、バンドラがクライアントに送信するのはPayloadのみであり、RSC関数は送信するべきではないからです。したがって、インポートするコードはありません。バンドラはクライアントがダウンロードできるようにコードを含めることはないため、使用できるRSCコードはありません。</p>
<p>サーバーとクライアントの両方で実行可能な共有コンポーネントをインポートすることは可能です。しかし、Client Componentに共有コンポーネントをインポートする場合、そのコードはクライアントがダウンロードできるようバンドルされます。Server Componentに共有コンポーネントをインポートする場合はバンドルされません。</p>
<p>「間違ってServer Componentをインポートした場合、バンドラはそれが間違いだと分かるのか」と疑問に思うかもしれません。</p>
<p>これは妥当な疑問です。これはセキュリティ上の問題でもあります。ダウンロードされて他人の目に触れることを想定していないコードがServer Componentに含まれる場合、間違ってClient Componentにインポートし、バンドルされてしまうことがあるかもしれません。サーバー固有の機能（データベースへの接続など）が含まれる場合、ブラウザ上で実行することはできませんが、そのまま本番環境にリリースされてしまうと、データベースのアドレスなどの機密情報が漏洩する可能性があります。</p>
<p>Next.jsは対策として、<a href="https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#keeping-server-only-code-out-of-the-client-environment">コンポーネントに「サーバーのみ（server-only）」とマーキングできるようにしています</a>。しかし、これは何かを忘れないように指に紐を結びつけておくようなものです。紐を結ぶのを忘れてしまうこともありえます。</p>
<p>他のメタフレームワークは、サーバーのコードがバンドルされ、クライアントに送信されないようにするより確実な方法を検討しています。</p>
<p>しかし、一度サーバーとクライアントの境界を抽象化してしまうと、その境界が存在すること自体忘れてしまう一定のリスクを受け入れることになります。</p>
<h3>Server Componentsを子としてClient Componentsに渡す</h3>
<p><strong>これは可能です</strong>。これは興味深い、特殊なケースです。Server Componentsを<code class="language-text">children</code> propsとしてClient Componentに渡すことはできます。これはインポートとは異なります。</p>
<p><code class="language-text">Counter</code>関数にchildrenを渡した場合、以下のようになります。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token comment">// components/Counter.js</span>
<span class="token string">'use client'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useState <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Counter</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> <span class="token punctuation">[</span>count<span class="token punctuation">,</span> setCount<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">(</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span><span class="token punctuation">></span></span><span class="token plain-text">
            </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token punctuation">{</span>count<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">
            </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setCount</span><span class="token punctuation">(</span>count <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">
                Enroll
            </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token plain-text">
            </span><span class="token punctuation">{</span> children <span class="token punctuation">}</span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">></span></span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// page.js</span>
<span class="token keyword">import</span> Counter <span class="token keyword">from</span> <span class="token string">"./components/Counter"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> DelayedMessage <span class="token keyword">from</span> <span class="token string">"./components/DelayedMessage"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Suspense <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Home</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">understandingreact.com</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Counter</span></span><span class="token punctuation">></span></span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">Server Text</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Counter</span></span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Suspense</span></span> <span class="token attr-name">fallback</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">Loading...</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">DelayedMessage</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Suspense</span></span><span class="token punctuation">></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p><code class="language-text">Counter</code>関数はクライアント上で実行され、このコンポーネントに渡された子<code class="language-text">（&lt;p>Server Text&lt;/p>）</code>はサーバー上で処理されますが、問題なく機能します。</p>
<p>なぜ機能するのかと言うと、実行するServer Componentコードではなく、仮想DOMツリーの一部（コードを実行した結果）を渡しているからです。</p>
<p>Payloadは以下のようになります。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token punctuation">{</span>
   <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
     <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"h1"</span><span class="token punctuation">,</span>
     <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
     <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token string">"understandingreact.com"</span>
     <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
     <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"$$type"</span><span class="token operator">:</span> <span class="token string">"reference"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"id"</span><span class="token operator">:</span> <span class="token string">"d"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"identifier"</span><span class="token operator">:</span> <span class="token string">"L"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"Lazy node"</span>
     <span class="token punctuation">}</span><span class="token punctuation">,</span>
     <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
     <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
       <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"p"</span><span class="token punctuation">,</span>
       <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
       <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
        <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token string">"Server Text"</span>
       <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
     <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
     <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"$$type"</span><span class="token operator">:</span> <span class="token string">"reference"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"id"</span><span class="token operator">:</span> <span class="token string">"e"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"identifier"</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
      <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"Reference"</span>
     <span class="token punctuation">}</span><span class="token punctuation">,</span>
     <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
     <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"fallback"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
       <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"p"</span><span class="token punctuation">,</span>
       <span class="token string-property property">"key"</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
       <span class="token string-property property">"props"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
        <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token string">"Loading..."</span>
       <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token string-property property">"children"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
       <span class="token string-property property">"$$type"</span><span class="token operator">:</span> <span class="token string">"reference"</span><span class="token punctuation">,</span>
       <span class="token string-property property">"id"</span><span class="token operator">:</span> <span class="token string">"f"</span><span class="token punctuation">,</span>
       <span class="token string-property property">"identifier"</span><span class="token operator">:</span> <span class="token string">"L"</span><span class="token punctuation">,</span>
       <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"Lazy node"</span>
      <span class="token punctuation">}</span>
     <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
   <span class="token punctuation">]</span>
  <span class="token punctuation">}</span>
 <span class="token punctuation">}</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>
<p>Payloadの"Server Text"の部分に注目してください。すでにpropsとしてClient Componentに渡されています。Client Componentで直接PayloadにJSXを書いたのと変わりません。</p>
<h2>バンドラ：縁の下の力持ち</h2>
<p>これらは全てある重要な点を示しています。<strong>React Server Componentsは、多くの点においてバンドラ機能だということです</strong>。</p>
<p>バンドラはコードを分析し、Client　Componentsがバンドルに含まれていることを確認し、Client ComponentsへのリファレンスがPayloadに適切に含まれるようにします。</p>
<p>バンドラはReactに欠かせない存在です。Reactのコードベースを見ると、以下のようなフォルダがあります。</p>
<div class="gatsby-highlight" data-language="text"><pre style="counter-reset: linenumber NaN" class="language-text line-numbers"><code class="language-text">/react-server-dom-parcel
/react-server-dom-turbopack
/react-server-dom-webpack
//...and more</code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span><span></span><span></span><span></span></span></pre></div>
<p>これらのフォルダの中にはFlight関連のコードが含まれており、バンドルされたコードが適切に実行されるようにします。</p>
<p>バンドラがRSCにおいて脇役的な存在であるということは、他の方法も可能だということです。メタフレームワークは、Next.jsが使用する<code class="language-text">use client</code>のアプローチを受け入れる必要はありません。例えば、<a href="https://tanstack.com/start/">TanStack Start</a>は単に「JSXを返す」（Flight形式）関数としてRSCを実装しています。</p>
<p>Reactは、FlightデータのストリーミングというAPIを提供しています。メタフレームワークはバージョンアップを行い、そのAPIを工夫して使うことができます。</p>
<h2>フックとRSC</h2>
<p>サーバー上で実行することにはメリットもありますが、制約もあります。
Reactは、各要素の構造を仮想DOMに格納するだけではなく、stateを格納します。コンポーネントに以下のように書くとします。</p>
<div class="gatsby-highlight" data-language="jsx"><pre style="counter-reset: linenumber NaN" class="language-jsx line-numbers"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span>counter<span class="token punctuation">,</span> setCounter<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code><span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;"><span></span></span></pre></div>
<p>そうすると、仮想DOMにおけるコンポーネントの場所に紐づけられた連結リスト上のノードにデータが置かれます。実際には、そのstateはクライアントのブラウザのメモリ上にあるJavaScriptオブジェクトの中にあります。</p>
<p>したがって、React Server Componentsは、その性質上これらのフックを利用できません。フックを利用できる環境で実行されていないのです。</p>
<p>つまり、RSCは「非インタラクティブ」だということです。Reactにおけるインタラクティビティとは、一般的にはクライアント側のReactによる再レンダリングを、stateの更新によってトリガーすることを意味します。</p>
<p>そのため、アプリのインタラクティブ機能が増えるにつれ、Server　ComponentsをClient ComponentとServer Componentのコンポジションに組み替えるようになります。</p>
<p><code class="language-text">useReducer</code>や<code class="language-text">useState</code>などが必要な場合は、Client Componentが必要になります。</p>
<p>コンポーネントがどこで実行されるのかを念頭に置いておくと、フックを適切に使用する（または使用しない）ことができます。</p>
<h2>ハイドレートするか否か</h2>
<p>よく誤解される点について少しお話ししたいと思います。RSCはハイドレートするのか、という点についてです。</p>
<p>答えは否です。ハイドレーションとは、イベントをハンドラにフックできるよう仮想DOMを構築するために、実際の関数をクライアント上で再実行することです。</p>
<p>Reactでは、ボタンをクリックすると、そのイベントはDOMツリーの最上部にあるReactのルートまで送られ、どのコンポーネントがクリックを処理するかをReactが判断します（答えは、ボタンの作成に関わったコンポーネントです）。</p>
<p>したがって、Reactがイベントに適切に対応できるよう、仮想DOMと、クリックを処理するコードがなくてはいけません。
RSCは非インタラクティブです。少なくともReactの通常のアプローチでは、stateの設定も、クリックの処理も行いません。実行するためにコードをクライアントに送ることもないので、当然ながらハイドレートしません。</p>
<p>しかし、仮想DOMの構築には関わっています。ツリーの差分検出処理にも関わっています。ハイドレートしないからと言って、ハイドレーション時にツリー上に存在しないわけではありません。存在します。</p>
<h2>再フェッチと差分検出</h2>
<p>実際のアプリでは、ページの初回ローディングだけでなく、サーバーコンポーネントの再フェッチについても考える必要があるでしょう。</p>
<p>つまり、サーバーにコンポーネント（場合によっては新しいpropsを含む）を再実行するよう指示し、仮想DOMを更新するために新しいPayloadデータを送ってもらうということです。</p>
<p>例えば、RSCが生成したデータリストにページ番号を付けていく場合、ルートが<code class="language-text">/page/1</code>か<code class="language-text">/page/2</code>であるかによって、異なるデータセットを取得することが望まれます。</p>
<p>これはRSCの利点であり、おそらくメタフレームワークのルーターに組み込まれています。UIはサーバー上で計算されますが、メタフレームワークはページ全体を再度読み込む必要がありません。</p>
<p>RSCはその性質上、仮想DOMの定義をクライアントにストリーミングすることができ、Reactは通常どおりクライアント側で差分検出処理を行うことができます。つまり、ページを再度読み込む必要がなく、ページ上の他のstateも失われません。</p>
<p>この点において、RSCはサーバーレンダリングとクライアントレンダリングの両方の長所を提供できます。サーバー上で実行しながら、クライアント上で実行された場合と同じように更新することができます
。
RSCの仕組みについて説明しましたので、より直感的にご理解いただけるのではないかと思います。Reactはすでに仮想DOMの差分計算によってDOMを更新していますので、仮想DOMのデータをサーバーから取得できれば、Reactは通常の動作を行うことが可能です。</p>
<h2>バンドルサイズに関する誤解</h2>
<p>10年以上前から、使用するツールの仕組みを深く理解することの重要性を説いてきました。<a href="https://tonyalicea.dev/courses">筆者のコース</a>では、一貫してこのテーマについて扱ってきました。</p>
<p>多くの生徒がこのアプローチの重要性を理解してくれていますが、「理論が多すぎる。実践から学べばいい」と不満を漏らす生徒もたまにいます。</p>
<p>しかし、使用するツールやライブラリ、フレームワークの仕組みを理解することの主な価値の一つが、正確な情報に基づき、アーキテクチャに関する効果的な判断ができることです。</p>
<p>例えば、Next.jsの世界ではRSCのメリットについて以前から誤解がありました。<code class="language-text">__next_f()</code>関数について、Next.jsコードのリポジトリでは<a href="https://github.com/vercel/next.js/discussions/42170">素晴らしい議論</a>が繰り広げられています。</p>
<p>RSCを使い始めたデベロッパーは、ページ下部の<code class="language-text">script</code>タグでこの関数に重複データが渡されていることに気づきました。なぜそこにこの関数があるのか疑問に感じ、無効にできないか尋ねるデベロッパーもいました。</p>
<p>この重複データは何でしょうか。そう、Payloadです。ストリーミングされてきた関数コールは、仮想DOMを作成するため、最終的にそのPayloadデータをReactに渡します。仕組みを理解していなければ、これはかなりショッキングなことでしょう。</p>
<p>問題は帯域幅の使用量が増えることであり、多くのデベロッパーがその点について不満を述べていました。ネットワークを介して送るデータ量が増えているのです。</p>
<p>なぜデベロッパー達は驚いたのでしょうか。当初、VercelはNext.jsのドキュメンテーションサイトでRSCについて次のように説明していました。</p>
<p><img src="https://tonyalicea.dev/assets/blogimages/vercel_orig.jpeg"></p>
<p><em>バンドルサイズ：Server Componentsでは、以前ならクライアントJavaScriptのバンドルサイズに影響するような大きな依存関係をサーバー上に残すことができます。クライアントはServer ComponentsのJavaScriptをダウンロードして解析し、実行する必要が一切ないため、これはインターネットの通信速度が遅い、あるいはデバイスの処理能力が低いユーザーにとって有益です。</em></p>
<p>「クライアントはServer ComponentsのJavaScriptをダウンロードして解析し、実行する必要が一切ない」という文言が誤解を招いたようです。これは実際には正しくありません。VercelはServer Componentsの実際のJavaScriptコードについて言っていたのですが、「一切」という言葉を使ってしまいました。</p>
<p>筆者はこれに関連し、VercelとSNS上で<a href="https://x.com/joshcstory/status/1766547542194409664">興味深い会話</a>をしました。この会話が、後日文言が変更されるきっかけになったのかもしれません（文言を変更したVercelに感謝）。</p>
<p><img src="https://tonyalicea.dev/assets/blogimages/vercel_updated.jpeg"></p>
<p><em>パフォーマンス：Server Componentsは、パフォーマンスをベースラインから最適化する手段を提供します。例えば、Client Componentだけで構成されるアプリから始めた場合、UIの非インタラクティブ要素をServer Componentsに移動することで、クライアント側が必要なJavaScriptの量を減らすことができます。ブラウザがダウンロードして解析し、実行するクライアント側JavaScriptの量が減るため、これはインターネットの通信速度が遅い、あるいはデバイスの処理能力が低いユーザーにとって有益です。</em></p>
<p>新しい説明には、Server Componentsが「クライアント側が必要なJavaScriptの量を減らすことができる」と書いてあります。それは事実です。しかし、Payloadはある意味JavaScript、少なくとも、JavaScriptの関数に渡されるデータなので、JavaScriptの量を増やすこともあります。</p>
<p>デベロッパーたちが誤解していた重要な点は、<strong>バンドルサイズと帯域幅の使用量は同じではないということです</strong>。</p>
<p>Server Componentのコードはバンドルに含まれていないので、バンドルサイズは小さくなります。しかし、Payloadのデータは倍増しますので、データが大きいと（この記事のような長編のブログ記事など）節約できるバイト数以上のデータを送信することになります。</p>
<h2>RSCはいつ使うべきか</h2>
<p>では、RSCはいつ使うべきなのでしょうか。大抵の場合そうであるように、「状況次第」というのが正しい答えです。RSCの仕組みに関する正確なメンタルモデルが構築できていれば、適切なアーキテクチャを選択できるはずです。</p>
<p>筆者の場合、このような長編のブログ記事にはRSCを使用しません。帯域幅の使用量が多くなり、理にかなわないからです。コンテンツの多いサイトやアプリには、<a href="https://astro.build/">Astro</a>などを使用します。</p>
<p>一方、データベースへのアクセスが多く、ロジックが複雑な場合、Server Componentを使ってサーバーに処理させるかもしれません。大容量のJavaScriptライブラリを使用して比較的少量のコンテンツを作成する必要がある場合も同様です。バンドルとPayloadのトレードオフが価値に見合うようなら、RSCは理にかなっていると思います。</p>
<p>高度にインタラクティブなアプリを開発していて、頻繁にバージョンアップを行って機能を追加しているような場合も、Client Componentsを中心に運用し、クライアントとサーバー両方を使用するようなリファクタリングは極力控えると思います。</p>
<p>明確な使い方を推奨するには不確定要素があまりにも多すぎます。この記事を通じて試みたように、読者が正確な情報をもとに判断できるよう、理解を深める手助けをするのが筆者にできる精一杯のことです。</p>
<h2>今後の展望</h2>
<p>React Server Componentsの未来はどのようなものになるでしょうか。それは必ずしも明らかではありません。ReactはAPIを作り、メタフレームワークがそれを利用しています。</p>
<p>筆者の考えでは、TanStack Startが採用した、完全なServer Componentsではなく、仮想DOMを返す関数を用いるアプローチが普及すると思います。しかし、用途によってはNext.jsのアプローチも有効だと思います。</p>
<p>セキュリティとパフォーマンスも徐々に改善して欲しいと思います。例えば、仮想DOMのある枝が全てServer Componentsの場合、差分検出処理やハイドレーションを最適化することでその枝をスキップするようにできると思います。</p>
<h2>もっと詳しく学びたい方へ</h2>
<p>この記事がReact Server Componentsの理解を深める一助になれば幸いです。Reactの全てについて、このようにゼロから詳しく学びたい方は、筆者の完全版「<a href="https://understandingreact.com/">Understanding React</a>（Reactを理解する）」コースにぜひご登録ください。</p>
<p>27個のモジュールと、16.5時間の動画コンテンツを通じてReactのソースコードを詳しく学び、デベロッパーにとって最も有益なツールである正確なメンタルモデルを構築することができます。</p>
<p>それではまた！</p>]]></content:encoded></item></channel></rss>