
【実装】React Context → zustand移行ガイド(persist + レガシー値マイグレーション)
はじめに
React Contextで管理していたキャラクター選択と言語設定をzustandに移行しました。Contextの問題点(Provider地獄、不要な再レンダリング)を解消しつつ、localStorageへの永続化も追加しています。
この記事でわかること
-
React Context → zustandの具体的な移行手順
-
persist ミドルウェアでlocalStorage永続化する方法
-
レガシー値のマイグレーション(merge関数)パターン
-
Provider削除によるlayout.tsxの簡素化
Before: React Context
// contexts/CharacterContext.tsx
const CharacterContext = createContext<CharacterContextType>(...);
export function CharacterProvider({ children }) {
const [selected, setSelected] = useState("akari");
// localStorage読み込み、レガシー値変換...
return (
<CharacterContext.Provider value={{...}}>
{children}
</CharacterContext.Provider>
);
}
// layout.tsx — Provider地獄
<AuthSessionProvider>
<LanguageProvider>
<CharacterProvider>
{children}
</CharacterProvider>
</LanguageProvider>
</AuthSessionProvider>
After: zustand + persist
// stores/useCharacterStore.ts
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface CharacterStore {
selectedSlug: string;
setSelectedSlug: (slug: string) => void;
}
const LEGACY_SLUG_MAP: Record<string, string> = {
blue: "haruto",
red: "akari",
};
export const useCharacterStore = create<CharacterStore>()(
persist(
(set) => ({
selectedSlug: "akari",
setSelectedSlug: (slug) => set({ selectedSlug: slug }),
}),
{
name: "selectedCharacter", // localStorageキー
merge: (persisted, current) => {
const p = persisted as Partial<CharacterStore> | undefined;
const raw = p?.selectedSlug ?? current.selectedSlug;
return { ...current, selectedSlug: LEGACY_SLUG_MAP[raw] ?? raw };
},
},
),
);
レガシー値マイグレーションのポイント
persist の merge オプションは、localStorageから読み込んだ値とデフォルト値をマージする関数です。ここでレガシー値の変換を行うことで、既存ユーザーのデータを壊さずに移行できます。
layout.tsxの変更
// Before: Provider地獄
<AuthSessionProvider>
<LanguageProvider>
<CharacterProvider>{children}</CharacterProvider>
</LanguageProvider>
</AuthSessionProvider>
// After: Providerが不要に
<AuthSessionProvider>
{children}
</AuthSessionProvider>
移行チェックリスト
-
zustand storeファイルを作成(persist設定、レガシーマイグレーション含む)
-
各コンポーネントのimportをuseContext → useXxxStore に置換
-
layout.tsxからProvider wrapperを削除
-
旧Contextファイルを削除
-
ビルド・動作確認(localStorage既存値の互換性確認含む)
まとめ
-
zustandはProvider不要で、import して使うだけ → Provider地獄解消
-
persist ミドルウェアで localStorage永続化がワンライナー
-
merge関数でレガシー値のマイグレーションを宣言的に記述できる
-
storeにはUIに必要な最小限のstate(slug等)だけ持ち、派生データはhookで計算する
最新記事
- 【設定・環境構築】OpenNext でNext.js SSGサイトをCloudflare Workersにデプロイする完全ガイド
2026/3/19
- 【実装】Notion calloutブロックをNext.jsでカラフルなUIコンポーネントとして表示する
2026/3/19
- 【トラブルシューティング】Cloudflare Pages → Workers 移行で遭遇したEdge Runtime問題集
2026/3/19
- 【実践】Next.js 13→16メジャーアップグレードの全記録 — 破壊的変更と対応策
2026/3/19
- 【自動化】Gemini Imagen APIでブログのeyecatch画像を自動生成してR2にアップロードする
2026/3/19
- 【実装】Notion APIでブログシステムを構築する(Next.js 13 App Router × SDK v5)
2026/3/19
- 【移行ガイド】microCMSからNotion APIへブログCMSを完全移行する
2026/3/19
- 【トラブルシューティング】本番デプロイで遭遇した問題と解決策まとめ
2026/3/15
- 【環境構築】Next.js × Cloudflare Workers の本番環境を一から構築する
2026/3/15
- 【設定・環境構築】Neon → Prisma Postgres 移行とローカル開発環境の構築
2026/2/26


