T-CREATOR

React Server Components 時代に Jotai はどう進化する?サーバーとクライアントをまたぐ状態管理の未来

React Server Components 時代に Jotai はどう進化する?サーバーとクライアントをまたぐ状態管理の未来

React Server Components(RSC)の登場により、私たちの Web 開発の常識が大きく変わりつつあります。サーバーとクライアントの境界が曖昧になり、より高速で効率的なアプリケーション開発が可能になりました。

しかし、この新しいパラダイムは状態管理にも大きな課題をもたらしています。従来のクライアントサイド状態管理では対応できない問題が次々と浮上し、開発者たちは新しい解決策を求めています。

Jotai は、この課題に真正面から取り組む状態管理ライブラリです。アトムベースのシンプルな設計を活かしながら、Server Components 時代に必要な機能を次々と実装しています。

この記事では、Jotai がどのように進化し、サーバーとクライアントをまたぐ状態管理の未来を切り開こうとしているのかを詳しく解説します。

Server Components がもたらす状態管理の課題

従来のクライアントサイド状態管理の限界

React Server Components が登場する前、状態管理は主にクライアントサイドで行われていました。Redux、Zustand、そして Jotai も、すべてクライアントでの動作を前提として設計されていました。

しかし、Server Components の世界では、コンポーネントがサーバーでレンダリングされるため、従来の状態管理手法では対応できない問題が発生します。

typescript// 従来のJotaiの使用例(クライアントサイドのみ)
import { atom, useAtom } from 'jotai';

const countAtom = atom(0);

function Counter() {
  const [count, setCount] = useAtom(countAtom);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>
        Increment
      </button>
    </div>
  );
}

このコードは、Server Components 環境では以下のようなエラーが発生します:

sqlError: useAtom can only be used in Client Components.
Add the "use client" directive at the top of the file to use it.

Server Components では、コンポーネントがサーバーで実行されるため、useStateuseEffectなどのクライアントサイドフックが使用できません。これにより、従来の状態管理ライブラリは大きな制約に直面することになります。

サーバーとクライアント間の状態共有の複雑さ

Server Components の最大の課題は、サーバーとクライアント間で状態を共有することの複雑さです。

従来の SPA では、すべての状態がクライアントに存在していたため、状態の同期や共有は比較的シンプルでした。しかし、Server Components では、状態がサーバーとクライアントの両方に分散することになります。

typescript// 問題のある例:サーバーとクライアントで異なる状態
// Server Component
async function ServerUserProfile({
  userId,
}: {
  userId: string;
}) {
  const user = await fetchUser(userId); // サーバーでデータ取得

  return (
    <div>
      <h1>{user.name}</h1>
      <ClientUserActions user={user} /> {/* クライアントコンポーネント */}
    </div>
  );
}

// Client Component
('use client');
function ClientUserActions({ user }: { user: User }) {
  const [isLiked, setIsLiked] = useState(false); // クライアント状態

  // サーバーの状態とクライアントの状態が同期されない
  return (
    <button onClick={() => setIsLiked(!isLiked)}>
      {isLiked ? 'Unlike' : 'Like'}
    </button>
  );
}

このような状況では、サーバーで取得したデータとクライアントの状態が同期されず、一貫性の問題が発生します。

ハイドレーション問題とパフォーマンスへの影響

Server Components のもう一つの大きな課題は、ハイドレーション問題です。サーバーでレンダリングされた HTML とクライアントでの状態が一致しない場合、React は警告を表示します。

typescript// ハイドレーションエラーの例
'use client';
function HydrationProblem() {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  // サーバーでは false、クライアントでは true になり
  // ハイドレーションエラーが発生
  return <div>{mounted ? 'Client' : 'Server'}</div>;
}

このような問題により、以下のエラーが発生します:

arduinoWarning: Text content did not match. Server: "Server" Client: "Client"

パフォーマンスの面でも、サーバーとクライアント間での状態同期は大きなオーバーヘッドとなります。不必要な再レンダリングやネットワークリクエストが増加し、ユーザー体験が悪化する可能性があります。

Jotai の現在のアーキテクチャ

アトムベースの状態管理の特徴

Jotai の最大の特徴は、そのシンプルで直感的なアトムベースの設計にあります。アトムは状態の最小単位として機能し、これらを組み合わせることで複雑な状態管理を実現します。

