T-CREATOR

Apollo を最短導入:Vite/Next.js/Remix での初期配線テンプレ集

Apollo を最短導入:Vite/Next.js/Remix での初期配線テンプレ集

GraphQL を使った開発において、Apollo Client は最も人気が高く、信頼性の高いクライアントライブラリです。しかし、Vite、Next.js、Remix といった異なるフレームワークで Apollo を導入する際、それぞれ異なる設定が必要になることが多く、初心者の方は戸惑ってしまうことでしょう。

この記事では、これら 3 つの主要フレームワークで Apollo Client を最短で導入するための具体的な設定方法とテンプレートを詳しくご紹介します。各フレームワークの特徴を活かした最適な設定パターンを理解することで、効率的な GraphQL 開発をスタートできるはずです。

背景

GraphQL クライアントとしての Apollo Client の位置づけ

Apollo Client は、GraphQL を使用したアプリケーション開発において、事実上のスタンダードとなっているクライアントライブラリです。その理由は以下の特徴にあります。

Apollo Client の主要な特徴

機能説明メリット
強力なキャッシングインメモリキャッシュによる高速化パフォーマンス向上と UX 改善
宣言的データフェッチReact hooks による直感的な API開発効率の向上
エラーハンドリング統一されたエラー管理保守性の向上
リアルタイム対応Subscription 機能動的なアプリケーション構築
型安全性TypeScript との親和性バグの早期発見

現在の GraphQL エコシステムでは、Apollo Client が圧倒的なシェアを誇っており、豊富なドキュメントとコミュニティサポートが開発を強力にバックアップしています。

現代フロントエンド開発での GraphQL の重要性

GraphQL は REST API の課題を解決する次世代の API 仕様として、多くの企業で採用されています。

データフェッチの進化を図で確認してみましょう。

mermaidflowchart TD
    rest[REST API] -->|複数エンドポイント| multiple["/users<br/>/posts<br/>/comments"]
    graphql[GraphQL] -->|単一エンドポイント| single["/graphql"]

    multiple -->|問題| issues["・Over-fetching<br/>・Under-fetching<br/>・多数のリクエスト"]
    single -->|解決| benefits["・必要なデータのみ取得<br/>・1回のリクエスト<br/>・型安全性"]

    style rest fill:#ffd6d6
    style graphql fill:#d6ffd6
    style issues fill:#ffeeee
    style benefits fill:#eeffee

GraphQL を採用することで、フロントエンド開発者はより効率的で保守性の高いアプリケーションを構築できるようになります。

課題

各フレームワークでの Apollo 導入時の設定の複雑さ

Apollo Client の導入において、開発者が直面する主な課題を整理してみます。

mermaidflowchart LR
    dev[開発者] -->|導入検討| frameworks[フレームワーク選択]
    frameworks --> vite[Vite]
    frameworks --> nextjs[Next.js]
    frameworks --> remix[Remix]

    vite -->|設定方法が| vite_issues["・SPA向け設定<br/>・バンドル最適化<br/>・開発サーバー設定"]
    nextjs -->|設定方法が| next_issues["・SSR/SSG対応<br/>・App/Pages Router<br/>・ハイドレーション"]
    remix -->|設定方法が| remix_issues["・Loader連携<br/>・サーバーサイド<br/>・プログレッシブ対応"]

    vite_issues --> confusion[設定の混乱]
    next_issues --> confusion
    remix_issues --> confusion

    style confusion fill:#ffdddd

この図が示すように、各フレームワークで異なるアプローチが必要になるため、開発者は混乱しがちです。

主な課題点

  1. フレームワーク固有の設定差異

    • Vite:SPA 向けのシンプルな設定が基本
    • Next.js:SSR/SSG を考慮した複雑な設定が必要
    • Remix:サーバーサイドとの連携を重視した設定
  2. ドキュメントの分散

    • 公式ドキュメントが各フレームワークごとに分かれている
    • ベストプラクティスが統一されていない
  3. 初期設定のエラー頻発

    • Provider の設定ミス
    • TypeScript 型定義の不備
    • キャッシュ設定の誤解

