土曜日, 8月 9, 2025
土曜日, 8月 9, 2025
- Advertisment -
ホームニューステックニュースClaude Codeで2週間→3日に短縮!AI駆動開発でWebサイトを爆速リプレイス

Claude Codeで2週間→3日に短縮!AI駆動開発でWebサイトを爆速リプレイス


🎯 この記事で得られるもの

  • 開発時間を 90%削減する具体的な方法
  • 4 つの AI ツール使い分け戦略(Claude Code + Cursor + Codex + Copilot)
  • SSG + ヘッドレス CMSによる爆速サイト構築
  • 実コード付き囲碁棋譜プレイヤーの実装

はじめに

2 週間かかる開発を 3 日で完了」これは誇張ではありません。

私はもっぱら会社の業務で AI 駆動開発でインフラ〜フロントエンドまで対応しています。今回、息抜きに行きつけの浦添囲碁会館の Web サイトのリプレイスを Claude Code を中心に、Cursor、Codex、Copilot を組み合わせた AI ツール群で実践し、驚異的な成果を得ました。

※隔週の土曜囲碁大会に参加しています 😀

📊 定量的な成果(実測値)

指標 従来の開発 AI 駆動開発 改善率
開発期間 14〜21 日 3 日 85.7%短縮
コーディング時間 72 時間 8 時間 88.9%削減
Lighthouse Score 50 98 96%向上
月額ホスティング費用 3,000 円〜 実質 100 円未満 97%削減

プロジェクト概要

沖縄県浦添市にある囲碁教室・囲碁会館の Web サイトです。地域の囲碁愛好家のためのコミュニティハブとして、教室情報、大会情報、棋譜記録などを提供しています。

🔄 ビフォーアフター

画像で見る劇的な変化
Before(旧サイト) After(新サイト)
過去のHP 現在のHP
項目 Before 🔴 After 🟢 改善率
レスポンシブ対応 ❌ 非対応 ✅ 完全対応
表示速度 🐌 3.5 秒 ⚡ 0.8 秒 77%高速化
Lighthouse Score 📊 50 📊 98 96%向上
モバイル対応 ❌ 未対応 ✅ 最適化
SEO 対策 🔻 基本のみ 🔺 完全最適化
月額費用 💰 3,000 円〜 💸 100 円未満 97%削減

facility

技術スタック

{
  "フレームワーク": "Next.js 14.2 (App Router)",
  "言語": "TypeScript 5.3",
  "スタイリング": "Tailwind CSS 3.4",
  "CMS": "ヘッドレスCMS (Firestore)",
  "データベース": "Prisma 5.7 + PostgreSQL 16",
  "認証": "Firebase Auth v10",
  "ホスティング": "Firebase Hosting + CDN (月額100円未満)",
  "ビルド": "SSG (Static Site Generation)",
  "AI開発支援": "Claude Code + Cursor + Codex + Copilot",
  "パフォーマンス": "Core Web Vitals最適化"
}

開発プロセス

1. 既存サイトの分析

このプロジェクトは、既存の Web サイトを最新技術でリプレイスする案件でした。最初のステップは、現行サイトの完全な理解から始まりました。

スクレイピングによるサイトマップ作成

私:「既存サイトの構造を把握したいので、スクレイピングしてサイトマップを作成してください」

Claude Code は、Puppeteer を使用して既存サイトをクロールし、SITEMAP.md を自動生成してくれました:


const puppeteer = require("puppeteer");
const fs = require("fs").promises;

async function crawlSite(baseUrl) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  const visited = new Set();
  const siteMap = [];

  async function crawlPage(url, depth = 0) {
    if (visited.has(url) || depth > 3) return;
    visited.add(url);

    try {
      await page.goto(url, { waitUntil: "networkidle2" });

      
      const pageInfo = await page.evaluate(() => ({
        title: document.title,
        description: document.querySelector('meta[name="description"]')
          ?.content,
        h1: document.querySelector("h1")?.textContent,
        sections: Array.from(document.querySelectorAll("h2")).map(
          (h) => h.textContent
        ),
      }));

      siteMap.push({ url, depth, ...pageInfo });

      
      const links = await page.evaluate(() =>
        Array.from(document.querySelectorAll("a[href]"))
          .map((a) => a.href)
          .filter((href) => href.startsWith(window.location.origin))
      );

      for (const link of links) {
        await crawlPage(link, depth + 1);
      }
    } catch (error) {
      console.error(`Error crawling ${url}:`, error.message);
    }
  }

  await crawlPage(baseUrl);
  await browser.close();
  return siteMap;
}