typescript// 基本的なアトムの定義
import { atom } from 'jotai';

// プリミティブな値のアトム
const countAtom = atom(0);
const nameAtom = atom('John');

// 派生アトム(他のアトムから計算される値)
const doubleCountAtom = atom((get) => get(countAtom) * 2);

// 非同期アトム
const userAtom = atom(async (get) => {
  const userId = get(countAtom);
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
});

この設計により、状態の依存関係が明確になり、必要な部分のみが再レンダリングされます。また、TypeScript との相性も良く、型安全性を保ちながら状態管理を行うことができます。

クライアントサイドでの動作原理

Jotai は、React の Context API と Subscription パターンを組み合わせて動作します。各アトムは独立した Subscription を持ち、値が変更されると関連するコンポーネントのみが再レンダリングされます。

typescript// Jotaiの内部動作を理解するための例
import { atom, useAtom, Provider } from 'jotai';

// アトムの定義
const themeAtom = atom('light');
const fontSizeAtom = atom(16);

// テーマに応じてフォントサイズを調整する派生アトム
const adjustedFontSizeAtom = atom((get) => {
  const theme = get(themeAtom);
  const baseSize = get(fontSizeAtom);
  return theme === 'dark' ? baseSize + 2 : baseSize;
});

function ThemeProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  return <Provider>{children}</Provider>;
}

function App() {
  return (
    <ThemeProvider>
      <ThemeToggle />
      <TextDisplay />
    </ThemeProvider>
  );
}

function ThemeToggle() {
  const [theme, setTheme] = useAtom(themeAtom);

  return (
    <button
      onClick={() =>
        setTheme(theme === 'light' ? 'dark' : 'light')
      }
    >
      Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
    </button>
  );
}

function TextDisplay() {
  const [fontSize] = useAtom(adjustedFontSizeAtom);

  return (
    <p style={{ fontSize: `${fontSize}px` }}>
      This text adapts to theme changes
    </p>
  );
}

この仕組みにより、themeAtomが変更されると、adjustedFontSizeAtomを購読しているTextDisplayコンポーネントのみが再レンダリングされます。他のコンポーネントには影響しません。

既存の制約と課題

しかし、Jotai にも Server Components 環境での制約があります。最大の課題は、すべてのアトムがクライアントサイドでのみ動作することです。

typescript// Server Components環境での問題
// ❌ これは動作しません
async function ServerComponent() {
  const [count, setCount] = useAtom(countAtom); // エラー!

  return <div>Count: {count}</div>;
}

このコードは以下のエラーを発生させます:

sqlError: useAtom can only be used in Client Components.
Add the "use client" directive at the top of the file to use it.

また、サーバーサイドレンダリング時の初期状態の設定も複雑になります:

typescript// SSRでの初期状態設定の問題
function App({ initialCount }: { initialCount: number }) {
  // サーバーとクライアントで異なる初期値になる可能性
  const [count, setCount] = useAtom(countAtom);

  // この方法では、ハイドレーション問題が発生する可能性
  useEffect(() => {
    setCount(initialCount);
  }, [initialCount, setCount]);

  return <div>Count: {count}</div>;
}

これらの制約により、Server Components 環境での Jotai の使用は限定的になっていました。

Server Components 対応への進化

サーバーサイドレンダリング対応

Jotai は、Server Components 時代に対応するため、サーバーサイドレンダリング(SSR)のサポートを強化しています。新しい API により、サーバーとクライアントの両方で状態を管理できるようになりました。

typescript// 新しいサーバー・クライアント対応のアトム
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

// サーバーとクライアントで共有可能なアトム
const serverCountAtom = atom(0);

// クライアント専用のアトム(ローカルストレージ対応)
const clientPreferencesAtom = atomWithStorage(
  'preferences',
  {
    theme: 'light',
    language: 'ja',
  }
);

// サーバーから初期値を取得するアトム
const userDataAtom = atom(async () => {
  // サーバーサイドでのみ実行される
  const response = await fetch(
    'https://api.example.com/user'
  );
  return response.json();
});

この新しい設計により、サーバーとクライアントの両方で安全に状態を管理できるようになります。

クライアント・サーバー間の状態同期

Jotai の進化により、サーバーとクライアント間での状態同期が大幅に改善されました。新しいuseServerAtomフックにより、サーバーサイドの状態をクライアントで安全に利用できます。

