Next.jsで高速eコマースアプリを構築する | エピソード10: デプロイ、セキュリティ強化、拡張の準備

こんにちは！前回のエピソードでは、`next-pwa`を使ってPWA機能を追加し、モバイルでのネイティブアプリのような体験を実現しました。シリーズ最終回となる今回は、アプリケーションをVercelにデプロイし、セキュリティを強化します。さらに、AIによる商品レコメンドやWeb3の統合など、将来の拡張に向けたアイデアも紹介します。これで、完全なeコマースアプリが完成します！

## このエピソードのゴール

- Vercelにアプリケーションをデプロイし、カスタムドメインを設定。
- 環境変数とCI/CDをGitHub Actionsで管理。
- セキュリティ対策（XSS、CSRF、Content Security Policy）を追加。
- AIやWeb3を活用した拡張のアイデアを提案。

## 必要なもの

- 前回のプロジェクト（`next-ecommerce`）がセットアップ済み。
- VercelアカウントとGitHubリポジトリ。
- カスタムドメイン（オプション）。
- 基本的なTypeScript、React、Next.jsの知識。

---

## ステップ1: Vercelへのデプロイ

Vercelは、Next.jsアプリのデプロイに最適なプラットフォームです。以下の手順でデプロイします：

1. **GitHubリポジトリの準備**：
   - プロジェクトをGitHubリポジトリにプッシュ。
   - 必要な環境変数（Shopify、Stripe、Algolia、Fredなど）を`.env.local`に記載。