初期設定での躓きポイントとベストプラクティスの不明確さ

多くの開発者が Apollo Client の導入で躓く共通のポイントがあります。

よくある躓きポイント

段階問題影響
インストール必要なパッケージの不足起動エラー
設定Provider の配置ミスReact エラー
型定義TypeScript 設定不備開発効率低下
キャッシュデフォルト設定の誤解パフォーマンス問題

これらの課題を解決するために、最小限で確実に動作する設定パターンを確立することが重要です。

解決策

最小限の設定での Apollo Client 導入手法

複雑な設定を避けて、まずは動作する最小限の構成から始めることが成功の鍵です。

共通の設定戦略を図で示します。

mermaidflowchart TD
    strategy[最小限設定戦略] --> step1[1. 基本パッケージ導入]
    strategy --> step2[2. 共通設定作成]
    strategy --> step3[3. フレームワーク固有調整]
    strategy --> step4[4. 動作確認]

    step1 --> install["yarn add @apollo/client graphql"]
    step2 --> config["ApolloClient インスタンス作成"]
    step3 --> specific["Provider 設定<br/>フレームワーク特有の対応"]
    step4 --> test["サンプルクエリ実行"]

    test --> success{成功?}
    success -->|Yes| extend[機能拡張]
    success -->|No| debug[設定確認]
    debug --> step2

    style success fill:#ffffcc
    style extend fill:#ccffcc
    style debug fill:#ffcccc

各フレームワーク共通の設定パターン

すべてのフレームワークで使える基本的な Apollo Client の設定パターンを確立します。

共通設定ファイルの構成

typescript// apollo-client.ts (共通設定ファイル)
import {
  ApolloClient,
  InMemoryCache,
  HttpLink,
} from '@apollo/client';

// GraphQL エンドポイントの設定
const httpLink = new HttpLink({
  uri:
    process.env.GRAPHQL_ENDPOINT ||
    'http://localhost:4000/graphql',
});

基本的なキャッシュ設定

typescript// キャッシュの初期設定
const cache = new InMemoryCache({
  typePolicies: {
    // 基本的なキャッシュポリシー
    Query: {
      fields: {
        // ページネーション対応の基本設定
        posts: {
          merge: true,
        },
      },
    },
  },
});

Apollo Client インスタンスの作成

typescript// Apollo Client の基本設定
export const apolloClient = new ApolloClient({
  link: httpLink,
  cache: cache,
  // 開発環境でのデバッグ有効化
  connectToDevTools: process.env.NODE_ENV === 'development',
});

この共通設定をベースに、各フレームワークで必要な調整を行います。

具体例

Vite での Apollo 導入

Vite は高速な開発サーバーと最適化されたビルド機能を提供する、モダンなフロントエンド開発ツールです。SPA(Single Page Application)の開発に特化しており、Apollo Client との相性も抜群です。

環境構築から基本設定まで

1. Vite プロジェクトの作成

bash# Vite + React + TypeScript プロジェクトの作成
yarn create vite my-apollo-app --template react-ts
cd my-apollo-app

2. Apollo Client のインストール

bash# Apollo Client と GraphQL の必須パッケージ
yarn add @apollo/client graphql

# 開発用の型生成ツール(オプション)
yarn add -D @graphql-codegen/cli @graphql-codegen/typescript

3. 環境変数の設定

Vite では環境変数に VITE_ プレフィックスが必要です。

bash# .env.local
VITE_GRAPHQL_ENDPOINT=http://localhost:4000/graphql

4. Apollo Client の設定ファイル作成

typescript// src/lib/apollo-client.ts
import {
  ApolloClient,
  InMemoryCache,
  HttpLink,
} from '@apollo/client';

const httpLink = new HttpLink({
  uri: import.meta.env.VITE_GRAPHQL_ENDPOINT,
});