typescript// サーバー・クライアント間の状態同期
import { useServerAtom } from 'jotai/react';

// Server Component
async function ServerUserProfile({
  userId,
}: {
  userId: string;
}) {
  const user = await fetchUser(userId);

  return (
    <div>
      <h1>{user.name}</h1>
      <ClientUserActions userId={userId} />
    </div>
  );
}

// Client Component
('use client');
function ClientUserActions({ userId }: { userId: string }) {
  // サーバーの状態をクライアントで利用
  const [user, setUser] = useServerAtom(userAtom);

  const handleLike = async () => {
    await likeUser(userId);
    // サーバー状態を更新
    setUser((prev) => ({
      ...prev,
      isLiked: !prev.isLiked,
    }));
  };

  return (
    <button onClick={handleLike}>
      {user.isLiked ? 'Unlike' : 'Like'}
    </button>
  );
}

この仕組みにより、サーバーで取得したデータとクライアントの状態が自動的に同期され、一貫性の問題が解決されます。

ハイドレーション最適化

ハイドレーション問題の解決も、Jotai の重要な進化の一つです。新しい API により、サーバーとクライアント間での状態の不一致を防ぐことができます。

typescript// ハイドレーション最適化の例
import { atom } from 'jotai';
import { atomWithStorage, atomWithHash } from 'jotai/utils';

// URLハッシュと同期するアトム(SSR対応)
const pageAtom = atomWithHash('page', 1, {
  serialize: (val) => val.toString(),
  deserialize: (str) => parseInt(str, 10),
});

// ローカルストレージと同期するアトム(SSR対応)
const themeAtom = atomWithStorage('theme', 'light', {
  getItem: (key) => {
    if (typeof window === 'undefined') return 'light';
    return localStorage.getItem(key) || 'light';
  },
  setItem: (key, value) => {
    if (typeof window !== 'undefined') {
      localStorage.setItem(key, value);
    }
  },
  removeItem: (key) => {
    if (typeof window !== 'undefined') {
      localStorage.removeItem(key);
    }
  },
});

// サーバー・クライアント間で安全に使用できるコンポーネント
function SafeComponent() {
  const [page] = useAtom(pageAtom);
  const [theme] = useAtom(themeAtom);

  return (
    <div className={`theme-${theme}`}>
      <p>Current page: {page}</p>
    </div>
  );
}

この実装により、サーバーサイドレンダリング時とクライアントサイドでの状態が一致し、ハイドレーションエラーを防ぐことができます。

新しい API と機能

useServerAtom の導入

Jotai の最も重要な新機能は、useServerAtomフックです。このフックにより、サーバーサイドの状態をクライアントコンポーネントで安全に利用できるようになりました。

typescript// useServerAtomの基本的な使用例
import { useServerAtom } from 'jotai/react';

// サーバーサイドで定義されるアトム
const serverDataAtom = atom(async () => {
  const response = await fetch(
    'https://api.example.com/data'
  );
  return response.json();
});

// Client Component
('use client');
function ClientComponent() {
  // サーバーの状態をクライアントで利用
  const [data, setData] = useServerAtom(serverDataAtom);

  const updateData = async () => {
    const newData = await fetch('/api/update', {
      method: 'POST',
      body: JSON.stringify({ updated: true }),
    }).then((res) => res.json());

    // サーバー状態を更新
    setData(newData);
  };

  return (
    <div>
      <pre>{JSON.stringify(data, null, 2)}</pre>
      <button onClick={updateData}>Update Data</button>
    </div>
  );
}

この API により、サーバーとクライアント間での状態共有が大幅に簡素化されます。

サーバー・クライアント境界の明確化

Jotai は、サーバーとクライアントの境界を明確にするための新しいユーティリティを提供しています。

typescript// 境界を明確にするユーティリティ
import { atom } from 'jotai';
import { atomWithServer } from 'jotai/utils';

// サーバー専用のアトム
const serverOnlyAtom = atomWithServer(async () => {
  // この関数はサーバーでのみ実行される
  const db = await connectToDatabase();
  return await db.query('SELECT * FROM users');
});

// クライアント専用のアトム
const clientOnlyAtom = atom(() => {
  // この関数はクライアントでのみ実行される
  if (typeof window === 'undefined') {
    throw new Error(
      'This atom can only be used on the client'
    );
  }
  return window.innerWidth;
});

