はじめに
皆さん、プロジェクトの締め切りに追われ、「速さ」の追求を忘れていることはありませんか?特にECサイトの商品一覧や検索結果のページでは、ほんの少しの遅延がユーザー体験、そして売り上げにも大きな影響を与えることがあります。今回は、Reactを使った表示速度の改善に役立つ戦略について、わたくし株式会社ファストコーディングのフルスタックエンジニア、Mr.Fireがお伝えいたします。
背景と課題:普通にSPA&APIサーバを強くするじゃダメなの?
あるプロジェクトで大量にある商品を表示する、商品一覧ページの高速化を求められたことがありました。しかし、そもそもの商品点数による初期データの重さや複数ソースからのデータ取得の遅さが、ユーザー体験を損なうリスクを生んでいました。一般的なクライアントサイドのレンダリング方法では、APIの応答待ちやデータ処理に時間がかかってしまいます。これを解決するには、端的にAPIサーバの処理能力を上げるではなく、もう少し工夫が必要です。
改善のポイント:Reactサーバーコンポーネントとストリーミングの活用
ここで注目すべきは、Reactサーバーコンポーネント(RSC)とストリーミングSSR(サーバーサイドレンダリング)の組み合わせです。特にAPIからの重いデータ取得に効果的です。Webディレクターやプロジェクトマネージャーの皆さん、こんな時はぜひ「Reactサーバーコンポーネント」の導入を検討してみてください。Next.js(14+)の機能を活用すると、初期表示速度と体感速度が格段に向上します。
RSC(React Server Components)とは?
サーバーで実行されるサーバー専用コンポーネント。UIの“表示用ロジック”とデータ取得をサーバーに寄せ、クライアントへ配るJSを最小化できます。
- 得意なこと: DB呼び出し、重い集計やデータ整形、機密値の取り扱い(環境変数/トークン等)
- 不得意なこと:
window/documentなどのブラウザAPI、DOMイベント(これはクライアントコンポーネントへ移動) - 設計の軸: 表示(RSC) と インタラクション(Client) を分離して考える。
RSC(React Server Components)参考情報
- React公式(日本語)|サーバコンポーネント:RSCの位置づけ(サーバ/クライアントの役割分離、どこで実行されるか、非同期処理など)を確認できます。
- Next.js公式(日本語)|サーバーコンポーネント:App Router前提で、Next.jsにおけるRSCの使い方・ベストプラクティスがまとまっています。
Streaming SSR(ストリーミングSSR)とは?
サーバーからHTMLを小さなチャンクで順次送出し、ページ全体を待たずにできた所から描画します。TTFB/LCPを改善し、“動いている感”を早期に提供。
- 向くケース: ヒーローセクション、要約、リストの先頭など「先に出せる領域」があるページ。
- 合わせ技:
<Suspense>境界を設け、優先度が高い部分からストリーム。
Streaming SSR(ストリーミングSSR)参考情報
- ・Zenn|Suspenseを活用したStreamingとSkeleton:
<Suspense>境界を使ったストリーム表示の実践的な導入例。 - ・Zenn|ReactのStreaming SSRをエッジでやる:
renderToReadableStreamなどReact 18のストリーミングAPIを使った手元検証と考察。
実装の流れ
1. ルートをサーバーコンポーネント化
まず最初のステップとして、ルートをReactサーバーコンポーネント化して、クライアントの負担を軽減します。これにより、APIから取得したデータをサーバーで効率よく処理し、迅速にクライアントに届けることができます。
// app/products/page.tsx ← デフォで Server Component
import { fetchProducts } from '@/lib/data';
export default async function ProductsPage() {
const products = await fetchProducts({ limit: 20 });
return (
<main>
<h1>商品一覧</h1>
<ul>
{products.map(p => (
<li key={p.id}>{p.name} — {p.price.toLocaleString()}円</li>
))}
</ul>
</main>
);
}
注意点
- ブラウザAPIが必要なUIは別ファイルに分けて、先頭に
"use client"。 - HTML肥大化を避けるため、“先出し領域”と“後送出領域”に分割(次ステップ)。
2. 重要部分はSuspenseで優先的にストリーミング
ユーザーが興味を引く部分や、主要情報を優先的にストリーミングします。これにより、必要な情報はすぐに表示され、ユーザーの視線を引きつけます。
// app/products/page.tsx
import { Suspense } from 'react';
import { Hero } from './sections/hero'; // Server
import { TopList } from './sections/top-list'; // Server(軽量)
import { RestList } from './sections/rest-list'; // Server(重い)
export default function ProductsPage() {
return (
<main>
<Suspense fallback={<div style={{height: 240}} aria-busy>読み込み中…</div>}>
<Hero />
</Suspense>
<Suspense fallback={<p aria-busy>トップ商品を取得中…</p>}>
<TopList limit={8} />
</Suspense>
<Suspense fallback={null}>
<RestList />
</Suspense>
</main>
);
}
注意点
fallbackは固定高さを確保してCLS防止。aria-busyでA11y配慮。- 重い処理は
<Suspense>境界の下に分離し、ストリーム効果を最大化。
3. その他の部分はゆっくり読み込み
重要ではない部分はコンポーネントを使って遅延読み込みします。こうすることで、全体の読み込み時間を短縮できます。例えばフィルタリングはこんなふうにクライアントサイドにもっていきます。
// app/products/filters.tsx
"use client";
import { useTransition, useState } from 'react';
export function Filters() {
const [pending, start] = useTransition();
const [q, setQ] = useState('');
return (
<form onSubmit={(e) => {
e.preventDefault();
start(() => {
const url = new URL(window.location.href);
url.searchParams.set('q', q);
window.history.pushState({}, '', url);
});
}}>
<input value={q} onChange={e => setQ(e.target.value)} placeholder="キーワード" />
<button disabled={pending}>{pending ? '検索中…' : '検索'}</button>
</form>
);
}
注意点
"use client"の適用範囲を最小に(親に付けると子までJS化)。useTransitionで体感を滑らかに(ボタン無効化/ローディング状態)。
4. 画像の表示を最適化
画像表示にはNext.jsのコンポーネントを使い、読み込み順序を最適化します。これにより、必要な画像を迅速に表示し、ページ全体の初速が改善されます。
// app/products/sections/hero.tsx
import Image from 'next/image';
export async function Hero() {
return (
<section className="hero">
<Image
src="/hero.jpg"
alt="特集バナー"
priority
width={1600}
height={900}
sizes="(max-width: 768px) 100vw, 1200px"
style={{ width: '100%', height: 'auto' }}
/>
</section>
);
}
注意点
- ヒーロー等は
priority、下部画像はデフォルトで遅延。 - width/heightを明示してCLS防止。
sizes最適化。
5. メタデータはサーバーで生成
SEOを考えて、メタデータはサーバー側で生成します。これにより、検索エンジン最適化が図れ、ユーザーに見つけてもらいやすくなります。
// app/products/metadata.ts ないし page.tsx の export const metadata
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: '商品一覧 | 例ショップ',
description: '新着や人気順で商品を探せます。',
openGraph: { title: '商品一覧', description: '新着・人気順で商品を探せます', images: ['/og/products.png'] }
};
注意点
- 動的値が必要なら
generateMetadataを使用。
パフォーマンスと効果測定
これらの改善施策により、TTFBやLCP(Largest Contentful Paint)、INPも改善が期待できます。データの往復回数を減らし、JSバンドルサイズも削減できて、ページ全体のパフォーマンスの向上になります。
実際の体験から学ぶ
今年、大手企業向けの製品検索ページにこれらの技法を導入しました。成果として、TTFBが約20%改善され、ユーザーの離脱率が7%減少しました。その結果、ページビューが20%増加し、実際に効果が現れました。
まとめ
Reactを使っていること前提、にはなっていますが、ReactサーバーコンポーネントとストリーミングSSRを使った改善策の一例をご案内しました。構築最初の段階でこの設計を入れることができれば、全体的な速度感が結構変わってきます。「速い体感」はユーザーの心を捉える大切な要素であり、導入によるメリットは大きいです。
株式会社ファストコーディングでは、このような速度改善の取り組みをサポートしています。興味がある方は、ぜひこちらからお問い合わせください。

