<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>React on AI2CORE - AI技術ブログ</title>
    <link>https://www.ai2core.com/tags/react/</link>
    <description>Recent content in React on AI2CORE - AI技術ブログ</description>
    <generator>Hugo -- 0.146.4</generator>
    <language>ja</language>
    <lastBuildDate>Tue, 24 Feb 2026 08:00:00 +0900</lastBuildDate>
    <atom:link href="https://www.ai2core.com/tags/react/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>React Compilerがもたらすフロントエンドの革新：手動メモ化からの解放</title>
      <link>https://www.ai2core.com/posts/2026-02-24-react-compiler/</link>
      <pubDate>Tue, 24 Feb 2026 08:00:00 +0900</pubDate>
      <guid>https://www.ai2core.com/posts/2026-02-24-react-compiler/</guid>
      <description>React Compilerの仕組みと、useMemoやuseCallbackが不要になる未来のReact開発について。</description>
      <content:encoded><![CDATA[<h2 id="react-compilerがもたらすフロントエンドの革新手動メモ化からの解放">React Compilerがもたらすフロントエンドの革新：手動メモ化からの解放</h2>
<h3 id="はじめにそのusememo本当に必要ですか">はじめに：そのuseMemo、本当に必要ですか？</h3>
<p>React開発者の皆さん、日々のコーディングお疲れ様です。コンポーネントのパフォーマンスを最適化するために、<code>useMemo</code>や<code>useCallback</code>、<code>React.memo</code>といったAPIと格闘した経験は誰にでもあるでしょう。依存配列（dependency array）を睨みながら、「この値は含めるべきか？」「この関数をメモ化しないと子コンポーネントが再レンダリングされてしまう…」と頭を悩ませる時間は、決して少なくないはずです。</p>
<p>これらのAPIは、Reactアプリケーションのパフォーマンスを維持するための強力なツールである一方、私たちのコードに複雑さをもたらす諸刃の剣でもあります。</p>
<ul>
<li><strong>コードの可読性の低下:</strong> 本来のロジックとは無関係なメモ化のための記述が、コンポーネントを肥大化させます。</li>
<li><strong>依存配列の管理ミス:</strong> 依存配列に漏れがあれば、値が更新されず、気づきにくいバグの原因となります。逆に、過剰に含めればメモ化の意味がなくなります。</li>
<li><strong>早すぎる最適化:</strong> パフォーマンス上の問題が起きていないにもかかわらず、慣習的にすべての関数を<code>useCallback</code>でラップしてしまう「過剰なメモ化」は、かえってコードを複雑にするだけです。</li>
</ul>
<p>もし、このような手動でのパフォーマンスチューニングから解放され、Reactが「よしなに」最適なパフォーマンスを発揮してくれるとしたら、私たちの開発体験はどれほど向上するでしょうか？</p>
<p>この記事では、その夢のような未来を実現する可能性を秘めた<strong>React Compiler</strong>について、その仕組みから私たち開発者に与える影響まで、徹底的に深掘りしていきます。React Compilerは、単なる便利ツールではありません。それは、Reactのメンタルモデルそのものを変革し、私たちを「手動メモ化の呪縛」から解放する、フロントエンド開発の大きな一歩なのです。</p>
<h3 id="なぜreact-compilerは生まれたのか背景にある根深い課題">なぜReact Compilerは生まれたのか？背景にある根深い課題</h3>
<p>React Compilerの重要性を理解するためには、まずReactの基本的なレンダリングの仕組みと、なぜ私たちが<code>useMemo</code>や<code>useCallback</code>を使わなければならなかったのかを再確認する必要があります。</p>
<h4 id="reactの再レンダリングの仕組みと素朴さ">Reactの再レンダリングの仕組みと「素朴さ」</h4>
<p>Reactの基本的な設計思想は「シンプルさ」にあります。StateやPropsが変更されると、コンポーネントは**再レンダリング（re-render）**されます。再レンダリングとは、コンポーネント関数が再実行され、新しいReact要素（仮想DOM）のツリーを生成するプロセスです。Reactは、この新しいツリーと前回のツリーを比較（差分検出、Reconciliation）し、変更があった部分だけを実際のDOMに適用します。</p>
<p>このモデルは非常に直感的で分かりやすい反面、パフォーマンス上の課題を内包しています。例えば、親コンポーネントが再レンダリングされると、<strong>propsが変更されていなくても、デフォルトではすべての子コンポーネントも再レンダリング</strong>されます。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useState</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;react&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// このコンポーネントはpropsを持たない
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ChildComponent</span> <span style="color:#f92672">=</span> () =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;ChildComponent is rendered&#39;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> &lt;<span style="color:#f92672">div</span>&gt;<span style="color:#a6e22e">I</span> <span style="color:#a6e22e">am</span> <span style="color:#a6e22e">a</span> <span style="color:#a6e22e">child</span>.&lt;/<span style="color:#f92672">div</span>&gt;;
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ParentComponent</span> <span style="color:#f92672">=</span> () =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> [<span style="color:#a6e22e">count</span>, <span style="color:#a6e22e">setCount</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">useState</span>(<span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">p</span>&gt;<span style="color:#a6e22e">Count</span><span style="color:#f92672">:</span> {<span style="color:#a6e22e">count</span>}&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">onClick</span><span style="color:#f92672">=</span>{() =&gt; <span style="color:#a6e22e">setCount</span>(<span style="color:#a6e22e">c</span> =&gt; <span style="color:#a6e22e">c</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>)}&gt;<span style="color:#a6e22e">Increment</span>&lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">ChildComponent</span> /&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></td></tr></table>
</div>
</div><p>上記の例では、<code>Increment</code>ボタンをクリックすると<code>ParentComponent</code>の<code>count</code>ステートが更新され、<code>ParentComponent</code>が再レンダリングされます。このとき、<code>ChildComponent</code>には何の変化もないにもかかわらず、コンソールには<code>ChildComponent is rendered</code>と表示され、再レンダリングが走っていることがわかります。</p>
<p>アプリケーションが小規模なうちは問題になりませんが、コンポーネントツリーが深くなり、各コンポーネントの処理が複雑になるにつれて、この不要な再レンダリングがパフォーマンスのボトルネックとなるのです。</p>
<h4 id="手動メモ化という職人芸とその限界">手動メモ化という「職人芸」とその限界</h4>
<p>この問題を解決するために、Reactは私たちに最適化のためのツールを提供しました。それが<code>React.memo</code>、<code>useMemo</code>、<code>useCallback</code>です。</p>
<ul>
<li><strong><code>React.memo</code>:</strong> コンポーネントをラップすることで、propsが変更された場合のみ再レンダリングされるようにする高階コンポーネント。</li>
<li><strong><code>useMemo</code>:</strong> 計算コストの高い処理の結果をメモ化（キャッシュ）し、依存する値が変更された場合のみ再計算するフック。</li>
<li><strong><code>useCallback</code>:</strong> 関数のインスタンスをメモ化し、依存する値が変更された場合のみ新しい関数を生成するフック。これは特に、<code>React.memo</code>でラップした子コンポーネントに関数をpropsとして渡す際に効果を発揮します。</li>
</ul>
<p>これらのツールを駆使することで、不要な再レンダリングを抑制し、パフォーマンスを劇的に改善できます。しかし、その代償として、私たちは常に「何を」「いつ」「どのように」メモ化するかを考え続けなければならなくなりました。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useState</span>, <span style="color:#a6e22e">useCallback</span>, <span style="color:#a6e22e">memo</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;react&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// React.memoでラップ
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ChildComponent</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">memo</span>(({ <span style="color:#a6e22e">onButtonClick</span> }) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;ChildComponent is rendered&#39;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">onClick</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">onButtonClick</span>}&gt;<span style="color:#a6e22e">Click</span> <span style="color:#a6e22e">me</span>&lt;/<span style="color:#f92672">button</span>&gt;;
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ParentComponent</span> <span style="color:#f92672">=</span> () =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> [<span style="color:#a6e22e">count</span>, <span style="color:#a6e22e">setCount</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">useState</span>(<span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> [<span style="color:#a6e22e">otherState</span>, <span style="color:#a6e22e">setOtherState</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">useState</span>(<span style="color:#66d9ef">false</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// useCallbackで関数をメモ化しないと、ParentComponentが再レンダリングされるたびに
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">// 新しいonButtonClick関数が生成され、ChildComponentのpropsが変更されたと見なされ
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">// React.memoの効果がなくなってしまう。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">handleButtonClick</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">useCallback</span>(() =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;Button clicked!&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ここでcountを使う場合は依存配列に含める必要がある
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// console.log(count);
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  }, []); <span style="color:#75715e">// 依存配列が空なので、この関数は初回レンダリング時しか生成されない
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">p</span>&gt;<span style="color:#a6e22e">Count</span><span style="color:#f92672">:</span> {<span style="color:#a6e22e">count</span>}&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">onClick</span><span style="color:#f92672">=</span>{() =&gt; <span style="color:#a6e22e">setCount</span>(<span style="color:#a6e22e">c</span> =&gt; <span style="color:#a6e22e">c</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>)}&gt;<span style="color:#a6e22e">Increment</span> <span style="color:#a6e22e">Count</span>&lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">onClick</span><span style="color:#f92672">=</span>{() =&gt; <span style="color:#a6e22e">setOtherState</span>(<span style="color:#a6e22e">s</span> =&gt; <span style="color:#f92672">!</span><span style="color:#a6e22e">s</span>)}&gt;<span style="color:#a6e22e">Toggle</span> <span style="color:#a6e22e">Other</span> <span style="color:#a6e22e">State</span>&lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">ChildComponent</span> <span style="color:#a6e22e">onButtonClick</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">handleButtonClick</span>} /&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></td></tr></table>
</div>
</div><p>このコードは一見すると正しく最適化されているように見えます。しかし、<code>handleButtonClick</code>の中で<code>count</code>ステートを使いたくなったらどうでしょう？依存配列に<code>count</code>を追加しなければならず、そうなると<code>count</code>が更新されるたびに新しい関数が生成され、結局<code>ChildComponent</code>は再レンダリングされてしまいます。</p>
<p>このように、手動メモ化は非常に繊細で、エラーを起こしやすい作業です。</p>
<ul>
<li><strong>認知負荷の増大:</strong> 開発者は常にコンポーネントの依存関係と再レンダリングの連鎖を意識しなければなりません。</li>
<li><strong>バグの温床:</strong> 依存配列の指定ミスは、アプリケーションの挙動をおかしくする深刻なバグに直結します。</li>
<li><strong>コードの陳腐化:</strong> リファクタリングによって依存関係が変わった際に、メモ化のコードを更新し忘れることがよくあります。</li>
</ul>
<p>Reactチームは、この「Reactを正しく、かつ効率的に書くことの難しさ」を根本的な課題と捉えました。開発者が本来のビジネスロジックに集中できる環境を作ることこそが、Reactの生産性をさらに高める鍵であると考えたのです。その答えが、<strong>React Compiler</strong>でした。</p>
<h3 id="react-compilerの核心コンパイラがreactのコードを書き換える">React Compilerの核心：コンパイラがReactのコードを書き換える</h3>
<p>React Compiler（開発コードネーム: &ldquo;Forget&rdquo;）は、その名の通り、Reactのコードを解析し、<strong>自動的にメモ化を適用するコンパイラ</strong>です。これはライブラリやフックではなく、Babelプラグインとして提供され、ビルドプロセスに組み込まれます。</p>
<p>開発者は、これまで通り「素朴な」Reactコードを書くだけです。<code>useMemo</code>や<code>useCallback</code>のことは忘れて（&ldquo;Forget&quot;して）構いません。すると、コンパイラがビルド時にコードをスキャンし、どこをメモ化すべきかを判断し、最適な形でメモ化のコードを自動的に挿入してくれるのです。</p>
<h4 id="仕組みの概要静的解析とコード変換">仕組みの概要：静的解析とコード変換</h4>
<p>React Compilerは、どのようにしてこの魔法のような処理を実現しているのでしょうか？その核心は、<strong>高度な静的解析</strong>にあります。</p>
<ol>
<li>
<p><strong>コードの解析（Parsing）:</strong>
コンパイラはまず、コンポーネントのコードを解析し、その構造（どの変数がどこで定義され、どこで使われているか）を理解します。State、Props、フック、通常の変数や関数などをすべて識別し、それらの依存関係をグラフとして構築します。</p>
</li>
<li>
<p><strong>リアクティビティ分析（Reactivity Analysis）:</strong>
次に、コンパイラは「何が変更されたときに、どの部分が影響を受けるか」を分析します。これは、Reactのリアクティビティモデル（StateやPropsの変更がレンダリングをトリガーする仕組み）を深く理解しているからこそ可能です。</p>
<ul>
<li>この値はリアクティブか？（例: <code>useState</code>の返り値）</li>
<li>この関数はリアクティブな値に依存しているか？</li>
<li>このJSXブロックは、どの値が変更されたときに再計算が必要か？</li>
</ul>
</li>
<li>
<p><strong>自動メモ化（Auto-memoization）:</strong>
分析結果に基づいて、コンパイラはコード内の適切な箇所にメモ化のロジックを挿入します。これは、私たちが手で<code>useMemo</code>や<code>useCallback</code>を書くのと同じような最適化ですが、はるかに正確かつ網羅的です。コンパイラは、値がプリミティブ（数値、文字列など）かオブジェクトか、あるいは関数かを見極め、それぞれに最適なメモ化戦略を適用します。</p>
</li>
<li>
<p><strong>コード生成（Code Generation）:</strong>
最後に、コンパイラは最適化された新しいコードを生成します。このコードには、<code>useMemo</code>や<code>useCallback</code>に似た、しかしより低レベルで効率的なコンパイラ専用のキャッシュ機構が埋め込まれています。</p>
</li>
</ol>
<h4 id="コード変換の具体例">コード変換の具体例</h4>
<p>言葉だけでは分かりにくいので、コンパイラがどのようなコード変換を行うのか、具体的な例を見てみましょう。</p>
<p><strong>変換前のコード（私たちが書くコード）:</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useState</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;react&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">UserProfile</span> <span style="color:#f92672">=</span> ({ <span style="color:#a6e22e">user</span> }) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> [<span style="color:#a6e22e">filter</span>, <span style="color:#a6e22e">setFilter</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">useState</span>(<span style="color:#e6db74">&#39;&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// このフィルタリング処理は、user.postsが巨大な場合にコストがかかる可能性がある
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">filteredPosts</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">posts</span>.<span style="color:#a6e22e">filter</span>(<span style="color:#a6e22e">post</span> =&gt;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">title</span>.<span style="color:#a6e22e">toLowerCase</span>().<span style="color:#a6e22e">includes</span>(<span style="color:#a6e22e">filter</span>.<span style="color:#a6e22e">toLowerCase</span>())
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// この関数は、UserProfileが再レンダリングされるたびに再生成される
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">handleFilterChange</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">e</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">setFilter</span>(<span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">target</span>.<span style="color:#a6e22e">value</span>);
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;text&#34;</span> <span style="color:#a6e22e">value</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">filter</span>} <span style="color:#a6e22e">onChange</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">handleFilterChange</span>} /&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">h2</span>&gt;{<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">name</span>}<span style="color:#960050;background-color:#1e0010">&#39;</span><span style="color:#a6e22e">s</span> <span style="color:#a6e22e">Posts</span>&lt;/<span style="color:#f92672">h2</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>        {<span style="color:#a6e22e">filteredPosts</span>.<span style="color:#a6e22e">map</span>(<span style="color:#a6e22e">post</span> =&gt; (
</span></span><span style="display:flex;"><span>          &lt;<span style="color:#f92672">li</span> <span style="color:#a6e22e">key</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">id</span>}&gt;{<span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">title</span>}&lt;/<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>        ))}
</span></span><span style="display:flex;"><span>      &lt;/<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></td></tr></table>
</div>
</div><p>このコードにはメモ化が一切ありません。<code>filter</code>を変更するたびに<code>UserProfile</code>が再レンダリグされ、<code>filteredPosts</code>の計算が走り、<code>handleFilterChange</code>関数も再生成されます。もし親コンポーネントから渡される<code>user</code>以外のpropsが変更された場合でも、これらの処理はすべて再実行されてしまいます。</p>
<p><strong>コンパイラによる変換後のコード（概念的なイメージ）:</strong></p>
<p>コンパイラは内部的に<code>useMemoCache</code>のようなAPIを使い、以下のようなコードを生成します（※これはあくまで概念を説明するための擬似コードであり、実際の出力とは異なります）。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useState</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;react&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useMemoCache</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;react/compiler-runtime&#39;</span>; <span style="color:#75715e">// コンパイラが内部的に使用するAPI
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">UserProfile</span> <span style="color:#f92672">=</span> ({ <span style="color:#a6e22e">user</span> }) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">cache</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">useMemoCache</span>(<span style="color:#ae81ff">3</span>); <span style="color:#75715e">// コンパイラが必要なキャッシュスロット数を計算
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> [<span style="color:#a6e22e">filter</span>, <span style="color:#a6e22e">setFilter</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">useState</span>(<span style="color:#e6db74">&#39;&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// filteredPostsの計算をメモ化
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">filteredPosts</span> <span style="color:#f92672">=</span> (() =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 依存関係である user.posts と filter が変更されたかチェック
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">cache</span>[<span style="color:#ae81ff">0</span>] <span style="color:#f92672">!==</span> <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">posts</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">cache</span>[<span style="color:#ae81ff">1</span>] <span style="color:#f92672">!==</span> <span style="color:#a6e22e">filter</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">posts</span>.<span style="color:#a6e22e">filter</span>(<span style="color:#a6e22e">post</span> =&gt;
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">title</span>.<span style="color:#a6e22e">toLowerCase</span>().<span style="color:#a6e22e">includes</span>(<span style="color:#a6e22e">filter</span>.<span style="color:#a6e22e">toLowerCase</span>())
</span></span><span style="display:flex;"><span>      );
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">cache</span>[<span style="color:#ae81ff">0</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">posts</span>;
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">cache</span>[<span style="color:#ae81ff">1</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">filter</span>;
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">cache</span>[<span style="color:#ae81ff">2</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">result</span>; <span style="color:#75715e">// 結果をキャッシュ
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>      <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">cache</span>[<span style="color:#ae81ff">2</span>]; <span style="color:#75715e">// キャッシュされた値を返す
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  })();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// handleFilterChange関数をメモ化
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">handleFilterChange</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">e</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">setFilter</span>(<span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">target</span>.<span style="color:#a6e22e">value</span>);
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// handleFilterChangeはリアクティブな値に依存しないため、
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">// コンパイラはこれが不変であると判断し、メモ化の必要すらないかもしれない。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">// もし依存があれば、useCallbackのようにメモ化される。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      {<span style="color:#75715e">/* ...JSX... */</span>}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></td></tr></table>
</div>
</div><p>この変換後のコードでは、<code>filteredPosts</code>の計算が<code>user.posts</code>と<code>filter</code>に依存していることをコンパイラが自動で検出し、これらの値が変更されない限りは再計算が行われないように最適化されています。開発者はこの複雑なキャッシュロジックを一切書く必要がありません。</p>
<h4 id="reactのルールの重要性">「Reactのルール」の重要性</h4>
<p>React Compilerが安全にコードを変換できる大前提として、<strong>私たちが「Reactのルール（Rules of React）」を守ってコードを書いていること</strong>が挙げられます。</p>
<ul>
<li><strong>Hooksはトップレベルで呼び出すこと。</strong> ループや条件分岐、ネストした関数の中で呼び出してはいけません。</li>
<li><strong>ReactコンポーネントとHooksは純粋であること。</strong> 同じ入力（props, state）に対しては常に同じ出力（JSX）を返す必要があります。レンダリング中に副作用（Stateの直接変更など）を起こしてはいけません。</li>
</ul>
<p>これらのルールは、コンパイラがコードの依存関係を静的に、つまりコードを実行することなく正確に予測するための生命線です。ルールが守られていないコードは、コンパイラの解析を妨げ、予期せぬ動作やエラーの原因となります。</p>
<p>幸い、私たちはすでに<code>eslint-plugin-react-hooks</code>という優れたリンターツールを持っており、これらのルールを強制することができます。React Compiler時代においては、このリンターの重要性がさらに増すことになるでしょう。</p>
<h3 id="メリットとデメリットreact開発はどう変わるのか">メリットとデメリット：React開発はどう変わるのか</h3>
<p>React Compilerの導入は、私たちの開発プロセスに多岐にわたる影響を与えます。</p>
<h4 id="メリット">メリット</h4>
<ol>
<li>
<p><strong>劇的な開発者体験（DX）の向上</strong>
最大のメリットは、何と言っても手動メモ化の煩わしさからの解放です。パフォーマンスチューニングという認知負荷の高い作業をコンパイラに任せることで、開発者はプロダクトの本質的な価値であるビジネスロジックの実装に集中できます。&ldquo;Just write React&rdquo; という、Reactが本来目指していた理想の開発スタイルに近づくことができます。</p>
</li>
<li>
<p><strong>最適なパフォーマンスの実現</strong>
人間による手動の最適化は、しばしば不完全です。最適化が不足している箇所を見逃したり、逆に不必要な箇所まで過剰にメモ化してしまったりします。コンパイラは、コードを網羅的に分析し、人間では見逃してしまうような細かな部分まで、一貫したルールに基づいて最適化を行います。これにより、手動で行うよりも優れた、あるいは少なくとも同等のパフォーマンスを安定して実現できます。</p>
</li>
<li>
<p><strong>コードの可読性と保守性の向上</strong>
<code>useMemo</code>や<code>useCallback</code>のラッパーがコードから消えることで、コンポーネントはよりシンプルで宣言的になります。ロジックが追いやすくなり、コードレビューの負担も軽減されます。将来のリファクタリング時にも、メモ化の依存関係を気にする必要がなくなり、保守性が大きく向上します。</p>
</li>
<li>
<p><strong>React学習コストの低減</strong>
React初学者がつまずきやすいポイントの一つが、<code>useMemo</code>や<code>useCallback</code>の概念と、依存配列の正しい使い方です。React Compilerが標準となれば、これらの高度な最適化手法を初期段階で学ぶ必要がなくなり、Reactの基本的な考え方（State、Props、コンポーネント）の習得に集中できるようになります。</p>
</li>
</ol>
<h4 id="デメリットと懸念点">デメリットと懸念点</h4>
<p>もちろん、良いことばかりではありません。新しい技術には必ずトレードオフや考慮すべき点が存在します。</p>
<ol>
<li>
<p><strong>コンパイラの「魔法」化</strong>
内部で何が起きているのかが分かりにくくなる、いわゆる「Magic」な部分が増える可能性があります。「なぜこのコンポーネントは再レンダリングされないのか？」あるいは「なぜここは期待通りに更新されないのか？」といった問題のデバッグが、これまで以上に難しくなるかもしれません。これに対しては、React DevToolsがCompilerによる最適化を可視化する機能を提供し、デバッグをサポートすることが期待されています。</p>
</li>
<li>
<p><strong>ビルド時間の増加</strong>
ビルドプロセスに静的解析とコード変換という新たなステップが加わるため、ビルド時間が長くなる可能性があります。大規模なプロジェクトでは、この影響が無視できないレベルになるかもしれません。ただし、これはコンパイラ自体の最適化や、インクリメンタルビルドの仕組みによって将来的には改善されていくでしょう。</p>
</li>
<li>
<p><strong>既存コードベースへの導入ハードル</strong>
長年運用されている巨大なコードベースでは、「Reactのルール」が守られていない箇所が散見されるかもしれません。Compilerを導入するためには、まずこれらのコードをリファクタリングする必要があります。コンパイラには、ファイル単位で有効/無効を切り替えるオプトイン/オプトアウトの仕組みが用意される予定であり、段階的な導入が可能になる見込みです。</p>
</li>
<li>
<p><strong>エコシステムとの互換性</strong>
React CompilerはBabelプラグインとして開発が進められていますが、近年のフロントエンド界隈ではesbuildやSWCといったRust製の高速なビルドツールが主流になりつつあります。これらのツールとReact Compilerがどのように統合されていくのかは、今後の大きな注目点です。</p>
</li>
</ol>
<h3 id="現場で使える実践的なtipsreact-compiler時代への備え">現場で使える実践的なTips：React Compiler時代への備え</h3>
<p>React Compilerはまだ実験的な段階ですが（InstagramのWebサイトなど、一部のMeta社製品では本番投入済み）、その登場はもはや時間の問題です。私たちは今から、来るべきCompiler時代に備えておくことができます。</p>
<h4 id="1-reactのルールを徹底する">1. &ldquo;Reactのルール&quot;を徹底する</h4>
<p>今すぐできる最も重要な準備は、あなたのコードベースで「Reactのルール」を徹底することです。</p>
<ul>
<li><strong><code>eslint-plugin-react-hooks</code>を導入・有効化する:</strong> もしまだ導入していないのであれば、今すぐ設定しましょう。<code>exhaustive-deps</code>ルールを含め、リンターの警告にはすべて対応する文化をチームに根付かせることが重要です。</li>
<li><strong>コンポーネントの純粋性を意識する:</strong> コンポーネントやフックの内部で、外部の状態を変更したり、APIリクエストを直接発行したりするような副作用を避けてください。副作用は<code>useEffect</code>やイベントハンドラ内に閉じ込めるのが基本です。</li>
</ul>
<p>これらの習慣は、Compilerの有無にかかわらず、Reactアプリケーションを健全に保つためのベストプラクティスです。</p>
<h4 id="2-メモ化apiへの考え方を変える">2. メモ化APIへの考え方を変える</h4>
<p>React Compilerが導入された後も、<code>useMemo</code>や<code>useCallback</code>が即座になくなるわけではありません。コンパイラがうまく最適化できない特殊なケースや、パフォーマンスクリティカルな部分で意図的に手動最適化を行いたい場合のために、これらのAPIは残り続けるでしょう。</p>
<p>しかし、私たちのマインドセットは変える必要があります。これからは、**「まずシンプルに書き、パフォーマンスの問題が実際に発生したら、プロファイラで計測した上で、最後の手段として手動メモ化を検討する」**というアプローチが基本になります。闇雲なメモ化はアンチパターンであるという認識を強く持つべきです。</p>
<h4 id="3-段階的な導入計画を立てる">3. 段階的な導入計画を立てる</h4>
<p>あなたのチームのプロジェクトにCompilerを導入する際は、いきなり全体に適用するのではなく、段階的に進めることをお勧めします。</p>
<ul>
<li><strong>新規プロジェクトから試す:</strong> これから始める新しいプロジェクトは、Compilerを導入する絶好の機会です。</li>
<li><strong>影響の少ない部分から適用する:</strong> 既存のプロジェクトでは、影響範囲の小さいコンポーネントや、ビジネスロジック的に重要度の低いページからオプトインで適用を始め、動作を確認しながら範囲を広げていくのが安全です。</li>
<li><strong>CIでリグレッションを検知する:</strong> Compilerによるコード変換が意図しない挙動の変化（リグレッション）を引き起こす可能性はゼロではありません。コンポーネントの見た目や動作をチェックするテスト（Visual Regression TestingやE2Eテスト）をCIに組み込み、安全性を確保しましょう。</li>
</ul>
<h3 id="まとめフロントエンド開発の新たな地平へ">まとめ：フロントエンド開発の新たな地平へ</h3>
<p>React Compilerは、単なるパフォーマンス改善ツールではありません。それは、Reactにおける開発のパラダイムを根底から変える可能性を秘めた、<strong>フロントエンド開発の革新</strong>です。</p>
<p>長年私たちを悩ませてきた手動メモ化の複雑さから解放されることで、Reactはより宣言的で、より直感的なフレームワークへと進化します。開発者はパフォーマンスの細部に気を取られることなく、ユーザーのための価値創造に集中できるようになります。</p>
<p>もちろん、コンパイラは万能の銀の弾丸ではなく、その導入には新たな学びや課題も伴うでしょう。しかし、それがもたらす生産性とコード品質の向上は、それらの課題を乗り越えるに値する大きな価値を持っています。</p>
<p>私たちは今、Reactの歴史における大きな転換点に立っています。<code>useMemo</code>と<code>useCallback</code>に別れを告げ、コンパイラと共に歩む新しいReact開発の時代。その幕開けは、もうすぐそこまで来ています。未来への準備を始めましょう。</p>
]]></content:encoded>
      <category>Tech</category>
      <category>React</category>
      <category>React Compiler</category>
      <category>Frontend</category>
    </item>
    <item>
      <title>React 20 Server Componentsのベストプラクティス</title>
      <link>https://www.ai2core.com/posts/2026-02-22-react-server-components/</link>
      <pubDate>Sun, 22 Feb 2026 12:00:00 +0900</pubDate>
      <guid>https://www.ai2core.com/posts/2026-02-22-react-server-components/</guid>
      <description>RSCの導入で変わるデータフェッチの常識と、クライアントコンポーネントとの使い分け。</description>
      <content:encoded><![CDATA[<h1 id="react-20-server-componentsのベストプラクティス-rsc導入で変わるデータフェッチの常識とクライアントコンポーネントとの使い分け">React 20 Server Componentsのベストプラクティス: RSC導入で変わるデータフェッチの常識と、クライアントコンポーネントとの使い分け</h1>
<h2 id="はじめに">はじめに</h2>
<p>「Reactのデータフェッチといえば、<code>useEffect</code>と<code>useState</code>を使って、クライアントサイドでAPIを叩くのが当たり前。」
もしあなたがまだそう考えているなら、この記事はあなたの常識を覆すことになるかもしれません。</p>
<p>Next.js 13のApp Routerと共に本格的に導入されたReact Server Components（RSC）は、Reactアプリケーションのアーキテクチャ、特にデータフェッチの方法を根本から変えようとしています。もはや、すべてのコンポーネントがブラウザ上で動くわけではありません。サーバーでレンダリングを完結させ、クライアントにはインタラクティブなUIの部品だけを送る、という新しい時代が到来したのです。</p>
<p>しかし、この大きなパラダイムシフトは、多くの開発者に新たな問いを突きつけています。</p>
<ul>
<li>「Server ComponentとClient Componentは、具体的にどう使い分ければいいの？」</li>
<li>「<code>&quot;use client&quot;</code>はどこに書くのが正解？」</li>
<li>「<code>useEffect</code>でのデータフェッチは、もう時代遅れなの？」</li>
<li>「SWRやReact Query（TanStack Query）はもう不要になる？」</li>
</ul>
<p>この記事では、そんな疑問を抱えるあなたのために、React Server Componentsの核心を解き明かし、次世代のReact開発におけるベストプラクティスを徹底的に解説します。この記事を読み終える頃には、あなたはRSCを自信を持って使いこなし、より高速で、より効率的なWebアプリケーションを構築するための確かな知識を手にしていることでしょう。</p>
<h2 id="なぜrscは登場したのか-従来のreact開発が抱えていた課題">なぜRSCは登場したのか？ 従来のReact開発が抱えていた課題</h2>
<p>React Server Componentsがなぜこれほど注目されているのかを理解するためには、まず従来のクライアントサイドレンダリング（CSR）やサーバーサイドレンダリング（SSR）が抱えていた課題を振り返る必要があります。</p>
<h3 id="課題1-クライアントサイドでのデータフェッチの限界">課題1: クライアントサイドでのデータフェッチの限界</h3>
<p>SPA（Single Page Application）の普及以降、React開発の主流はクライアントサイドでのデータフェッチでした。しかし、このアプローチにはいくつかの構造的な問題が存在します。</p>
<h4 id="ネットワークウォーターフォール問題">ネットワークウォーターフォール問題</h4>
<p>コンポーネントのレンダリングが完了してから、<code>useEffect</code>内でデータフェッチを開始するため、親コンポーネントのデータ取得が終わらないと子コンポーネントのデータ取得が始まらない、といった連鎖的な遅延（ウォーターフォール）が発生しがちでした。これにより、ページの表示完了までに時間がかかっていました。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#75715e">// 典型的なウォーターフォール
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">ProfilePage</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">user</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">useUser</span>(); <span style="color:#75715e">// 1回目のAPIコール
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// userが取得できてから、次のコンポーネントがレンダリングされる
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">user</span> <span style="color:#f92672">?</span> &lt;<span style="color:#f92672">UserDetails</span> <span style="color:#a6e22e">userId</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">id</span>} /&gt; <span style="color:#f92672">:</span> &lt;<span style="color:#f92672">Spinner</span> /&gt;;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">UserDetails</span>({ <span style="color:#a6e22e">userId</span> }) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">posts</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">usePosts</span>(<span style="color:#a6e22e">userId</span>); <span style="color:#75715e">// 2回目のAPIコール
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="バンドルサイズの肥大化">バンドルサイズの肥大化</h4>
<p>データフェッチのためのライブラリ（axios, SWRなど）、状態管理ロジック、データ整形ロジックなど、すべてがJavaScriptバンドルに含まれ、クライアントに送信されます。アプリケーションが複雑になるほどバンドルサイズは増大し、初期ロード時間（Initial Load Time）を悪化させる大きな要因となっていました。</p>
<h4 id="セキュリティリスク">セキュリティリスク</h4>
<p>APIキーやデータベースへの接続情報など、本来サーバーサイドに留めておくべき機密情報を、クライアントサイドのコードから（直接的でなくとも）扱わざるを得ないケースがありました。これにより、情報漏洩のリスクが高まります。</p>
<h3 id="課題2-ssrssgの限界">課題2: SSR/SSGの限界</h3>
<p>これらの課題を解決するためにSSR（Server-Side Rendering）やSSG（Static-Site Generation）が登場しましたが、これらにもまた別の限界がありました。</p>
<ul>
<li><strong>SSRの課題</strong>: サーバーでページ全体のHTMLを生成するため初期表示は高速ですが、JavaScriptがダウンロードされ、ハイドレーションが完了するまでページはインタラクティブになりません。また、データフェッチが完了するまでHTMLの生成がブロックされるため、一部のデータ取得が遅いとページ全体の表示が遅れてしまいます。</li>
<li><strong>SSGの課題</strong>: ビルド時にすべてのページを生成するため非常に高速ですが、動的なデータやユーザー固有のコンテンツを表示するには不向きでした。</li>
</ul>
<p>これらの課題を解決し、「サーバーのパワーを最大限に活用しつつ、クライアントの豊かなインタラクティビティも損なわない」という、両者の&quot;いいとこ取り&quot;を目指して生まれたのが、React Server Componentsなのです。</p>
<h2 id="rscの核心-server-componentとclient-componentの徹底解説">RSCの核心: Server ComponentとClient Componentの徹底解説</h2>
<p>RSCの最も重要な概念は、コンポーネントを「サーバーで実行されるもの」と「クライアントで実行されるもの」に明確に分離したことです。Next.jsのApp Routerでは、<strong>すべてのコンポーネントはデフォルトでServer Component</strong>として扱われます。</p>
<h3 id="server-component-デフォルト">Server Component (デフォルト)</h3>
<p>Server Componentは、その名の通りサーバーサイドでのみレンダリングされるコンポーネントです。</p>
<p><strong>特徴:</strong></p>
<ul>
<li><code>useState</code>, <code>useEffect</code>, <code>useContext</code>などのフックや、<code>onClick</code>などのイベントハンドラは使用できません。</li>
<li>ブラウザAPI（<code>window</code>, <code>localStorage</code>など）にアクセスできません。</li>
<li><code>async/await</code>を直接使用して、データフェッチや非同期処理を行えます。</li>
<li>データベースやファイルシステムなど、サーバーサイドのリソースに直接アクセスできます。</li>
<li>レンダリングされても、そのJavaScriptコードはクライアントのバンドルに含まれません。</li>
</ul>
<p>Server Componentの最大の利点は、データソースの近くでコンポーネントをレンダリングできることです。これにより、データフェッチのレイテンシーを最小限に抑え、必要なデータだけをコンポーネントに埋め込んでクライアントに送信できます。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#75715e">// app/posts/page.tsx (Server Component)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">db</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/lib/db&#39;</span>; <span style="color:#75715e">// サーバーサイドのDBクライアント
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getPosts</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// サーバー上で直接データベースにクエリを発行
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">posts</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">findMany</span>();
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">posts</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">PostsPage</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// コンポーネント自体を非同期関数にできる
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">posts</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">getPosts</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">main</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">h1</span>&gt;<span style="color:#a6e22e">All</span> <span style="color:#a6e22e">Posts</span>&lt;/<span style="color:#f92672">h1</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>        {<span style="color:#a6e22e">posts</span>.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">post</span>) =&gt; (
</span></span><span style="display:flex;"><span>          &lt;<span style="color:#f92672">li</span> <span style="color:#a6e22e">key</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">id</span>}&gt;{<span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">title</span>}&lt;/<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>        ))}
</span></span><span style="display:flex;"><span>      &lt;/<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">main</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>このコードでは、APIエンドポイントを介さずに直接データベースからデータを取得しています。また、このコンポーネントのロジックはクライアントに送られないため、バンドルサイズはゼロです。</p>
<h3 id="client-component">Client Component</h3>
<p>Client Componentは、従来のReactコンポーネントと同じように、クライアント（ブラウザ）でレンダリングされ、インタラクティブな動作を担当します。</p>
<p><strong>特徴:</strong></p>
<ul>
<li>ファイルの先頭に<code>&quot;use client&quot;;</code>というディレクティブを記述する必要があります。</li>
<li><code>useState</code>, <code>useEffect</code>などのフックを使用して、状態管理や副作用を扱えます。</li>
<li><code>onClick</code>, <code>onChange</code>などのイベントハンドラを定義できます。</li>
<li>ブラウザAPIにアクセスできます。</li>
</ul>
<p><code>&quot;use client&quot;</code>は、単なるマーカーではありません。これは、<strong>Server ComponentとClient Componentの境界線を定義する</strong>重要な役割を果たします。<code>&quot;use client&quot;</code>が書かれたファイルからインポートされるすべてのコンポーネントも、自動的にClient Componentとして扱われます。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#75715e">// components/Counter.tsx (Client Component)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#e6db74">&#34;use client&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useState</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;react&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">Counter</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> [<span style="color:#a6e22e">count</span>, <span style="color:#a6e22e">setCount</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">useState</span>(<span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">onClick</span><span style="color:#f92672">=</span>{() =&gt; <span style="color:#a6e22e">setCount</span>(<span style="color:#a6e22e">count</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>)}&gt;
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Count</span><span style="color:#f92672">:</span> {<span style="color:#a6e22e">count</span>}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="使い分けの基本フロー">使い分けの基本フロー</h3>
<p>では、具体的にどう使い分ければ良いのでしょうか。以下のフローチャートを参考に判断するのがおすすめです。</p>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TD
    A[コンポーネント開発開始] --&gt; B{インタラクティブな要素は必要？&lt;br&gt;(onClick, onChangeなど)};
    B -- Yes --&gt; C[Client Component&lt;br&gt;(&#34;use client&#34; を使用)];
    B -- No --&gt; D{状態管理やライフサイクルフックは必要？&lt;br&gt;(useState, useEffectなど)};
    D -- Yes --&gt; C;
    D -- No --&gt; E{ブラウザ専用APIは必要？&lt;br&gt;(window, localStorageなど)};
    E -- Yes --&gt; C;
    E -- No --&gt; F[Server Component&lt;br&gt;(デフォルトのまま)];
</code></pre><p><strong>基本原則は、「可能な限りServer Componentを使い、インタラクティブ性が必要な部分だけをClient Componentとして切り出す」ことです。</strong></p>
<h2 id="rsc時代のデータフェッチ戦略">RSC時代のデータフェッチ戦略</h2>
<p>RSCの導入により、データフェッチの考え方は大きく変わりました。クライアントでの<code>useEffect</code>に頼るのではなく、サーバーでデータを取得することが第一の選択肢となります。</p>
<h3 id="基本戦略-データフェッチはserver-componentで行う">基本戦略: データフェッチはServer Componentで行う</h3>
<p>Server Component内では、<code>async/await</code>を使って、まるでNode.jsのスクリプトを書くかのようにシンプルにデータをフェッチできます。</p>
<p>Next.jsは、標準の<code>fetch</code> APIを拡張しており、きめ細やかなキャッシュ戦略を可能にしています。</p>
<ul>
<li><strong>静的データフェッチ (SSG相当):</strong> デフォルトの動作。ビルド時にデータを取得し、キャッシュします。
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#75715e">// キャッシュを強制（デフォルト）
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">&#39;https://...&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#75715e">// または明示的に
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">&#39;https://...&#39;</span>, { <span style="color:#a6e22e">cache</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;force-cache&#39;</span> });
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li><strong>再検証 (ISR相当):</strong> 指定した時間（秒）が経過すると、次にリクエストがあった際にバックグラウンドでデータを再取得します。
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#75715e">// 60秒ごとにデータを再検証
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">&#39;https://...&#39;</span>, { <span style="color:#a6e22e">next</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">revalidate</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">60</span> } });
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li><strong>動的データフェッチ (SSR相当):</strong> リクエストごとに常に最新のデータを取得します。
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#75715e">// キャッシュを利用しない
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">&#39;https://...&#39;</span>, { <span style="color:#a6e22e">cache</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;no-store&#39;</span> });
</span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
<p>これにより、<code>getStaticProps</code>や<code>getServerSideProps</code>といったNext.jsの旧来のAPIは不要になり、コンポーネント内でデータ要件を完結させることができます。</p>
<h3 id="パターン1-server-componentでデータをフェッチしclient-componentにpropsで渡す">パターン1: Server Componentでデータをフェッチし、Client ComponentにPropsで渡す</h3>
<p>これは最も基本的で強力なパターンです。データの取得と表示ロジックはServer Componentに任せ、ユーザーインタラクションが必要な部分だけをClient Componentとして分離します。</p>
<p><strong>例: 記事一覧と、各記事の「いいね」ボタン</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#75715e">// app/blog/page.tsx (Server Component)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">LikeButton</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/components/LikeButton&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">getPosts</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/lib/posts&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">BlogPage</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">posts</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">getPosts</span>(); <span style="color:#75715e">// サーバーで全記事データを取得
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">h1</span>&gt;<span style="color:#a6e22e">Blog</span>&lt;/<span style="color:#f92672">h1</span>&gt;
</span></span><span style="display:flex;"><span>      {<span style="color:#a6e22e">posts</span>.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">post</span>) =&gt; (
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">article</span> <span style="color:#a6e22e">key</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">id</span>}&gt;
</span></span><span style="display:flex;"><span>          &lt;<span style="color:#f92672">h2</span>&gt;{<span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">title</span>}&lt;/<span style="color:#f92672">h2</span>&gt;
</span></span><span style="display:flex;"><span>          &lt;<span style="color:#f92672">p</span>&gt;{<span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">content</span>}&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>          {<span style="color:#75715e">/* インタラクティブな部分だけClient Componentに分離 */</span>}
</span></span><span style="display:flex;"><span>          {<span style="color:#75715e">/* 初期データはPropsとして渡す */</span>}
</span></span><span style="display:flex;"><span>          &lt;<span style="color:#f92672">LikeButton</span> <span style="color:#a6e22e">postId</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">id</span>} <span style="color:#a6e22e">initialLikes</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">likes</span>} /&gt;
</span></span><span style="display:flex;"><span>        &lt;/<span style="color:#f92672">article</span>&gt;
</span></span><span style="display:flex;"><span>      ))}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#75715e">// components/LikeButton.tsx (Client Component)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#e6db74">&#34;use client&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useState</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;react&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">incrementLikes</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/app/actions&#39;</span>; <span style="color:#75715e">// Server Actionを利用
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">LikeButton</span>({ <span style="color:#a6e22e">postId</span>, <span style="color:#a6e22e">initialLikes</span> }) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> [<span style="color:#a6e22e">likes</span>, <span style="color:#a6e22e">setLikes</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">useState</span>(<span style="color:#a6e22e">initialLikes</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> [<span style="color:#a6e22e">isPending</span>, <span style="color:#a6e22e">startTransition</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">useTransition</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">handleClick</span> <span style="color:#f92672">=</span> () =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">startTransition</span>(<span style="color:#66d9ef">async</span> () =&gt; {
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">// サーバー側の関数を直接呼び出してDBを更新
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newLikes</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">incrementLikes</span>(<span style="color:#a6e22e">postId</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">setLikes</span>(<span style="color:#a6e22e">newLikes</span>);
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">onClick</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">handleClick</span>} <span style="color:#a6e22e">disabled</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">isPending</span>}&gt;
</span></span><span style="display:flex;"><span>      <span style="color:#960050;background-color:#1e0010">👍</span> {<span style="color:#a6e22e">likes</span>} {<span style="color:#a6e22e">isPending</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">&#39;...&#39;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;&#39;</span>}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>この構成により、以下のメリットが生まれます。</p>
<ul>
<li>記事一覧のデータ取得はサーバーで完結し、クライアントのバンドルサイズを圧迫しない。</li>
<li>インタラクティブな<code>LikeButton</code>のみがClient Componentとなり、JavaScriptのロード量を最小限に抑える。</li>
<li><code>initialLikes</code>をPropsで渡すことで、ハイドレーション後も初期状態が正しく表示される。</li>
</ul>
<h3 id="パターン2-server-componentをclient-componentに埋め込む-children-props">パターン2: Server ComponentをClient Componentに埋め込む (<code>children</code> props)</h3>
<p>Client Componentの中に、静的なコンテンツとしてServer Componentを配置したい場合があります。これは<code>children</code> propsを活用することで実現できます。これにより、Client Componentの島（Island）の中に、サーバーでレンダリングされたコンテンツの穴（Hole）を作ることができます。</p>
<p><strong>例: タブ切り替えUI（Client）と、各タブのコンテンツ（Server）</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#75715e">// components/TabContainer.tsx (Client Component)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#e6db74">&#34;use client&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useState</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;react&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">TabContainer</span>({ <span style="color:#a6e22e">tabs</span> }) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> [<span style="color:#a6e22e">activeTab</span>, <span style="color:#a6e22e">setActiveTab</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">useState</span>(<span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">role</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;tablist&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        {<span style="color:#a6e22e">tabs</span>.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">tab</span>, <span style="color:#a6e22e">index</span>) =&gt; (
</span></span><span style="display:flex;"><span>          &lt;<span style="color:#f92672">button</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">key</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">tab</span>.<span style="color:#a6e22e">label</span>}
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">role</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;tab&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">onClick</span><span style="color:#f92672">=</span>{() =&gt; <span style="color:#a6e22e">setActiveTab</span>(<span style="color:#a6e22e">index</span>)}
</span></span><span style="display:flex;"><span>          &gt;
</span></span><span style="display:flex;"><span>            {<span style="color:#a6e22e">tab</span>.<span style="color:#a6e22e">label</span>}
</span></span><span style="display:flex;"><span>          &lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>        ))}
</span></span><span style="display:flex;"><span>      &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">role</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;tabpanel&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        {<span style="color:#75715e">/* childrenとして渡されたServer Componentがここに表示される */</span>}
</span></span><span style="display:flex;"><span>        {<span style="color:#a6e22e">tabs</span>[<span style="color:#a6e22e">activeTab</span>].<span style="color:#a6e22e">content</span>}
</span></span><span style="display:flex;"><span>      &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#75715e">// app/dashboard/page.tsx (Server Component)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">TabContainer</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/components/TabContainer&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">UserProfile</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/components/UserProfile&#39;</span>; <span style="color:#75715e">// Server Component
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">AnalyticsData</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/components/AnalyticsData&#39;</span>; <span style="color:#75715e">// Server Component
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// これらのコンポーネントは重いデータフェッチを行うと仮定
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">UserProfile</span>() { <span style="color:#75715e">/* ... */</span> }
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">AnalyticsData</span>() { <span style="color:#75715e">/* ... */</span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">DashboardPage</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tabs</span> <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    { <span style="color:#a6e22e">label</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Profile&#39;</span>, <span style="color:#a6e22e">content</span><span style="color:#f92672">:</span> &lt;<span style="color:#f92672">UserProfile</span> /&gt; },
</span></span><span style="display:flex;"><span>    { <span style="color:#a6e22e">label</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Analytics&#39;</span>, <span style="color:#a6e22e">content</span><span style="color:#f92672">:</span> &lt;<span style="color:#f92672">AnalyticsData</span> /&gt; }
</span></span><span style="display:flex;"><span>  ];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">main</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">h1</span>&gt;<span style="color:#a6e22e">Dashboard</span>&lt;/<span style="color:#f92672">h1</span>&gt;
</span></span><span style="display:flex;"><span>      {<span style="color:#75715e">/* Client ComponentにServer ComponentをPropsとして渡す */</span>}
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">TabContainer</span> <span style="color:#a6e22e">tabs</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">tabs</span>} /&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">main</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>このパターンでは、タブの切り替えという状態管理はクライアントで行いつつ、各タブの中身はサーバーで完全にレンダリングされます。これにより、初期表示時に不要なタブのコンテンツまでクライアントに送られることがなくなり、パフォーマンスが向上します。</p>
<h3 id="パターン3-クライアントサイドでのデータフェッチが依然として有効な場合">パターン3: クライアントサイドでのデータフェッチが依然として有効な場合</h3>
<p>RSCが強力だからといって、クライアントサイドでのデータフェッチが完全になくなるわけではありません。SWRやTanStack Query (旧React Query) は、特定のユースケースにおいて依然として非常に有効です。</p>
<p><strong>主なユースケース:</strong></p>
<ul>
<li><strong>ユーザーの操作に頻繁に反応するデータ:</strong> 検索ボックスのサジェスト機能、無限スクロール、頻繁に更新されるダッシュボードなど。</li>
<li><strong>クライアントの状態に依存するデータ:</strong> 認証状態（ログインしているユーザーの情報など）に基づいて取得するデータ。</li>
<li><strong>複雑なキャッシュ戦略やミューテーション管理:</strong> オプティミスティックUIアップデートなど、高度なUI/UXを実現したい場合。</li>
</ul>
<p>RSCは<strong>初期ロード</strong>のパフォーマンス最適化に絶大な効果を発揮しますが、ロード<strong>後</strong>のクライアント上でのリッチなデータインタラクションは、これらのライブラリが得意とする領域です。RSCとクライアントデータフェッチライブラリは、対立するものではなく、<strong>共存し補完し合う関係</strong>と捉えるのが正解です。</p>
<h2 id="現場で使える実践的なtips">現場で使える実践的なTips</h2>
<h3 id="tip-1-use-clientはできるだけ末端leafのコンポーネントに設定する">Tip 1: <code>&quot;use client&quot;</code>はできるだけ末端（Leaf）のコンポーネントに設定する</h3>
<p><code>&quot;use client&quot;</code>は境界線であり、一度宣言されるとその配下のすべてのコンポーネントがClient Componentになります。そのため、できるだけコンポーネントツリーの末端、つまり本当にインタラクティブ性が必要な最小単位のコンポーネントに設定することが重要です。</p>
<p><strong>悪い例:</strong> ページ全体をClient Componentにしてしまう</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#75715e">// app/dashboard/layout.tsx
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#e6db74">&#34;use client&#34;</span>; <span style="color:#75715e">// ← アンチパターン！
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">Header</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/components/Header&#39;</span>; <span style="color:#75715e">// Client Componentになる
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">Sidebar</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/components/Sidebar&#39;</span>; <span style="color:#75715e">// Client Componentになる
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">MainContent</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/components/MainContent&#39;</span>; <span style="color:#75715e">// Client Componentになる
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">DashboardLayout</span>({ <span style="color:#a6e22e">children</span> }) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>良い例:</strong> インタラクティブな部分だけを分離する</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#75715e">// app/dashboard/layout.tsx (Server Component)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">UserMenu</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/components/UserMenu&#39;</span>; <span style="color:#75715e">// Client Component
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">Sidebar</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/components/Sidebar&#39;</span>; <span style="color:#75715e">// Server Component
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">MainContent</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/components/MainContent&#39;</span>; <span style="color:#75715e">// Server Component
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">DashboardLayout</span>({ <span style="color:#a6e22e">children</span> }) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">header</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">nav</span>&gt;...&lt;/<span style="color:#f92672">nav</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">UserMenu</span> /&gt; {<span style="color:#75715e">/* インタラクティブな部分だけがClient Component */</span>}
</span></span><span style="display:flex;"><span>      &lt;/<span style="color:#f92672">header</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">Sidebar</span> /&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">MainContent</span>&gt;{<span style="color:#a6e22e">children</span>}&lt;/<span style="color:#f92672">MainContent</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="tip-2-server-actionsでデータ更新をシンプルにする">Tip 2: Server Actionsでデータ更新をシンプルにする</h3>
<p>フォーム送信やデータ更新（Mutation）のために、もはやAPIエンドポイントを自前で用意する必要はありません。Server Actionsを使えば、サーバーサイドで実行される関数をクライアントコンポーネントから直接、安全に呼び出せます。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#75715e">// app/actions.ts
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#e6db74">&#34;use server&#34;</span>; <span style="color:#75715e">// ファイルの先頭に記述
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">db</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/lib/db&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">revalidatePath</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;next/cache&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">addPost</span>(<span style="color:#a6e22e">formData</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">FormData</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">title</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">formData</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;title&#39;</span>) <span style="color:#a6e22e">as</span> <span style="color:#a6e22e">string</span>;
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">post</span>.<span style="color:#a6e22e">create</span>({ <span style="color:#a6e22e">data</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">title</span> } });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// 関連するページのキャッシュをクリアして再描画をトリガー
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">revalidatePath</span>(<span style="color:#e6db74">&#39;/posts&#39;</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#75715e">// components/AddPostForm.tsx (Client Component)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#e6db74">&#34;use client&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">addPost</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/app/actions&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">AddPostForm</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">form</span> <span style="color:#a6e22e">action</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">addPost</span>}&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;text&#34;</span> <span style="color:#a6e22e">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;title&#34;</span> <span style="color:#a6e22e">required</span> /&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;submit&#34;</span>&gt;<span style="color:#a6e22e">Add</span> <span style="color:#a6e22e">Post</span>&lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">form</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>form</code>タグの<code>action</code>属性に関数を渡すだけで、フォーム送信時にサーバー上の<code>addPost</code>関数が実行されます。APIルートの作成、<code>fetch</code>の呼び出し、ローディング状態の管理といった定型的なコードが大幅に削減され、開発体験が飛躍的に向上します。</p>
<h3 id="tip-3-suspenseとstreamingで体感速度を向上させる">Tip 3: SuspenseとStreamingで体感速度を向上させる</h3>
<p>ページの特定の部分でデータ取得に時間がかかる場合でも、<code>Suspense</code>を使うことで他の部分の表示をブロックすることなく、UIを段階的に表示（Streaming）できます。</p>
<p>Next.jsでは、<code>loading.tsx</code>という規約ファイルを使うことで、これを簡単に実現できます。</p>
<p><strong>例: 記事詳細ページとコメント欄</strong>
記事本体はすぐに表示したいが、コメントの読み込みには時間がかかるとします。</p>
<pre tabindex="0"><code>app/
└ posts/
  └ [slug]/
    ├ page.tsx      // 記事本体を表示するServer Component
    └ loading.tsx   // page.tsxのレンダリング中に表示されるUI