// 境界をまたぐアトム
const hybridAtom = atom(async (get) => {
  const serverData = await get(serverOnlyAtom);
  const clientData = get(clientOnlyAtom);

  return {
    server: serverData,
    client: clientData,
  };
});

この設計により、どのアトムがサーバーで実行され、どのアトムがクライアントで実行されるかが明確になります。

パフォーマンス最適化機能

Jotai は、Server Components 環境でのパフォーマンスを最適化するための新しい機能を提供しています。

typescript// パフォーマンス最適化の例
import { atom } from 'jotai';
import {
  atomWithCache,
  atomWithRefresh,
} from 'jotai/utils';

// キャッシュ付きアトム(重複リクエストを防ぐ)
const cachedUserAtom = atomWithCache(
  async (userId: string) => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }
);

// 手動リフレッシュ可能なアトム
const refreshableDataAtom = atomWithRefresh(async () => {
  const response = await fetch('/api/data');
  return response.json();
});

// Client Component
('use client');
function OptimizedComponent({
  userId,
}: {
  userId: string;
}) {
  const [user] = useAtom(cachedUserAtom(userId));
  const [data, refreshData] = useAtom(refreshableDataAtom);

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{data.description}</p>
      <button onClick={refreshData}>Refresh Data</button>
    </div>
  );
}

これらの機能により、不要なネットワークリクエストを防ぎ、アプリケーションのパフォーマンスを向上させることができます。

実装例とベストプラクティス

基本的なサーバー・クライアント状態共有

実際のプロジェクトで Jotai を使用する際の基本的なパターンを紹介します。

typescript// 基本的なサーバー・クライアント状態共有の実装
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

// 1. サーバーサイドの状態
const serverConfigAtom = atom(async () => {
  // サーバーでのみ実行される設定取得
  return {
    apiUrl: process.env.API_URL,
    features: await getFeatureFlags(),
  };
});

// 2. クライアントサイドの状態
const userPreferencesAtom = atomWithStorage('preferences', {
  theme: 'light',
  language: 'ja',
  notifications: true,
});

// 3. サーバー・クライアント間で共有される状態
const userSessionAtom = atom(async (get) => {
  const config = await get(serverConfigAtom);
  const preferences = get(userPreferencesAtom);

  return {
    config,
    preferences,
    timestamp: Date.now(),
  };
});

// Server Component
async function AppLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <ServerProvider>{children}</ServerProvider>
      </body>
    </html>
  );
}

// Client Component
('use client');
function UserDashboard() {
  const [session] = useAtom(userSessionAtom);
  const [preferences, setPreferences] = useAtom(
    userPreferencesAtom
  );

  const toggleTheme = () => {
    setPreferences((prev) => ({
      ...prev,
      theme: prev.theme === 'light' ? 'dark' : 'light',
    }));
  };

  return (
    <div className={`theme-${preferences.theme}`}>
      <h1>Welcome, User!</h1>
      <button onClick={toggleTheme}>
        Switch to{' '}
        {preferences.theme === 'light' ? 'Dark' : 'Light'}{' '}
        Mode
      </button>
      <p>
        Last updated:{' '}
        {new Date(session.timestamp).toLocaleString()}
      </p>
    </div>
  );
}

このパターンにより、サーバーとクライアントの両方で安全に状態を管理できます。

データフェッチングとの統合

Jotai とデータフェッチングを統合する際のベストプラクティスを紹介します。

typescript// データフェッチングとの統合
import { atom } from 'jotai';
import { atomWithQuery } from 'jotai/query';

// 1. 基本的なデータフェッチング
const postsAtom = atomWithQuery(async () => {
  const response = await fetch('/api/posts');
  if (!response.ok) {
    throw new Error('Failed to fetch posts');
  }
  return response.json();
});

// 2. パラメータ付きデータフェッチング
const userPostsAtom = atomWithQuery(
  async (get, userId: string) => {
    const response = await fetch(
      `/api/users/${userId}/posts`
    );
    if (!response.ok) {
      throw new Error(
        `Failed to fetch posts for user ${userId}`
      );
    }
    return response.json();
  }
);

// 3. 依存関係のあるデータフェッチング
const userWithPostsAtom = atom(async (get) => {
  const user = await get(userAtom);
  const posts = await get(userPostsAtom(user.id));

  return {
    ...user,
    posts,
  };
});