async function generateSiteMapDocument(siteMap) {
  let markdown = "# サイトマップ\n\n";

  
  const pagesByDepth = {};
  siteMap.forEach((page) => {
    if (!pagesByDepth[page.depth]) pagesByDepth[page.depth] = [];
    pagesByDepth[page.depth].push(page);
  });

  
  Object.keys(pagesByDepth).forEach((depth) => {
    const indent = "  ".repeat(parseInt(depth));
    pagesByDepth[depth].forEach((page) => {
      markdown += `${indent}- [${page.title}](${page.url})\n`;
      if (page.sections?.length > 0) {
        page.sections.forEach((section) => {
          markdown += `${indent}  - ${section}\n`;
        });
      }
    });
  });

  await fs.writeFile("SITEMAP.md", markdown);
}

ペアプログラミングでの要件定義

生成された SITEMAP.md を基に、Claude Code とペアプログラミング形式で要件定義書を作成しました:

# 要件定義書 - 浦添囲碁会館 Web サイトリニューアル

## 1. プロジェクト概要

- **目的**: 既存サイトのモダナイゼーション
- **対象**: https://igo.okinawa/ のリプレイス
- **期間**: 3 日間での実装

## 2. 現行サイト分析(SITEMAP.md より)

### ページ構成

- トップページ
  - お知らせセクション
  - 教室案内
  - アクセス情報
- 教室詳細ページ(/lessons)
  - 初心者コース
  - 中級者コース
  - 上級者コース
- 施設案内(/facility)
- 大会情報(/tournaments)
- ブログ(/blog)
- 棋譜(/game-records)
- FAQ(/faq)

## 3. 技術要件

### 必須要件

- SSG(静的サイト生成)による高速化
- レスポンシブデザイン
- SEO 最適化
- 既存コンテンツの完全移行

### 追加要件

- 棋譜プレイヤーの実装
- お問い合わせフォームの改善
- パフォーマンス向上(Lighthouse Score 90 以上)

この要件定義書を基に、Claude Code と対話しながら実装を進めました:

私:「既存サイトのデザインを踏襲しつつ、モダンな技術スタックでリプレイスしたい」

Claude Code:「Next.js 14 の App Router を使用し、SSG で高速化しながら、既存のデザインアセットを活かす構成を提案します」

2. プロジェクト初期設定

Claude Code が提案した構造は、実務でも通用する実践的なものでした:

urasoe_igo/
├── frontend/              # Next.jsアプリケーション
│   ├── src/
│   │   ├── app/          # App Router
│   │   ├── components/   # 再利用可能なコンポーネント
│   │   ├── lib/          # ユーティリティ関数
│   │   └── styles/       # グローバルスタイル
│   ├── public/           # 静的ファイル
│   └── prisma/           # データベーススキーマ
├── backend/               # APIサーバー(必要に応じて)
└── shared/                # 共通型定義

3. コンポーネント開発

私:「トップページにインパクトのあるヒーローセクションを作りたい」

Claude Code の回答と実装:


export const HeroSection: React.FC = () => {
  return (
    section className="relative h-screen flex items-center justify-center">
      div className="absolute inset-0 bg-gradient-to-r from-green-900 to-green-600 opacity-90" />
      div className="relative z-10 text-center text-white px-4">
        h1 className="text-5xl md:text-7xl font-bold mb-6">浦添囲碁会館h1>
        p className="text-xl md:text-2xl mb-8">
          伝統と革新が交わる、囲碁の学び舎
        p>
        button className="bg-white text-green-800 px-8 py-4 rounded-full text-lg font-semibold hover:bg-green-50 transition-colors">
          無料体験を申し込む
        button>
      div>
    section>
  );
};

hero

4. SSG × ヘッドレス CMS

