ClaudeCodeが作った画像一覧が重すぎたので、サーバーコンポーネント+サーバーアクションにした話
2025年07月28日
背景
ClaudeCodeを使って生成した画像一覧を、Next.jsのクライアントコンポーネントで表示していたところ、以下のような課題が発生しました。
- ページ読み込みが遅い
- メモリ使用量が高い
- 更新・削除アクション時に毎回状態管理が面倒
課題
当初はクライアントコンポーネント側で以下のような処理を行っていました。
- APIから画像一覧をfetch
useStateuseEffectで状態管理- 削除・更新ボタンもクライアント側で処理
結果として、画像が増えるほどパフォーマンスが悪化。
改善方針
- 画像一覧の取得 → サーバーコンポーネントで実行
- 更新・削除操作 → App Routerのサーバーアクションで対応
実装
サーバーコンポーネントで画像一覧取得
// app/images/page.tsx
import { getAllImages } from '@/lib/image';
export default async function ImageListPage() {
const images = await getAllImages();
return (
<div>
{images.map((img) => (
<div key={img.id}>
<img src={img.url} alt={img.name} />
{/* ボタンは後述 */}
</div>
))}
</div>
);
}
サーバーアクションで削除・更新
// app/images/actions.ts
'use server';
import { deleteImage, updateImage } from '@/lib/image';
export async function deleteImageAction(id: string) {
await deleteImage(id);
}
export async function updateImageAction(id: string, newData: any) {
await updateImage(id, newData);
}
ボタンとの連携(Client Component)
// app/images/DeleteButton.tsx
'use client';
import { deleteImageAction } from './actions';
export function DeleteButton({ id }: { id: string }) {
const handleDelete = async () => {
await deleteImageAction(id);
};
return <button onClick={handleDelete}>削除</button>;
}
呼び出し側でボタンを使う
// app/images/page.tsx 内の一部
import { DeleteButton } from './DeleteButton';
{images.map((img) => (
<div key={img.id}>
<img src={img.url} alt={img.name} />
<DeleteButton id={img.id} />
</div>
))}
効果
- 初回レンダリングが高速化:CSR → SSRへの移行で不要なJSロジックを排除
- 状態管理がシンプルに:クライアントでのuseState/useEffectが不要に
- UXが安定:ページ遷移や再描画をSSRで一貫して処理可能に
まとめ
サーバーアクションと組み合わせることで、以下を両立できます:
- ユーザー体験の改善(表示が速い)
- コードの可読性・保守性の向上
- クライアント側の状態管理の削減
また、サーバーコンポーネントの更新にはrevalidatePathを使いました
同じように画像一覧や重いデータを扱っていて、パフォーマンスや状態管理に悩んでいる方の参考になれば幸いです。