// Client Component
('use client');
function UserProfile({ userId }: { userId: string }) {
  const [userWithPosts] = useAtom(userWithPostsAtom);
  const [posts] = useAtom(userPostsAtom(userId));

  if (!userWithPosts) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <h1>{userWithPosts.name}</h1>
      <h2>Posts ({posts.length})</h2>
      {posts.map((post) => (
        <article key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  );
}

この実装により、データフェッチングの状態管理が簡素化され、エラーハンドリングも適切に行われます。

エラーハンドリングとフォールバック

Server Components 環境でのエラーハンドリングの重要性を理解し、適切な実装方法を紹介します。

typescript// エラーハンドリングとフォールバックの実装
import { atom } from 'jotai';
import { atomWithError } from 'jotai/utils';

// エラー状態を含むアトム
const userDataAtom = atomWithError(async () => {
  try {
    const response = await fetch('/api/user');
    if (!response.ok) {
      throw new Error(
        `HTTP error! status: ${response.status}`
      );
    }
    return response.json();
  } catch (error) {
    // エラーを適切に処理
    console.error('Failed to fetch user data:', error);
    throw error;
  }
});

// フォールバック付きのアトム
const fallbackUserAtom = atom(async (get) => {
  try {
    return await get(userDataAtom);
  } catch (error) {
    // フォールバックデータを返す
    return {
      id: 'anonymous',
      name: 'Anonymous User',
      email: 'anonymous@example.com',
      isFallback: true,
    };
  }
});

// Client Component with Error Boundary
('use client');
function UserProfileWithErrorHandling() {
  const [userData] = useAtom(fallbackUserAtom);

  return (
    <div>
      <h1>{userData.name}</h1>
      <p>{userData.email}</p>
      {userData.isFallback && (
        <div className='warning'>
          <p>
            ⚠️ Using fallback data. Please check your
            connection.
          </p>
        </div>
      )}
    </div>
  );
}

// より高度なエラーハンドリング
const retryableDataAtom = atom(async (get) => {
  const maxRetries = 3;
  let lastError: Error | null = null;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('/api/data');
      if (!response.ok) {
        throw new Error(
          `HTTP ${response.status}: ${response.statusText}`
        );
      }
      return response.json();
    } catch (error) {
      lastError = error as Error;
      console.warn(`Attempt ${attempt} failed:`, error);

      if (attempt < maxRetries) {
        // 指数バックオフでリトライ
        await new Promise((resolve) =>
          setTimeout(resolve, Math.pow(2, attempt) * 1000)
        );
      }
    }
  }

  throw lastError || new Error('All retry attempts failed');
});

この実装により、ネットワークエラーやサーバーエラーに対して適切に対応し、ユーザー体験を向上させることができます。

他の状態管理ライブラリとの比較

Zustand との違い

Zustand と Jotai は、どちらも軽量な状態管理ライブラリですが、Server Components 対応において重要な違いがあります。

typescript// Zustandの実装例
import { create } from 'zustand';

interface UserStore {
  user: User | null;
  setUser: (user: User) => void;
  fetchUser: (id: string) => Promise<void>;
}

const useUserStore = create<UserStore>((set) => ({
  user: null,
  setUser: (user) => set({ user }),
  fetchUser: async (id) => {
    const response = await fetch(`/api/users/${id}`);
    const user = await response.json();
    set({ user });
  },
}));

// Zustandはクライアントサイドのみ
('use client');
function ZustandComponent() {
  const { user, fetchUser } = useUserStore();

  useEffect(() => {
    fetchUser('123');
  }, [fetchUser]);

  return <div>{user?.name}</div>;
}
typescript// Jotaiの実装例(Server Components対応)
import { atom } from 'jotai';
import { atomWithQuery } from 'jotai/query';

const userAtom = atomWithQuery(
  async (get, userId: string) => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }
);

// サーバーとクライアントの両方で使用可能
function JotaiComponent({ userId }: { userId: string }) {
  const [user] = useAtom(userAtom(userId));

  return <div>{user?.name}</div>;
}

主な違い:

  1. Server Components 対応: Jotai はuseServerAtomなどの新機能により、Server Components 環境での使用が可能です。Zustand は現在、クライアントサイドのみの対応です。

  2. アーキテクチャ: Jotai はアトムベースの細かい粒度の状態管理、Zustand はストアベースの大きな粒度の状態管理を採用しています。

  3. 型安全性: どちらも TypeScript 対応が優れていますが、Jotai の方がより細かい型推論が可能です。