このプロジェクトでは、Firebase Firestore をヘッドレス CMS として活用し、SSG でコンテンツを静的に生成する構成を採用しました:


import { initializeApp } from "firebase/app";
import { getFirestore, collection, getDocs } from "firebase/firestore";

const app = initializeApp(firebaseConfig);
const db = getFirestore(app);


export async function getCMSContent(collectionName: string) {
  const snapshot = await getDocs(collection(db, collectionName));
  return snapshot.docs.map((doc) => ({
    id: doc.id,
    ...doc.data(),
  }));
}

Next.js の SSG 機能を活用


import { Metadata } from "next";
import { getCMSContent } from "@/lib/cms";

export const metadata: Metadata = {
  title: "教室案内 | 浦添囲碁会館",
  description: "初心者から有段者まで、レベルに応じた囲碁教室をご用意しています",
};


async function getData() {
  const lessons = await getCMSContent("lessons");
  return lessons;
}


export default async function LessonsPage() {
  const lessons = await getData();

  return (
    div className="container mx-auto px-4 py-16">
      h1 className="text-4xl font-bold mb-8">教室案内h1>
      div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
        {lessons.map((lesson) => (
          LessonCard key={lesson.id} lesson={lesson} />
        ))}
      div>
    div>
  );
}

この構成により、以下のメリットを実現:

  • 高速な配信: 静的ファイルのため CDN から直接配信
  • コンテンツの柔軟な管理: CMS から管理画面でコンテンツ更新可能
  • ビルド時の最適化: 必要なデータのみを取得して静的化
  • SEO 最適化: 完全にレンダリングされた HTML を配信
  • 超低コスト運用: Firebase Hosting の無料枠で実質月額 100 円未満

5. パフォーマンス最適化

Claude Code は画像最適化の重要性も指摘してくれました:

import Image from "next/image";

export const OptimizedImage: React.FC{ src: string; alt: string }> = ({
  src,
  alt,
}) => {
  return (
    Image
      src={src}
      alt={alt}
      width={800}
      height={600}
      loading="lazy"
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
      className="rounded-lg shadow-lg"
    />
  );
};

AI 開発ツールの使い分け

1. Claude Code(メイン開発 – 70%)

役割: 大規模な機能実装とアーキテクチャ設計
強み: 200K tokensの長大なコンテキスト理解
例: 囲碁棋譜プレイヤー全体の実装

2. Cursor(軽微な修正 – 15%)

役割: インライン編集と即座の修正
強み: VSCode統合による快適な編集体験
例: タイポ修正、小さなバグフィックス

3. Codex(レビュー対応 – 10%)

役割: レビューコメントに基づく修正実装
強み: レビュー内容を理解した的確な修正
例: レビュアーの指摘事項を自動修正、改善案の実装

4. GitHub Copilot(レビュー支援 – 5%)

役割: コードレビューとPRレビュアー
強み: GitHubとの深い統合
例: PR説明文自動生成、レビューコメント作成、改善提案

効果的な活用法

1. 明確な指示を与える

  • 曖昧:「かっこいいデザインにして」
  • 具体的:「緑色グラデーション背景、白文字タイトル、レスポンシブ対応」

2. 段階的な実装

  • 基本構造 → スタイリング → インタラクション → 最適化

3. コードレビューの活用

  • パフォーマンス改善やアクセシビリティの観点でレビュー依頼

遭遇した課題と解決策

課題 1: 状態管理の複雑化

当初、props のバケツリレーが発生していました。

解決策: Context API の導入


const AppContext = createContextAppContextType | undefined>(undefined);

export const AppProvider: React.FC{ children: ReactNode }> = ({
  children,
}) => {
  const [state, setState] = useState(initialState);

  return (
    AppContext.Provider value={{ state, setState }}>
      {children}
    AppContext.Provider>
  );
};

課題 2: SEO 対策

静的サイトでも SEO 対策は重要です。

解決策: メタデータの最適化と structured data

export const metadata: Metadata = {
  title: "浦添囲碁会館 | 沖縄の囲碁教室",
  description: "初心者から有段者まで...",
  openGraph: {
    title: "浦添囲碁会館",
    description: "...",
    images: ["/og-image.jpg"],
  },
};