</code></pre><div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#75715e">// app/posts/[slug]/page.tsx
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">PostContent</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/components/PostContent&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">Comments</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@/components/Comments&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Suspense</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;react&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">PostPage</span>({ <span style="color:#a6e22e">params</span> }) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      {<span style="color:#75715e">/* 記事本体はすぐにレンダリングされる */</span>}
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">PostContent</span> <span style="color:#a6e22e">slug</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">slug</span>} /&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      {<span style="color:#75715e">/* コメントはデータ取得が終わるまでフォールバックUIを表示 */</span>}
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">Suspense</span> <span style="color:#a6e22e">fallback</span><span style="color:#f92672">=</span>{&lt;<span style="color:#f92672">p</span>&gt;<span style="color:#a6e22e">Loading</span> <span style="color:#a6e22e">comments</span>...&lt;/<span style="color:#f92672">p</span>&gt;}&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">Comments</span> <span style="color:#a6e22e">slug</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">slug</span>} /&gt;
</span></span><span style="display:flex;"><span>      &lt;/<span style="color:#f92672">Suspense</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#75715e">// components/Comments.tsx (Server Component)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getComments</span>(<span style="color:#a6e22e">slug</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">await</span> <span style="color:#66d9ef">new</span> Promise(<span style="color:#a6e22e">resolve</span> =&gt; <span style="color:#a6e22e">setTimeout</span>(<span style="color:#a6e22e">resolve</span>, <span style="color:#ae81ff">2000</span>)); <span style="color:#75715e">// 意図的に遅延
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">Comments</span>({ <span style="color:#a6e22e">slug</span> }) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">comments</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">getComments</span>(<span style="color:#a6e22e">slug</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>この構成により、ユーザーはまず記事本体を読み始めることができ、その間にコメントが非同期でロードされます。これにより、実際の読み込み時間が同じでも、体感速度は劇的に改善されます。</p>
<h2 id="まとめ">まとめ</h2>
<p>React Server Componentsは、単なる新機能ではなく、Reactアプリケーションの設計思想そのものを変える、大きなパラダイムシフトです。</p>
<p>本記事の要点をまとめます。</p>
<ol>
<li><strong>デフォルトはServer Component:</strong> すべてのコンポーネントはサーバーで実行されるものと考え、インタラクティブ性が必要な最小単位だけを<code>&quot;use client&quot;</code>でClient Componentに切り出す。</li>
<li><strong>データフェッチの主戦場はサーバーへ:</strong> <code>async/await</code>をServer Componentで直接使い、データソースの近くでデータを取得する。<code>useEffect</code>でのデータフェッチは、クライアントでのインタラクションに応じた動的な取得に限定される。</li>
<li><strong><code>&quot;use client&quot;</code>は境界線:</strong> このディレクティブは、サーバーとクライアントの世界を分ける重要な役割を果たす。ツリーのなるべく末端に配置することを心がける。</li>
<li><strong>パターンを使い分ける:</strong> 「Propsでデータを渡す」「<code>children</code>でコンポーネントを埋め込む」といった基本的なパターンをマスターし、コンポーネントの責務を明確に分離する。</li>
<li><strong>エコシステムを賢く利用する:</strong> Server Actionsでミューテーションを簡略化し、SuspenseとStreamingでUXを向上させる。SWRやTanStack Queryも、依然としてクライアントサイドでの複雑なデータ管理に有効。</li>
</ol>
<p>最初は戸惑うことも多いかもしれません。しかし、このサーバーとクライアントの新しい協調モデルを理解し、使いこなすことができれば、これまで以上に高速で、スケーラブルで、そして開発者体験の良いアプリケーションを構築できることは間違いありません。</p>
<p>さあ、今日からあなたのReact開発に、Server Componentsの力を取り入れてみませんか？</p>
]]></content:encoded>
      <category>Frontend</category>
      <category>React</category>
      <category>RSC</category>
      <category>Next.js</category>
    </item>
  </channel>
</rss>