2. **Vercelでのプロジェクト作成**：
   - [Vercelダッシュボード](https://vercel.com/)にログイン。
   - 「New Project」を選択し、GitHubリポジトリをインポート。
   - 環境変数をVercelの設定画面で追加：
     ```
     NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN=あなたのトークン
     NEXT_PUBLIC_SHOPIFY_STOREFRONT_ENDPOINT=あなたのエンドポイント
     STRIPE_SECRET_KEY=あなたのキー
     NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=あなたのキー
     NEXT_PUBLIC_ALGOLIA_APP_ID=あなたのID
     NEXT_PUBLIC_ALGOLIA_SEARCH_KEY=あなたのキー
     ALGOLIA_ADMIN_API_KEY=あなたのキー
     NEXT_PUBLIC_FIREBASE_API_KEY=あなたのキー
     （他のFirebase関連キー）
     ```

3. **カスタムドメインの設定**：
   - Vercelダッシュボードの「Domains」セクションでカスタムドメイン（例: `yourstore.com`）を追加。
   - DNS設定でVercelの指示に従い、CNAMEまたはAレコードを追加。

4. **デプロイの実行**：
   - 「Deploy」ボタンをクリックしてビルドを開始。
   - ビルド完了後、提供されたURL（例: `your-project.vercel.app`）またはカスタムドメインでアプリにアクセス。

---

## ステップ2: CI/CDの設定

GitHub Actionsを使って、コードのプッシュ時に自動デプロイを設定します。`.github/workflows/deploy.yml`ファイルを作成：

```yaml
name: Deploy to Vercel

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      - name: Install dependencies
        run: npm install
      - name: Build and deploy
        env:
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
          VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
          VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
        run: npx vercel --prod --token $VERCEL_TOKEN --project $VERCEL_PROJECT_ID --org $VERCEL_ORG_ID
```

1. **Vercelトークンの取得**：
   - Vercelダッシュボードの「Settings」→「Tokens」でトークンを作成。

2. **GitHub Secretsの設定**：
   - GitHubリポジトリの「Settings」→「Secrets and variables」→「Actions」で以下を追加：
     - `VERCEL_TOKEN`
     - `VERCEL_PROJECT_ID`（Vercelプロジェクト設定から取得）
     - `VERCEL_ORG_ID`（Vercelアカウント設定から取得）

これで、`main`ブランチへのプッシュごとにVercelが自動でビルドとデプロイを実行します。

---

## ステップ3: セキュリティ強化

eコマースアプリでは、セキュリティが重要です。以下の対策を実装します：

### 1. XSS（クロスサイトスクリプティング）対策
Shopifyの`descriptionHtml`など、ユーザー入力や外部データを表示する際は、HTMLをサニタイズします。`sanitize-html`パッケージをインストール：

```bash
npm install sanitize-html
```

`src/app/[locale]/products/[handle]/page.tsx`を更新：

```tsx
import sanitizeHtml from 'sanitize-html';

// ... 既存のコード ...

export default function ProductPage({ product }: { product: ProductDetail }) {
  if (!product) return notFound();

  const ogImage = product.images.edges[0]?.node.url || '';
  const cleanDescription = sanitizeHtml(product.descriptionHtml, {
    allowedTags: ['p', 'b', 'i', 'em', 'strong', 'a'],
    allowedAttributes: { a: ['href'] },
  });

  return (
    // ... 既存のJSX ...
    <div className="prose mb-6" dangerouslySetInnerHTML={{ __html: cleanDescription }} />
    // ... 残りのJSX ...
  );
}
```

### 2. CSRF（クロスサイトリクエストフォージェリ）対策
Stripe CheckoutやAPIリクエストでCSRFトークンを使用します。`src/app/api/checkout/route.ts`にトークンチェックを追加：

```ts
import { NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';

export async function POST(request: Request) {
  const csrfToken = request.headers.get('x-csrf-token');
  if (csrfToken !== process.env.CSRF_TOKEN) {
    return NextResponse.json({ error: '無効なCSRFトークン' }, { status: 403 });
  }

  try {
    const { items } = await request.json();
    const session = await stripe.checkout.sessions.create({
      // ... 既存のコード ...
    });
    return NextResponse.json({ url: session.url });
  } catch (error) {
    console.error('Stripeエラー:', error);
    return NextResponse.json({ error: '決済セッションの作成に失敗しました' }, { status: 500 });
  }
}
```

`.env.local`にCSRFトークンを追加：

```env
CSRF_TOKEN=あなたのランダムなトークン
```

チェックアウトページでCSRFトークンを送信するように更新します。

### 3. Content Security Policy（CSP）
CSPを追加して、許可されていないスクリプトの実行を防ぎます。`next.config.js`にヘッダーを追加：

```js
const nextConfig = {
  images: {
    domains: ['cdn.shopify.com'],
  },
  headers: async () => [
    {
      source: '/(.*)',
      headers: [
        {
          key: 'Content-Security-Policy',
          value:
            "default-src 'self'; script-src 'self' 'unsafe-eval' https://js.stripe.com https://www.gstatic.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://cdn.shopify.com; connect-src 'self' https://api.stripe.com https://api.algolia.net;",
        },
      ],
    },
  ],
};
```

このCSPは、Stripe、Firebase、Algolia、Shopifyからのリソースのみを許可します。

---

## ステップ4: 拡張のアイデア

アプリをさらに進化させるためのアイデアを紹介します：

### 1. AIによる商品レコメンド
- **ツール**: TensorFlow.jsまたはVercelのAI SDKを活用。
- **実装**: ユーザーの閲覧履歴や購入履歴を基に、類似商品を提案。
- **例**: 商品詳細ページに「関連商品」セクションを追加し、Algoliaの「related products」APIと組み合わせ。

### 2. Web3（暗号通貨決済）の統合
- **ツール**: MetaMaskやCoinbase Commerce。
- **実装**: EthereumやSolanaでの支払いオプションを追加。
- **例**: チェックアウトページに「暗号通貨で支払う」ボタンを追加し、ウォレット接続を処理。

### 3. ユーザー分析の強化
- **ツール**: MixpanelやGoogle Analytics。
- **実装**: ユーザーの行動（クリック、購入、カート放棄など）を追跡し、ダッシュボードで可視化。
- **例**: Vercel Analyticsと組み合わせて、コンバージョン率を最適化。

---

## ステップ5: 動作確認

1. GitHubにコードをプッシュし、Vercelでの自動デプロイを確認。
2. デプロイされたURL（例: `yourstore.com`）またはカスタムドメインにアクセスし、以下の点を確認：
   - すべてのページ（PLP、PDP、カート、チェックアウト）が正常に動作。
   - 多言語対応（`/en`, `/ja`）とPWA機能が正しく機能。
   - セキュリティヘッダーが適用されている（デベロッパーツールで確認）。
3. テスト決済（Stripeのテストカード）を実行し、エラーがないことを確認。
4. オフラインでアプリにアクセスし、PWAのキャッシュが機能することを確認。

エラーがあれば、Vercelのビルドログやコンソールを確認してください。

---

## まとめと次のステップ

このエピソードでは、Vercelにアプリをデプロイし、セキュリティを強化しました。CI/CDの設定やCSPの追加により、プロダクション環境での信頼性を確保しました。さらに、AIやWeb3を活用した拡張のアイデアを提案し、アプリの可能性を広げました。

これで、高速でスケーラブルなeコマースアプリが完成しました！今後は、ユーザーからのフィードバックを基に新機能を追加したり、マーケティング戦略を強化したりして、ビジネスを成長させましょう。シリーズを通じて学んだ知識を活かし、次のプロジェクトにも挑戦してください！

---

この記事が役に立ったと思ったら、ぜひ「いいね」を押して、ストックしていただければ嬉しいです！次回のエピソードもお楽しみに！