const jsonLd = {
  "@context": "https://schema.org",
  "@type": "EducationalOrganization",
  name: "浦添囲碁会館",
  
};

🎮 囲碁棋譜プレイヤーの実装

プロジェクトで最も技術的に挑戦的だったのは、Web 上で動作する囲碁棋譜プレイヤーの実装でした。

📋 実装例を見る: https://igo.okinawa/records/394/

技術的な要件

  1. SGF 形式の棋譜データの解析
  2. 19×19 の碁盤のレンダリング
  3. 手順の再生・巻き戻し機能
  4. コメント・変化図の表示
  5. レスポンシブ対応

実装アプローチ

1. SGF パーサーの実装

SGF(Smart Game Format)は囲碁の棋譜を記録する標準形式です。Claude Code と一緒に、このフォーマットを解析するパーサーを実装しました:


interface SGFNode {
  move?: { color: "B" | "W"; position: [number, number] };
  comment?: string;
  variations?: SGFNode[][];
}

export class SGFParser {
  private input: string;
  private position: number = 0;

  constructor(sgfString: string) {
    this.input = sgfString;
  }

  parse(): SGFNode[] {
    return this.parseSequence();
  }

  private parseSequence(): SGFNode[] {
    const nodes: SGFNode[] = [];

    while (this.position  this.input.length) {
      if (this.input[this.position] === ";") {
        this.position++;
        nodes.push(this.parseNode());
      } else if (this.input[this.position] === "(") {
        
        this.position++;
        const variation = this.parseSequence();
        if (nodes.length > 0) {
          nodes[nodes.length - 1].variations =
            nodes[nodes.length - 1].variations || [];
          nodes[nodes.length - 1].variations.push(variation);
        }
      } else if (this.input[this.position] === ")") {
        this.position++;
        break;
      } else {
        this.position++;
      }
    }

    return nodes;
  }

  private parseNode(): SGFNode {
    const node: SGFNode = {};

    while (
      this.position  this.input.length &&
      this.input[this.position] !== ";" &&
      this.input[this.position] !== "(" &&
      this.input[this.position] !== ")"
    ) {
      const prop = this.parseProperty();
      if (prop) {
        
        this.processProperty(node, prop);
      }
    }

    return node;
  }
}

2. Canvas API を使用した碁盤のレンダリング

SVG ではなく Canvas API を選択した理由:

  • 大量の石を描画する際のパフォーマンス
  • アニメーション効果の実装が容易
  • ピクセル単位での精密な制御

import { useEffect, useRef, useState } from "react";

interface GoBoardProps {
  gameState: GameState;
  onCellClick?: (x: number, y: number) => void;
}