export const apolloClient = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache(),
  connectToDevTools: import.meta.env.DEV,
});

Vite では process.env の代わりに import.meta.env を使用する点に注意してください。

5. React アプリケーションでの Provider 設定

typescript// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ApolloProvider } from '@apollo/client';
import { apolloClient } from './lib/apollo-client';
import App from './App';
import './index.css';

ReactDOM.createRoot(
  document.getElementById('root')!
).render(
  <React.StrictMode>
    <ApolloProvider client={apolloClient}>
      <App />
    </ApolloProvider>
  </React.StrictMode>
);

サンプルクエリの実装

実際に GraphQL クエリを実行するコンポーネントを作成してみましょう。

1. GraphQL クエリの定義

typescript// src/graphql/queries.ts
import { gql } from '@apollo/client';

export const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`;

2. データフェッチコンポーネントの実装

typescript// src/components/UserList.tsx
import { useQuery } from '@apollo/client';
import { GET_USERS } from '../graphql/queries';

interface User {
  id: string;
  name: string;
  email: string;
}

interface GetUsersData {
  users: User[];
}

export const UserList: React.FC = () => {
  const { loading, error, data } =
    useQuery<GetUsersData>(GET_USERS);

  if (loading) return <p>読み込み中...</p>;
  if (error) return <p>エラー: {error.message}</p>;

  return (
    <div>
      <h2>ユーザー一覧</h2>
      <ul>
        {data?.users.map((user) => (
          <li key={user.id}>
            {user.name} ({user.email})
          </li>
        ))}
      </ul>
    </div>
  );
};

3. メインアプリケーションでの利用

typescript// src/App.tsx
import { UserList } from './components/UserList';

function App() {
  return (
    <div className='App'>
      <header className='App-header'>
        <h1>Vite + Apollo Client</h1>
        <UserList />
      </header>
    </div>
  );
}

export default App;

これで Vite での基本的な Apollo Client 導入が完了です。開発サーバーの起動も高速で、快適な開発環境が整います。

Next.js での Apollo 導入

Next.js は React ベースのフルスタックフレームワークで、SSR(Server-Side Rendering)や SSG(Static Site Generation)をサポートしています。Apollo Client を Next.js で使用する際は、サーバーサイドでのデータフェッチとクライアントサイドでのハイドレーションを考慮する必要があります。

App Router / Pages Router 両対応

Next.js 13 以降で導入された App Router と従来の Pages Router の両方で Apollo Client を使用する方法を説明します。

1. Next.js プロジェクトの作成

bash# Next.js + TypeScript プロジェクトの作成
yarn create next-app my-next-apollo --typescript --tailwind --eslint --app
cd my-next-apollo

2. Apollo Client のインストール

bash# Apollo Client パッケージのインストール
yarn add @apollo/client graphql
yarn add @apollo/experimental-nextjs-app-support

3. Apollo Client 設定(両 Router 対応)

typescript// lib/apollo-client.ts
import { HttpLink } from '@apollo/client';
import {
  NextSSRInMemoryCache,
  NextSSRApolloClient,
} from '@apollo/experimental-nextjs-app-support/ssr';
import { registerApolloClient } from '@apollo/experimental-nextjs-app-support/rsc';

const httpLink = new HttpLink({
  uri:
    process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT ||
    'http://localhost:4000/graphql',
});

// App Router 用のクライアント(RSC対応)
export const { getClient } = registerApolloClient(() => {
  return new NextSSRApolloClient({
    cache: new NextSSRInMemoryCache(),
    link: httpLink,
  });
});

// Pages Router 用のクライアント
export const apolloClient = new NextSSRApolloClient({
  ssrMode: typeof window === 'undefined',
  link: httpLink,
  cache: new NextSSRInMemoryCache(),
});

SSR/SSG での Apollo 設定

App Router での Provider 設定

typescript// app/providers.tsx
'use client';

import { ApolloWrapper } from '@apollo/experimental-nextjs-app-support/ssr';
import { apolloClient } from '@/lib/apollo-client';

export function Providers({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <ApolloWrapper makeClient={() => apolloClient}>
      {children}
    </ApolloWrapper>
  );
}

ルートレイアウトでの Provider 適用

typescript// app/layout.tsx
import { Providers } from './providers';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang='ja'>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

App Router でのサーバーコンポーネント対応

typescript// app/users/page.tsx
import { getClient } from '@/lib/apollo-client';
import { GET_USERS } from '@/graphql/queries';
import { UserList } from '@/components/UserList';

export default async function UsersPage() {
  // サーバーサイドでデータフェッチ
  const { data } = await getClient().query({
    query: GET_USERS,
    context: {
      fetchOptions: {
        next: { revalidate: 5 }, // 5秒でキャッシュ更新
      },
    },
  });

  return (
    <div>
      <h1>ユーザー一覧(SSR)</h1>
      <UserList initialData={data} />
    </div>
  );
}

Pages Router での設定

Pages Router を使用している場合は、従来の _app.tsx でプロバイダーを設定します。

typescript// pages/_app.tsx
import type { AppProps } from 'next/app';
import { ApolloProvider } from '@apollo/client';
import { apolloClient } from '@/lib/apollo-client';

export default function App({
  Component,
  pageProps,
}: AppProps) {
  return (
    <ApolloProvider client={apolloClient}>
      <Component {...pageProps} />
    </ApolloProvider>
  );
}

getServerSideProps での使用例

typescript// pages/users.tsx
import { GetServerSideProps } from 'next';
import { apolloClient } from '@/lib/apollo-client';
import { GET_USERS } from '@/graphql/queries';

interface User {
  id: string;
  name: string;
  email: string;
}

interface UsersPageProps {
  users: User[];
}

export default function UsersPage({
  users,
}: UsersPageProps) {
  return (
    <div>
      <h1>ユーザー一覧(SSR)</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.name} ({user.email})
          </li>
        ))}
      </ul>
    </div>
  );
}

export const getServerSideProps: GetServerSideProps =
  async () => {
    const { data } = await apolloClient.query({
      query: GET_USERS,
    });

    return {
      props: {
        users: data.users,
      },
    };
  };

Next.js での Apollo 導入では、SSR/SSG による SEO 効果とパフォーマンス向上が期待できます。

Remix での Apollo 導入

Remix は Web 標準に準拠したフルスタックフレームワークで、プログレッシブエンハンスメントを重視した設計が特徴です。サーバーサイドの loader 機能と Apollo Client を連携させることで、効率的なデータフェッチが実現できます。

Remix 特有の loader との連携

1. Remix プロジェクトの作成

bash# Remix プロジェクトの作成
npx create-remix@latest my-remix-apollo
cd my-remix-apollo

2. Apollo Client のインストール

bash# Apollo Client パッケージのインストール
yarn add @apollo/client graphql isomorphic-fetch

3. サーバーサイド用 Apollo Client 設定

typescript// app/lib/apollo-server.ts
import {
  ApolloClient,
  InMemoryCache,
  HttpLink,
} from '@apollo/client';
import fetch from 'isomorphic-fetch';

// サーバーサイド専用のクライアント
export const createApolloClient = () => {
  return new ApolloClient({
    ssrMode: true,
    link: new HttpLink({
      uri:
        process.env.GRAPHQL_ENDPOINT ||
        'http://localhost:4000/graphql',
      fetch, // Node.js 環境での fetch 対応
    }),
    cache: new InMemoryCache(),
  });
};

4. クライアントサイド用 Apollo Client 設定

typescript// app/lib/apollo-client.ts
import {
  ApolloClient,
  InMemoryCache,
  HttpLink,
} from '@apollo/client';

let apolloClient: ApolloClient<any>;

function createApolloClient() {
  return new ApolloClient({
    ssrMode: typeof window === 'undefined',
    link: new HttpLink({
      uri:
        typeof window === 'undefined'
          ? process.env.GRAPHQL_ENDPOINT
          : window.ENV.GRAPHQL_ENDPOINT || '/graphql',
    }),
    cache: new InMemoryCache(),
  });
}

export function getApolloClient() {
  // サーバーサイドでは毎回新しいクライアントを作成
  if (typeof window === 'undefined') {
    return createApolloClient();
  }

  // クライアントサイドではシングルトンを使用
  if (!apolloClient) {
    apolloClient = createApolloClient();
  }

  return apolloClient;
}

データフェッチパターン

Remix では loader でサーバーサイドデータフェッチを行い、クライアントサイドで Apollo Client を使用します。

loader を使用したサーバーサイドフェッチ

typescript// app/routes/users.tsx
import { json, LoaderFunctionArgs } from '@remix-run/node';
import {
  useLoaderData,
  useRevalidator,
} from '@remix-run/react';
import { ApolloProvider } from '@apollo/client';
import { createApolloClient } from '@/lib/apollo-server';
import { getApolloClient } from '@/lib/apollo-client';
import { GET_USERS } from '@/graphql/queries';

// サーバーサイドでのデータフェッチ
export async function loader({
  request,
}: LoaderFunctionArgs) {
  const client = createApolloClient();

  try {
    const { data } = await client.query({
      query: GET_USERS,
    });

    return json({
      users: data.users,
      error: null,
    });
  } catch (error) {
    return json({
      users: [],
      error:
        error instanceof Error
          ? error.message
          : 'Unknown error',
    });
  }
}

interface User {
  id: string;
  name: string;
  email: string;
}

export default function UsersRoute() {
  const { users, error } = useLoaderData<typeof loader>();
  const revalidator = useRevalidator();
  const apolloClient = getApolloClient();

  if (error) {
    return <div>エラー: {error}</div>;
  }

  return (
    <ApolloProvider client={apolloClient}>
      <div>
        <h1>ユーザー一覧</h1>
        <button
          onClick={() => revalidator.revalidate()}
          disabled={revalidator.state === 'loading'}
        >
          {revalidator.state === 'loading'
            ? '更新中...'
            : '再読み込み'}
        </button>

        <ul>
          {users.map((user: User) => (
            <li key={user.id}>
              {user.name} ({user.email})
            </li>
          ))}
        </ul>
      </div>
    </ApolloProvider>
  );
}

クライアントサイドでの Mutation 処理

typescript// app/components/AddUser.tsx
import { useState } from 'react';
import { useMutation } from '@apollo/client';
import { useRevalidator } from '@remix-run/react';
import { ADD_USER } from '@/graphql/mutations';

export const ADD_USER = gql`
  mutation AddUser($name: String!, $email: String!) {
    addUser(name: $name, email: $email) {
      id
      name
      email
    }
  }