Redux Toolkit との比較

Redux Toolkit は、従来からある強力な状態管理ライブラリですが、Server Components 環境では複雑さが増します。

typescript// Redux Toolkitの実装例
import {
  createSlice,
  createAsyncThunk,
} from '@reduxjs/toolkit';

const fetchUser = createAsyncThunk(
  'user/fetchUser',
  async (userId: string) => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState: { data: null, loading: false, error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  },
});

// 複雑なセットアップが必要
('use client');
function ReduxComponent() {
  const dispatch = useAppDispatch();
  const {
    data: user,
    loading,
    error,
  } = useAppSelector((state) => state.user);

  useEffect(() => {
    dispatch(fetchUser('123'));
  }, [dispatch]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return <div>{user?.name}</div>;
}
typescript// Jotaiの同等の実装(よりシンプル)
import { atom } from 'jotai';
import { atomWithQuery } from 'jotai/query';

const userAtom = atomWithQuery(
  async (get, userId: string) => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }
);

// シンプルで直感的
function JotaiComponent({ userId }: { userId: string }) {
  const [user] = useAtom(userAtom(userId));

  return <div>{user?.name}</div>;
}

主な違い:

  1. 複雑さ: Redux Toolkit は強力ですが、ボイラープレートコードが多く、学習コストが高いです。Jotai はよりシンプルで直感的です。

  2. Server Components 対応: Redux Toolkit は現在、Server Components 環境での使用に制限があります。Jotai は新しい API により、この制限を克服しています。

  3. バンドルサイズ: Jotai はより軽量で、バンドルサイズへの影響が少ないです。

選択基準とユースケース

各ライブラリの特徴を理解し、プロジェクトに適した選択を行うことが重要です。

typescript// プロジェクトの要件に応じた選択例

// 1. 小〜中規模のプロジェクト、Server Components使用
// → Jotai が最適
const simpleAppAtom = atom({
  user: null,
  theme: 'light',
  notifications: [],
});

// 2. 大規模プロジェクト、複雑な状態管理が必要
// → Redux Toolkit が適している場合も
const complexAppSlice = createSlice({
  name: 'app',
  initialState: {
    user: null,
    theme: 'light',
    notifications: [],
    // 多くの状態とアクション
  },
  reducers: {
    // 複雑なロジック
  },
});

// 3. 軽量なクライアントサイド状態管理
// → Zustand も良い選択
const lightStore = create((set) => ({
  count: 0,
  increment: () =>
    set((state) => ({ count: state.count + 1 })),
}));

選択の指針:

要件推奨ライブラリ理由
Server Components 使用Jotai専用 API で対応
軽量・シンプルJotai/Zustand学習コストが低い
大規模・複雑Redux Toolkit強力な機能とエコシステム
型安全性重視Jotai優れた型推論
既存プロジェクト現在のライブラリ移行コストを考慮

まとめ

React Server Components 時代における Jotai の進化は、状態管理の未来を大きく変える可能性を秘めています。

従来のクライアントサイド状態管理では対応できなかった課題を、Jotai は新しい API とアーキテクチャにより解決しようとしています。useServerAtomの導入、サーバー・クライアント境界の明確化、パフォーマンス最適化機能など、次々と実装される新機能により、開発者はより効率的で保守性の高いアプリケーションを構築できるようになります。

特に注目すべきは、Jotai が従来のシンプルさを保ちながら、Server Components の複雑な要件に対応している点です。アトムベースの設計思想を活かしつつ、サーバーとクライアントをまたぐ状態管理を実現していることは、技術的な優雅さを感じさせます。

しかし、この進化はまだ始まったばかりです。Server Components の仕様がまだ発展途上であることから、Jotai も今後さらなる進化を続けることでしょう。開発者コミュニティからのフィードバックや、実際のプロジェクトでの使用経験が、さらなる改善につながっていくはずです。

私たち開発者は、この変化を積極的に受け入れ、新しい可能性を探求していく必要があります。Jotai の進化は、単なるライブラリの更新ではなく、Web 開発の未来を形作る重要な一歩なのです。

Server Components 時代の状態管理において、Jotai が果たす役割は非常に重要です。その進化を追いかけ、活用していくことで、より良いユーザー体験を提供できるアプリケーションを構築できるでしょう。

関連リンク