export const GoBoard: React.FCGoBoardProps> = ({ gameState, onCellClick }) => {
  const canvasRef = useRefHTMLCanvasElement>(null);
  const [dimensions, setDimensions] = useState({ width: 600, height: 600 });

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    
    const updateDimensions = () => {
      const container = canvas.parentElement;
      if (container) {
        const size = Math.min(container.clientWidth, container.clientHeight);
        setDimensions({ width: size, height: size });
        canvas.width = size;
        canvas.height = size;
      }
    };

    updateDimensions();
    window.addEventListener("resize", updateDimensions);

    
    drawBoard(ctx, dimensions);

    
    drawStones(ctx, gameState, dimensions);

    return () => window.removeEventListener("resize", updateDimensions);
  }, [gameState, dimensions]);

  const drawBoard = (ctx: CanvasRenderingContext2D, dimensions: Dimensions) => {
    const { width, height } = dimensions;
    const cellSize = width / 20; 

    
    ctx.fillStyle = "#DCB35C"; 
    ctx.fillRect(0, 0, width, height);

    
    ctx.strokeStyle = "#000000";
    ctx.lineWidth = 1;

    for (let i = 1; i  19; i++) {
      
      ctx.beginPath();
      ctx.moveTo(cellSize * i, cellSize);
      ctx.lineTo(cellSize * i, cellSize * 19);
      ctx.stroke();

      
      ctx.beginPath();
      ctx.moveTo(cellSize, cellSize * i);
      ctx.lineTo(cellSize * 19, cellSize * i);
      ctx.stroke();
    }

    
    const starPoints = [4, 10, 16];
    ctx.fillStyle = "#000000";
    starPoints.forEach((x) => {
      starPoints.forEach((y) => {
        ctx.beginPath();
        ctx.arc(cellSize * x, cellSize * y, 3, 0, Math.PI * 2);
        ctx.fill();
      });
    });
  };

  const drawStones = (
    ctx: CanvasRenderingContext2D,
    gameState: GameState,
    dimensions: Dimensions
  ) => {
    const cellSize = dimensions.width / 20;

    gameState.board.forEach((row, y) => {
      row.forEach((cell, x) => {
        if (cell !== null) {
          const centerX = cellSize * (x + 1);
          const centerY = cellSize * (y + 1);
          const radius = cellSize * 0.45;

          
          ctx.shadowColor = "rgba(0, 0, 0, 0.3)";
          ctx.shadowBlur = 3;
          ctx.shadowOffsetX = 2;
          ctx.shadowOffsetY = 2;

          
          ctx.beginPath();
          ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);

          if (cell === "black") {
            const gradient = ctx.createRadialGradient(
              centerX - radius / 3,
              centerY - radius / 3,
              0,
              centerX,
              centerY,
              radius
            );
            gradient.addColorStop(0, "#404040");
            gradient.addColorStop(1, "#000000");
            ctx.fillStyle = gradient;
          } else {
            const gradient = ctx.createRadialGradient(
              centerX - radius / 3,
              centerY - radius / 3,
              0,
              centerX,
              centerY,
              radius
            );
            gradient.addColorStop(0, "#FFFFFF");
            gradient.addColorStop(1, "#E0E0E0");
            ctx.fillStyle = gradient;
          }

          ctx.fill();
          ctx.shadowColor = "transparent";
        }
      });
    });
  };

  return (
    div className="relative w-full aspect-square">
      canvas
        ref={canvasRef}
        className="w-full h-full cursor-pointer"
        onClick={handleClick}
      />
    div>
  );
};

3. 再生コントロールの実装

play_go


export const KifuPlayer: React.FC{ sgfData: string }> = ({ sgfData }) => {
  const [currentMove, setCurrentMove] = useState(0);
  const [gameStates, setGameStates] = useStateGameState[]>([]);
  const [isAutoPlaying, setIsAutoPlaying] = useState(false);

  useEffect(() => {
    const parser = new SGFParser(sgfData);
    const moves = parser.parse();
    const states = generateGameStates(moves);
    setGameStates(states);
  }, [sgfData]);

  
  useEffect(() => {
    if (!isAutoPlaying) return;

    const interval = setInterval(() => {
      setCurrentMove((prev) => {
        if (prev >= gameStates.length - 1) {
          setIsAutoPlaying(false);
          return prev;
        }
        return prev + 1;
      });
    }, 1000); 

    return () => clearInterval(interval);
  }, [isAutoPlaying, gameStates.length]);

  const controls = {
    first: () => setCurrentMove(0),
    prev: () => setCurrentMove(Math.max(0, currentMove - 1)),
    next: () =>
      setCurrentMove(Math.min(gameStates.length - 1, currentMove + 1)),
    last: () => setCurrentMove(gameStates.length - 1),
    toggleAutoPlay: () => setIsAutoPlaying(!isAutoPlaying),
  };

  return (
    div className="space-y-4">
      GoBoard gameState={gameStates[currentMove]} />

      div className="flex items-center justify-center gap-2">
        button onClick={controls.first} className="p-2">button>
        button onClick={controls.prev} className="p-2">button>
        button onClick={controls.toggleAutoPlay} className="p-2">
          {isAutoPlaying ? "⏸" : "▶️"}
        button>
        button onClick={controls.next} className="p-2">button>
        button onClick={controls.last} className="p-2">button>
      div>

      div className="text-center">
        手数: {currentMove} / {gameStates.length - 1}
      div>
    div>
  );
};