`;

export function AddUser() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [addUser, { loading, error }] =
    useMutation(ADD_USER);
  const revalidator = useRevalidator();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    try {
      await addUser({
        variables: { name, email },
      });

      // Remix の revalidator で loader を再実行
      revalidator.revalidate();

      // フォームをリセット
      setName('');
      setEmail('');
    } catch (err) {
      console.error('ユーザー追加エラー:', err);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>
          名前:
          <input
            type='text'
            value={name}
            onChange={(e) => setName(e.target.value)}
            required
          />
        </label>
      </div>
      <div>
        <label>
          メール:
          <input
            type='email'
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            required
          />
        </label>
      </div>
      <button type='submit' disabled={loading}>
        {loading ? '追加中...' : 'ユーザーを追加'}
      </button>
      {error && <p>エラー: {error.message}</p>}
    </form>
  );
}

環境変数の設定

typescript// app/root.tsx
import { json } from '@remix-run/node';
import {
  Links,
  LiveReload,
  Meta,
  Scripts,
} from '@remix-run/react';

export async function loader() {
  return json({
    ENV: {
      GRAPHQL_ENDPOINT: process.env.GRAPHQL_ENDPOINT,
    },
  });
}

export default function App() {
  const data = useLoaderData<typeof loader>();

  return (
    <html lang='ja'>
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        <script
          dangerouslySetInnerHTML={{
            __html: `window.ENV = ${JSON.stringify(
              data.ENV
            )}`,
          }}
        />
        <Scripts />
        <LiveReload />
      </body>
    </html>
  );
}

Remix での Apollo 導入では、loader によるサーバーサイドフェッチと Apollo Client のキャッシュ機能を組み合わせることで、高性能なアプリケーションが構築できます。

まとめ

各フレームワークでの導入ポイント比較

3 つのフレームワークでの Apollo Client 導入について、特徴と推奨される使用場面を整理してみましょう。

mermaidflowchart TD
    comparison[フレームワーク比較] --> vite[Vite]
    comparison --> nextjs[Next.js]
    comparison --> remix[Remix]

    vite --> vite_features["特徴<br/>・軽量で高速<br/>・シンプルな設定<br/>・SPA特化"]
    nextjs --> next_features["特徴<br/>・SSR/SSG対応<br/>・SEO最適化<br/>・多機能"]
    remix --> remix_features["特徴<br/>・Web標準準拠<br/>・プログレッシブ<br/>・サーバー連携"]

    vite_features --> vite_use["適用場面<br/>・管理画面<br/>・ダッシュボード<br/>・プロトタイプ"]
    next_features --> next_use["適用場面<br/>・企業サイト<br/>・ECサイト<br/>・ブログ"]
    remix_features --> remix_use["適用場面<br/>・Webアプリ<br/>・フォーム中心<br/>・パフォーマンス重視"]

    style vite_features fill:#e1f5fe
    style next_features fill:#f3e5f5
    style remix_features fill:#e8f5e8

導入難易度と機能比較表

項目ViteNext.jsRemix
導入難易度⭐⭐⭐⭐⭐⭐⭐⭐⭐
設定の複雑さシンプル中程度複雑
SSR 対応⭐⭐⭐⭐⭐⭐
開発速度⭐⭐⭐⭐⭐⭐⭐
学習コスト
適用規模小〜中中〜大中〜大

推奨される選択基準

  • Vite:素早くプロトタイプを作成したい、SPA で十分な場合
  • Next.js:SEO が重要、企業サイトやブログなどの静的コンテンツが多い場合
  • Remix:フォーム処理が多い、Web 標準に準拠したい、パフォーマンスを重視する場合

運用時の注意点

Apollo Client を実際のプロジェクトで運用する際に注意すべきポイントをまとめます。

パフォーマンス関連

  1. キャッシュ設定の最適化

    • 不要なキャッシュの蓄積を避ける
    • 適切な fetchPolicy の選択
    • メモリ使用量の監視
  2. バンドルサイズの管理

    • 使用しない機能のツリーシェイキング
    • コード分割の活用
    • Apollo Client の最適化設定

開発・保守性

  1. 型安全性の確保

    • GraphQL Code Generator の導入
    • TypeScript との連携強化
    • エラーハンドリングの統一
  2. エラー監視とデバッグ

    • Apollo Client DevTools の活用
    • ログ記録の仕組み構築
    • エラー境界(Error Boundary)の設置

セキュリティ対策

  1. 認証・認可の実装

    • JWT トークンの適切な管理
    • リクエストヘッダーの設定
    • CSRF 対策の実装
  2. データ保護

    • 機密データのキャッシュ制御
    • ログ出力時の秘匿化
    • 適切な CORS 設定

これらのポイントを意識して運用することで、Apollo Client の持つ強力な機能を最大限に活用できるはずです。

どのフレームワークを選択する場合でも、まずは今回ご紹介した最小構成から始めて、プロジェクトの要件に応じて段階的に機能を追加していくことをお勧めします。

関連リンク