Claude Code との協働で得た知見

  1. 複雑なアルゴリズムの段階的実装

    • まず基本的な碁盤の描画から始める
    • 次に石の配置機能を追加
    • 最後に棋譜の解析と再生機能を実装
  2. デバッグの効率化

    • Claude Code に「この SGF データが正しく解析できない」と具体例を示すと、的確な修正案を提示
  3. パフォーマンス最適化

    • 「大量の石を描画すると重い」という問題に対し、Canvas API の最適化手法を提案
  4. エッジケースの処理

    • コウ、セキ、変化図など、囲碁特有のルールへの対応

実装の成果

  • 軽量: 外部ライブラリに依存せず、純粋な TypeScript/React で実装
  • 高速: Canvas API による効率的なレンダリング
  • 柔軟: SGF 形式の様々なバリエーションに対応
  • レスポンシブ: モバイルでも快適に閲覧可能

この棋譜プレイヤーの実装は、Claude Code の真価を発揮した例でした。囲碁のルールを理解し、適切なデータ構造を提案し、パフォーマンスを考慮したコードを生成してくれました。

🎮 動作デモ: 実際の棋譜プレイヤーはこちらでご覧いただけます。再生ボタンで自動再生、矢印ボタンで手動操作が可能です。

成果とベストプラクティス

📊 開発成果

  • 開発期間: 2-3 週間 → 3 日間
  • コード品質: TypeScript 型定義の一貫性、高い再利用性
  • パフォーマンス: Lighthouse Score 98 達成

lighthouse

💡 3 つのベストプラクティス

  1. ペアプログラミング: Claude Code を「もう一人の開発者」として活用
  2. 学習ツール: 生成コードから新しいパターンを学習
  3. レビュアー: 見落としを防ぐためのコードレビュー

実践 Tips


const workflow = {
  "設計・実装": "Claude Code (70%)",
  修正: "Cursor (15%)",
  レビュー対応: "Codex (10%)",
  レビュー: "Copilot (5%)",
};

効果的なプロンプト例

  • Claude Code: 「囲碁棋譜プレイヤーを SGF 形式対応で実装」
  • Cursor: 「タイポを修正」
  • Codex: 「レビューコメントを反映」
  • Copilot: 「潜在的な問題点を指摘」

今すぐ始める 3 ステップ

1. 環境構築(5 分)


npm install -g claude-code-cli


claude-code init my-project

2. 最初のプロンプト

"Next.js 14でSSG対応のWebサイトを作成。
Tailwind CSS使用、TypeScript、
レスポンシブ対応、SEO最適化済み"

3. 3 時間後には公開可能

まとめ

2 週間 →3 日の短縮は本当に可能です。

Claude Code により、以下が実現:

  • コーディング時間 88.9%削減
  • Lighthouse Score 98 達成(50→98)
  • 開発期間 85.7%短縮
  • 月額費用 97%削減(Firebase Hosting 活用で 100 円未満)

今すぐアクション

  1. Claude Codeを今すぐ試す
  2. この記事のコードをコピペして実行
  3. あなたの成功事例をコメントで共有

AI 駆動開発は、もはや未来ではなく今ここにある現実です。

📚 参考リンク

著者について

🚀 AI 駆動開発を日々実践中のエンジニア

  • 💼 業務:インフラ〜フロントエンドまで AI 駆動で開発
  • 🏢 経験:GCP/AWS、オンプレインフラ構築、フルスタック開発 $
    開発歴: {new Date().getFullYear() – 2005}年〜
  • 🎯 目標:AI 駆動開発のスペシャリストを目指して日々学習中
  • ♟️ 趣味:囲碁(浦添囲碁会館で土曜大会参加)

📧 お仕事のご相談

AI 駆動開発のご相談、開発案件のお問い合わせはお気軽にどうぞ!

以下のような案件を承っております:

  • 🌐 Web サイト・アプリケーション開発
  • 🔄 既存システムの AI 活用リファクタリング
  • ☁️ インフラ構築・最適化
  • 💡 技術顧問・コンサルティング

連絡先:

  • 📧 メール: [email protected]
  • 💬 Zenn: DM でお気軽に
  • 🐙 GitHub: Issue またはメッセージ

フォローしていただけると嬉しいです!最新の AI 開発テクニックを共有していきます

  • 📘 Zenn: この記事の著者をフォロー
  • 🐙 GitHub: @sakumoto-shota



Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -