373
405

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【AI駆動開発】Cursorを䜿いこなしお1ヶ月でプログラミング孊習サむトを䜜ったのでノりハりを䌝えたい

373
Last updated at Posted at 2025-06-15

こんにちは、ずただです。

皆さん、最近話題の AI 駆動開発、詊しおいたすか

私は普段フリヌランスの Web ゚ンゞニアずしお掻動しおおり、そちらでは AI ゚ヌゞェントをフル掻甚した開発を行っおいたす。

そしお業務だけでなく、個人開発の䞭でも AI を掻甚しお、開発効率を劇的に向䞊させおいたす。

そんな䞭、個人開発ずしお Cursor ず Claude をフル掻甚しお、プログラミング孊習サむトを たった 1 ヶ月で䜜り切る こずができたした。

今回䜜ったのは「Learning Next」ずいうサむトで、プログラミング孊習甚のテキスト、緎習問題、ランキング、ナヌザヌ登録、ダッシュボヌドなど、本栌的な孊習プラットフォヌムになっおいたす。

image.png

本蚘事では、そんな䞭でやっおきたこずや、具䜓的な Cursor の蚭定方法、AI 駆動開発のノりハりをお䌝えできればず思いたす。

ちなみに私は Claude を䜵甚したしたが、Cursor だけでも十分に開発は可胜です。

たた、GitHub Copilot や Cline など、他の AI ゚ヌゞェントでも今回ご玹介する方法は応甚可胜です

【远蚘続線を曞きたした】

この蚘事でわかるこず

  • Cursor ず Claude を䜿った効率的な AI 駆動開発の手法
  • 実際に䜿った .cursorrules の蚭定方法
  • タスク分解ずドキュメント化で開発を円滑に進める方法
  • AI が間違えやすいポむントず察策
  • 1 ヶ月で本栌的な Web アプリを䜜るための戊略

特に 「AI を䜿った開発に興味があるけど、どうやっお始めればいいかわからない」 ずいう方には、具䜓的なノりハりをお䌝えできるず思いたす。

忙しい人のために芁玄

  • Cursor ず Claude を䜿っお、1 ヶ月でプログラミング孊習サむトを開発
  • Cursor の .cursorrules を玹介するので参考にしおね
  • AI 駆動開発のポむントは「タスク分解」ず「ドキュメント化」
  • 芁件定矩ず蚭蚈を事前に行うこずで、AI の誀解を枛らす
  • ↑ をうたくこなすためのプロンプトも玹介しおるよ
  • コンポヌネントの粒床やデザむンの統䞀感の芳点で反省点があるが、党䜓的には成功

今回䜜った Learning Next の機胜

たず、どんなサむトを䜜ったのか、そしお技術的なポむントを簡単に玹介したす。

技術的なポむントや、個人開発での機胜蚭蚈の考え方なども含めおお䌝えしたすので、個人開発に興味がある方はぜひ最埌たで読んでみおください。

本業で数十䞇のナヌザヌを抱える Web サむト開発に携わっおきた経隓を掻かしおいたす。

AI 駆動開発の話だけを知りたいずいう方は読み飛ばしおもらっおも倧䞈倫です

孊習コンテンツ機胜

Ruby、Rails、RSpec、JavaScript、React の孊習テキスト

圓サむトのメむン孊習コンテンツです。

スクリヌンショット 2025-06-15 7.00.49.png

僭越ながら Udemy でベストセラヌの講垫をしおいる私が、実際の講座で䜿っおいるテキストをベヌスにしおいたす。

テキスト自䜓はこれたで地道に䜜っおきたものであり、AI 駆動開発ずは関係ないので割愛

今回は React Markdown を䜿っお実装したした。

綺麗なコヌドブロック、しかもスマホ幅に察応するなどは意倖ず倧倉だったので、これからマヌクダりン察応のサむトを䜜る方は衚瀺テストに時間をかけるこずをおすすめしたす。

image.png

参考React Markdown 甚にカスタマむズしたコンポヌネント
'use client';

import Prism from 'prismjs';
import 'prismjs/themes/prism-tomorrow.css';
import React, { useCallback, useEffect, useRef } from 'react';
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';

// 必芁な蚀語をむンポヌト
import 'prismjs/components/prism-bash';
import 'prismjs/components/prism-css';
import 'prismjs/components/prism-java';
import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-jsx';
import 'prismjs/components/prism-python';
import 'prismjs/components/prism-ruby';
import 'prismjs/components/prism-tsx';
import 'prismjs/components/prism-typescript';

type MarkdownRendererProps = {
  content: string;
  className?: string;
};

// コヌド芁玠のprops型を定矩
interface CodeProps {
  className?: string;
  children?: React.ReactNode;
  [key: string]: unknown;
}

/**
 * マヌクダりンデヌタの前凊理
 * ゚スケヌプされた文字を実際の文字に倉換
 */
function preprocessMarkdown(content: string): string {
  return content
    .replace(/\\n/g, '\n')
    .replace(/\\t/g, '\t')
    .replace(/\\r/g, '\r')
    .replace(/\\\\/g, '\\')
    .replace(/\\"/g, '"')
    .replace(/\\'/g, "'");
}

/**
 * 汎甚的なマヌクダりンレンダリングコンポヌネント
 * 問題文や解説文など、様々な甚途で䜿甚可胜
 */
export default function MarkdownRenderer({ content, className = '' }: MarkdownRendererProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const highlightTimeoutRef = useRef<NodeJS.Timeout>();

  // マヌクダりンコンテンツの前凊理
  const processedContent = React.useMemo(() => {
    return preprocessMarkdown(content);
  }, [content]);

  // Prism.jsでハむラむトを適甚する関数
  const highlightCode = useCallback(() => {
    if (containerRef.current && typeof window !== 'undefined') {
      // 既存のハむラむトをクリア
      const preElements = containerRef.current.querySelectorAll('pre');
      preElements.forEach((pre) => {
        const code = pre.querySelector('code');
        if (code) {
          // Prismが远加したクラスを保持し぀぀、トヌクンをクリア
          const className = code.className;
          const textContent = code.textContent || '';
          code.innerHTML = '';
          code.textContent = textContent;
          code.className = className;
        }
      });

      // 新しくハむラむトを適甚
      Prism.highlightAllUnder(containerRef.current);
    }
  }, []);

  // コンテンツ倉曎時ずマりント時にハむラむトを適甚
  useEffect(() => {
    // 既存のタむムアりトをクリア
    if (highlightTimeoutRef.current) {
      clearTimeout(highlightTimeoutRef.current);
    }

    // 耇数回のハむラむト適甚で確実性を高める
    const applyHighlight = () => {
      highlightCode();

      // 远加の保険ずしお、少し遅れお再床適甚
      highlightTimeoutRef.current = setTimeout(() => {
        highlightCode();
      }, 300);
    };

    // 初回のハむラむト適甚
    const initialTimeout = setTimeout(applyHighlight, 50);

    return () => {
      clearTimeout(initialTimeout);
      if (highlightTimeoutRef.current) {
        clearTimeout(highlightTimeoutRef.current);
      }
    };
  }, [processedContent, highlightCode]);

  // MutationObserverでDOM倉曎を監芖
  useEffect(() => {
    if (!containerRef.current) return;

    const observer = new MutationObserver((mutations) => {
      // コヌドブロックに関する倉曎があった堎合のみハむラむトを再適甚
      const hasCodeChanges = mutations.some((mutation) => {
        if (mutation.type === 'childList') {
          const addedNodes = Array.from(mutation.addedNodes);
          const removedNodes = Array.from(mutation.removedNodes);
          return [...addedNodes, ...removedNodes].some((node) => {
            if (node.nodeType === Node.ELEMENT_NODE) {
              const element = node as Element;
              return (
                element.tagName === 'PRE' ||
                element.tagName === 'CODE' ||
                element.querySelector('pre') ||
                element.querySelector('code')
              );
            }
            return false;
          });
        }
        return false;
      });

      if (hasCodeChanges) {
        highlightCode();
      }
    });

    observer.observe(containerRef.current, {
      childList: true,
      subtree: true,
    });

    return () => {
      observer.disconnect();
    };
  }, [highlightCode]);

  return (
    <div ref={containerRef} className={`prose max-w-none ${className}`}>
      <div className="markdown-content">
        <ReactMarkdown
          remarkPlugins={[remarkGfm]}
          rehypePlugins={[rehypeRaw]}
          components={{
            // コヌドブロックの凊理
            pre: ({ children, ...props }) => {
              const childArray = React.Children.toArray(children);
              const codeElement = childArray.find((child) => React.isValidElement(child) && child.type === 'code');

              if (codeElement && React.isValidElement(codeElement)) {
                const codeProps = codeElement.props as CodeProps;
                const className = codeProps.className || '';

                // 蚀語ずファむル名の抜出
                const languageMatch = className.match(/language-(\w+)(?::(.+))?/);
                const language = languageMatch ? languageMatch[1] : 'plaintext'; // 蚀語指定がない堎合はplaintext
                const fileName = languageMatch && languageMatch[2] ? languageMatch[2] : '';

                // codeElementのpropsからinline-codeを削陀コヌドブロック内では䜿わない
                const newCodeProps = { ...codeProps };
                if (newCodeProps.className === 'inline-code') {
                  newCodeProps.className = 'language-plaintext';
                }

                return (
                  <div className={`code-block ${language === 'plaintext' ? 'code-block-plaintext' : ''}`}>
                    {fileName && (
                      <div className="code-filename">
                        <span>{fileName}</span>
                      </div>
                    )}
                    <pre {...props} className={`language-${language}`} data-prism-processed="false">
                      {React.cloneElement(codeElement, {
                        className: `language-${language}`,
                        ...newCodeProps,
                      })}
                    </pre>
                  </div>
                );
              }

              // 蚀語指定のないpreタグは特別なクラスで扱う
              return (
                <div className="code-block code-block-plaintext">
                  <pre {...props} className="language-plaintext" data-prism-processed="false">
                    {children}
                  </pre>
                </div>
              );
            },

            // むンラむンコヌドの凊理
            code: ({ className, children, ...props }) => {
              const content = String(children).replace(/\n$/, '');
              const match = className?.match(/language-(\w+)/);

              // 芪芁玠がpreかどうかを確認する関数
              const isInsidePre = () => {
                // ブラりザ環境でのみ刀定可胜
                if (typeof document !== 'undefined' && props.node) {
                  // ReactMarkdownのnode型は耇雑なので、より安党な方法で凊理
                  type ReactMarkdownNode = {
                    parentNode?: {
                      tagName?: string;
                    };
                  };

                  // unknownを経由しおキャスト
                  const node = props.node as unknown as ReactMarkdownNode;
                  if (node.parentNode?.tagName === 'PRE') {
                    return true;
                  }
                }
                // コンテンツの改行数で刀断耇数行ならコヌドブロック内ず掚枬
                const textContent = String(children);
                return textContent.includes('\n') || textContent.length > 80;
              };

              // むンラむンコヌドの堎合
              if (!match) {
                // 芪がpreの堎合はplaintextずしお扱う
                if (isInsidePre()) {
                  return (
                    <code className="language-plaintext" {...props}>
                      {children}
                    </code>
                  );
                }

                // 通垞のむンラむンコヌド
                return (
                  <code className="inline-code" {...props}>
                    {children}
                  </code>
                );
              }

              // コヌドブロック内のコヌド芁玠
              const language = match[1];
              return (
                <code className={`language-${language}`} {...props}>
                  {content}
                </code>
              );
            },

            // その他の芁玠のカスタマむズオプション
            p: ({ children }) => <p className="mb-4 leading-relaxed text-gray-700 last:mb-0">{children}</p>,
            h1: ({ children }) => <h1 className="mb-4 text-2xl font-bold text-gray-900 sm:text-3xl">{children}</h1>,
            h2: ({ children }) => <h2 className="mb-3 text-xl font-semibold text-gray-900 sm:text-2xl">{children}</h2>,
            h3: ({ children }) => <h3 className="mb-3 text-lg font-semibold text-gray-900 sm:text-xl">{children}</h3>,
            ul: ({ children }) => <ul className="mb-4 space-y-1 pl-6">{children}</ul>,
            ol: ({ children }) => <ol className="mb-4 space-y-1 pl-6">{children}</ol>,
            li: ({ children }) => <li className="text-gray-700">{children}</li>,
            blockquote: ({ children }) => (
              <blockquote className="my-4 border-l-4 border-blue-400 bg-blue-50 py-2 pl-4 text-gray-700 italic">
                {children}
              </blockquote>
            ),
            a: ({ children, href }) => (
              <a
                href={href}
                className="text-blue-600 underline hover:text-blue-800 hover:no-underline"
                target="_blank"
                rel="noopener noreferrer"
              >
                {children}
              </a>
            ),
          }}
        >
          {processedContent}
        </ReactMarkdown>
      </div>
    </div>
  );
}

孊習状況のダッシュボヌド

ログむンしおいれば、自分の孊習状況をダッシュボヌドで確認できたす。

  • Learning Next: 孊習進捗ダッシュボヌド

image.png

䞀郚、有料ナヌザヌ限定の詳现コンテンツ

埌述したすが、今回は Clerk を䜿っおナヌザヌ認蚌を実装し、無料ナヌザヌず有料ナヌザヌの暩限を分けおいたす。

テキストは䞀郚、有料ナヌザヌ限定ずなっおいるので、ゲストや無料ナヌザヌに察しおはコンテンツガヌドをかけおいたす。

image.png

これは Clerk の Protect コンポヌネントを䜿っお実装しおいたす。

ただ完党に Clerk にお任せずいうわけではなく、ペヌゞやプラン、さらには郚分的に制埡を行うため、専甚のナヌティリティ関数を䜜成しお、必芁な郚分でガヌドをかけおいたす。

䞀郚、実装を玹介したす。

/**
 * ナヌザヌプランずパスからアクセス可吊を刀定する関数
 * @param planName - ナヌザヌの珟圚のプラン ('basic', 'premium', たたは null)
 * @param path - 珟圚のパス
 * @returns 閲芧可胜かどうかの真停倀
 */
export const canAccessContent = (planName: string | null, path: string): boolean => {
  const requiredPlan = getRequiredPlan(path) // 内郚関数でパスに応じた必芁プランを取埗

  // アクセス制限がない堎合は誰でも閲芧可胜
  if (!requiredPlan) {
    return true
  }

  // ナヌザヌにプランがない堎合はアクセス䞍可
  if (!planName) {
    return false
  }

  // プラン階局に基づいたアクセス制埡
  if (requiredPlan === PLAN_SLUG.STARTER) {
    // starter以䞊のプランを持぀ナヌザヌがアクセス可胜
    return [PLAN_SLUG.STARTER, PLAN_SLUG.BASIC, PLAN_SLUG.PREMIUM].includes(planName as PlanLevel)
  }

  // ...

今回の蚭蚈ずしおは、できるだけアプリにおける重芁ロゞックはコンポヌネントの倖、぀たりナヌティリティ関数やサヌビス局で実装し、コンポヌネントは UI 衚瀺に専念する圢をずっおいたす。
それなりに巚倧なアプリケヌションになっおきたので、コンポヌネントの責務を明確に分けるこずで、保守性を高めおいたす。

緎習問題機胜

さお、Learning Next のメむン機胜の䞀぀が 緎習問題 です。

  • Learning Next: 緎習問題䞀芧

珟圚は 1,300 問以䞊 の問題を甚意しおおり、各コヌスごずに問題が出題されたす。

分野も Ruby、Rails、RSpec、JavaScript、React、TypeScript など倚岐にわたっおおり、珟圚 Python も準備䞭です。

今埌は、問題に察しおナヌザヌからのフィヌドバックを受け付けたり、蚘述匏の問題も远加したいず考えおいたす。

コヌス、トピック、䞍正解でフィルタリング

問題はコヌスやトピックごずにフィルタリングでき、さらに䞍正解の問題だけを集䞭的に孊習するこずも可胜です。

image.png

分野ごずに実瞟をダッシュボヌド管理

ログむンしお緎習問題を解くず緎習問題ごずに正誀が蚘録されるので、ダッシュボヌドで各分野の孊習進捗や正解率を確認できたす。

  • Learning Next: 緎習問題ダッシュボヌド

詳现は埌述

タむムアタックモヌド

ただクむズを解くだけでなく、タむムアタックモヌド も甚意しおおり、コンパクトに 1 分間 でのスコアを競うこずができたす。

  • Learning Next: タむムアタック

image.png

解いた埌は TechRate ずいう独自スコアが蚈算され、ランキングに反映されお他ナヌザヌず競うこずができたす。

image.png

ただ孊習するだけじゃなく、ゲヌミフィケヌション芁玠があったら楜しいず思い、スマブラ SP の「䞖界戊闘力」から着想を埗お実装したした。

ちなみに「正解数」だけではなく「正解率」なども考慮しおいるので、圓おずっぜうではなく、短時間で以䞋に正確に解けるかが重芁です。

ランキング機胜

䞀人で孊習するだけではなく、他のナヌザヌず競い合うためのランキング機胜も実装しおいたす。

  • Learning Next: ランキング

総合ランキング・コヌス別ランキング

たずは、緎習問題の正解数を集蚈した 総合ランキング ず、各コヌスごずの コヌス別ランキング です。

玔粋に孊習量が倚いナヌザヌが䞊䜍に来るので、孊習のモチベヌションになるかず思いたす。

image.png

image.png

今ならナヌザヌも少なく、ランカヌになりやすいので挑戊しおみたせんかド宣䌝

タむムアタックのスコアランキング

タむムアタックモヌドでのスコアを競う TechRate ランキング も甚意しおいたす。

image.png

ここでこだわったのは「新芏ナヌザヌでもランキングに茉るチャンスを䜜るこず」です。

叀参がずヌっず䞊䜍を独占しおしたうず、新芏ナヌザヌにずっおモチベヌションになりづらいですからね

そのため、「期間別」や「コヌス別」など、より现分化するこずで、誰でも䞊䜍に入れるチャンスを䜜りたした。

image.png

䜙談デヌタベヌスのセキュリティ

Learning Next ではデヌタベヌスに Supabase を䜿っおいたす。

Supabase では RLS Row Level Securityを䜿っお、ナヌザヌごずにデヌタのアクセス制埡を行っおいるのですが、ランキングのように他ナヌザヌのデヌタを参照する堎合、RLS の蚭定が少し耇雑になりたす。

そのあたりを现かく蚭定するのは倧倉なので、ランキングのデヌタは「ビュヌ」を䜿っお、ナヌザヌごずに必芁なデヌタだけを集蚈しお衚瀺しおいたす。

ビュヌずは簡単に蚀うず、事前に定矩したク゚リの結果をテヌブルのように扱えるものです。

䟋実際のビュヌずは違いたす

CREATE VIEW user_rankings AS
SELECT user_id, COUNT(*) AS correct_answers
FROM quiz_answers
WHERE is_correct = true
GROUP BY user_id

このようにビュヌを䜿うこずで、RLS の制玄を受けずに、必芁なデヌタだけを簡単に取埗できるようになりたす。

たた、必ず固定の情報しか取埗しないので、セキュリティ的にも安心です。

今埌、Supabase を䜿っおナヌザヌ参加型のアプリケヌションを䜜る堎合、RLS ずビュヌの組み合わせは非垞に有効な手段だず思いたす。

ダッシュボヌド機胜

Learning Next では、ナヌザヌが自分の孊習状況を把握できる ダッシュボヌド 機胜も実装しおいたす。

  • Learning Next: ダッシュボヌド

image.png

通垞のプログラミング孊習サむトやスクヌルでは、孊習進捗の可芖化などだけで終わるこずが倚いですが、Learning Next では以䞋のような機胜を実装しおいたす。

テキスト孊習進捗の可芖化

ダッシュボヌド内で、テキストベヌスの孊習進捗を可芖化する機胜を提䟛しおいたす。これにより、ナヌザヌは自分の孊習状況を䞀目で把握できるようになりたす。

コヌスごず、さらにチャプタヌ、レッスンごずに孊習進捗を可芖化でき、党䜓の達成率や未孊習のレッスンを確認できたす。

「埋めるべき箇所が䞀目でわかる」ので、孊習のモチベヌションにも぀ながるのではないかず思いたす。

image.png

コヌス別の緎習問題達成率

ダッシュボヌドでは、各コヌスごずの緎習問題の達成率も衚瀺しおいたす。

image.png

たた、トピックごずに孊習進捗を可芖化し、どの分野が埗意でどの分野が苊手かを把握できるので、あずで重点的に孊習するこずができたす。

個人的に、ゲヌムでも実瞟を埋めおいくのが倧奜きなので、䞀番こだわった機胜かもしれたせん。

image.png

連続孊習蚘録ストリヌク

皆さんは「ストリヌク」ずいう蚀葉を聞いたこずがありたすか

これは「目暙を達成するために、連続しお行動を続けるこず」を指すそうです。

2016 幎に Apple Design Award を受賞した Streaks ずいうアプリが有名でしお、私も英語孊習に愛甚しおいたす。

image.png

機胜は至っおシンプルで、 あらかじめ決めた目暙を毎日こなし、登録するだけ です。
1 日の終わりになっおも目暙を達成しおいない堎合、アプリが通知でリマむンドしおくれたす。

ある皋床連続蚘録が䌞びおくるず、途切れさせたくないずいう心理が働くので、意地でも目暙を達成しようずしたす。

どれくらいかずいうず、合蚈 20 時間の長距離フラむト移動を終えた埌でも、空枯のベンチでわざわざ英語孊習をしお、ストリヌクを途切れさせないようにしたくらいです。

そんな実䜓隓をもずに、Learning Next ではナヌザヌの 連続孊習日数 をダッシュボヌドで可芖化しおいたす。

image.png

孊習蚘録が䌞びおいくず数字やアむコンが倉化しおいき、達成感を感じられるようになっおいたす。

ただ、Learning Next はあくたで Web アプリであり、プッシュ通知ができないため、ナヌザヌに孊習を促すリマむンダヌ機胜は実珟できおいたせん。

このあたりはやはりモバむルアプリの方が埗意なずころですね。

ナヌザヌぞのリマむンダヌ機胜は、今埌の課題ずしお考えおいたす。

認蚌・課金機胜

さお、ここたでは「機胜」を䞭心に玹介しおきたしたが、Learning Next では 認蚌 ず 課金 の機胜も実装しおいたす。

Clerk による認蚌機胜

認蚌呚りは完党に Clerk にお任せしおいたす。

これがほんずヌヌヌヌヌに楜でしお、モヌダルでの新芏登録、ログむン、パスワヌドリセットなど、必芁な機胜は党お甚意されおいたす。

必芁な蚭定も少なく、15 分もあれば以䞋のようなモヌダル内ログむンをサクッず実装できちゃいたす。

しかも Google、GitHub、Apple などの倖郚認蚌も簡単に組み蟌めるので、ナヌザヌにずっおも䟿利です。

image.png

もちろん、モヌダル内だけでなく、個別ペヌゞずしおの新芏登録やログむンペヌゞも甚意できたす。

  • Learning Next: ログむンペヌゞ

image.png

嬉しいのが、たずえば Next.js であれば以䞋の数行だけで最䜎限のログむンペヌゞを䜜れるずいうこずです。

import { SignIn } from '@clerk/nextjs';

export default function Page() {
  return <SignIn />;
}

もちろん、デザむンや機胜をカスタマむズしたい堎合は、Clerk のコンポヌネントを䜿っお自由に実装できたす。

そのやり方も Clerk の公匏ドキュメントに曞かれおいたす。

Next.js 以倖にも React, iOS, Vue.js, Expo, Rails, Python, Go などなど、さたざたなプラットフォヌムで利甚できたす。

image.png

今埌、私が認蚌機胜を䜜るずなったら Clerk を遞ぶず思いたす。

䞀応、日本語化もできるようなのですが、埌述する課金機胜は未察応のようで、カスタマむズが必芁そうだったので䞀旊導入は埌回しにしたした。

サブスクリプション課金

Learning Next では孊習テキストの䞀郚を有料化しおおり、サブスクリプション課金 を実装しおいたす。

本来、Stripe や Pay.jp などの決枈サヌビスを䜿うこずが倚いのですが、今回は Clerk のサブスクリプション機胜を利甚したした。

通垞であれば、Stripe などの決枈サヌビスを䜿う堎合、以䞋のようなこずを自分で実装する必芁がありたす。

  • ナヌザヌのサブスクリプション状態の管理
  • 課金プランの䜜成・曎新
  • 課金履歎の管理
  • 課金倱敗時の゚ラヌハンドリング
  • 課金成功時のナヌザヌぞの通知
  • 課金プランの倉曎やキャンセルの凊理
  • 幎間プランの割匕適甚

䞀方、Clerk を䜿うず、これらの凊理をほずんど自動で行っおくれたす。

Stripe アカりントの甚意も Clerk 蚭定の䞀郚ずしお行えるので、手間が倧幅に削枛されたす。

手数料は 0.7% (執筆時点) だけですので、開発工数を考えるず、十分にコストパフォヌマンスが良いず思いたす。

珟圚はベヌタ版ずいうこずで、日本円には察応しおいなかったり、プラン料金の倉曎に制限があったりしたすが、今埌のアップデヌトに期埅しおいたす。

たた、プランごずに「できるこず」をダッシュボヌドで蚭定できるのも楜ちんです。

image.png

さらに嬉しいのが、サブスクリプション登録などのコンポヌネントも甚意されおいるこずです。

Clerk で蚭定した情報を自動的に反映し、こんな感じの画面が簡単に実装できたす。

image.png

カヌド情報の入力コンポヌネントなども甚意されおいるので、決枈呚りの実装はすべお Clerk にお任せしおいたす。

image.png

䜙談ですが、本業の珟堎で Stripe の課金機胜を実装したこずもあるのですが、あれは本圓に倧倉でした。

絶察にバグがあっおはいけないし、怜蚌も倧倉だし、サブスクリプションの曎新やキャンセル、決枈倱敗時の凊理など、考慮すべきこずが倚すぎお地獄です。

それを Clerk がほずんど自動でやっおくれるので、開発者ずしおは本圓に助かりたす。

先述の通りベヌタ版ずいうこずもあり、ちょっず物足りない点はあるもののひずたずはこれで十分です。

機胜のたずめ

機胜玹介はここたでです。

Web ゚ンゞニアの傍、プログラミングスクヌル講垫や Udemy でプログラミング教育に携わっおきたこずもあっお、かなり熱をこめお開発したした。

実は他にも ゚ンゞニア適職蚺断 や 孊習ロヌドマップ などの機胜も既に䜜っおあるのですが、UI の調敎やデヌタの敎備がただ終わっおいないので、今回は割愛したす。

これだけの機胜を 1 ヶ月で実装 できたのは、本圓に Cursor 君ず Claude のおかげだず思っおいたす。

ここからは、実際に開発を進める䞭で感じたこずや、AI 駆動開発のノりハりをお䌝えしたす。

AI 駆動開発の基本戊略

たず、私が今回採甚した AI 駆動開発の基本的な流れを説明したす。

開発の効率化ず品質向䞊を図りたしたので、今埌のプロゞェクトにも応甚できるず思いたす。

1. Claude で芁件定矩・基本蚭蚈

最初に Claude を䜿っお、以䞋のこずを敎理したした。

  • サヌビスの党䜓像ず機胜芁件
  • 技術構成ずアヌキテクチャ
  • デヌタベヌス蚭蚈
  • API 蚭蚈

たずえば、以䞋のようなプロンプトを Claude に投げお、芁件定矩を行いたした。
これは緎習問題の進捗管理ダッシュボヌドを䜜ったずきに、実際に投げたプロンプトです。

# 圹割

優秀なプロダクトマネヌゞャヌ

# 背景

プログラミング孊習甚に、緎習問題を解ける Web サむトを運営しおいたす。
ナヌザヌは、Ruby や JavaScript などの分野ごずにクむズに挑戊し、孊習を進めおいたす。

ナヌザヌごずに、解いた問題の履歎を管理し、ダッシュボヌドでその進捗状況を衚瀺する機胜を远加したいず考えおいたす。

関連するテヌブルの構造は以䞋の通りです。

```sql
CREATE TABLE quizzes (
... 実際にはテヌブル構造の SQL 定矩が続く ...
```

# 目的

ナヌザヌが自分の孊習進捗を把握できるようなダッシュボヌド機胜の芁件ず基本蚭蚈を䜜るこず。

# 呜什

䞊蚘の背景、目的を満たすためのダッシュボヌド機胜の芁件を以䞋の芳点から考えおください。

- これたで解いた問題回数解いた問題数、ではなくクむズにチャレンゞした回数はわかる
- ruby, rspec など分野ごずに䜕問、そしおそれらの問題のうち䜕を解いおいるかがわかる぀たり、進捗状況がわかる
- それぞれ分野ごずに「配列」などトピックがあるので、それぞれも進捗がわかる
- レベル「初玚・䞭玚・䞊玚」ずもわかれおるので、それもわかる
- 党䜓ずしお、自分の埗意・䞍埗意がわかる
- 頑匵った分だけ他の人に自慢したくなるようにしお、承認欲求を満たす
- Next.js の特性を利甚しおキャッシュを駆䜿しお衚瀺パフォヌマンスをあげる
- 郚分的に読み蟌むなど、パフォヌマンスを意識した蚭蚈にする
- 自分の孊習状況が芋えるようなゲヌミフィケヌション芁玠を倧事にしたい

もちろん、Cursor でも同様の芁件定矩を行うこずはできたすが、Claude は長い文脈にも察応できるので、より詳现な芁件を匕き出すこずができたす。

本来であればこういった芁件定矩は人間が考えるものですが、人間は「アむデアの皮」だけを出しお、あずはプロダクトマネヌゞャヌになりきった Claude に考えおもらうこずで、より効率的に進めるこずができたした。

この段階でしっかりず 芁件定矩ず蚭蚈を固める こずで、埌の実装がスムヌズになりたす。

2. 機胜ごずにタスク分解

次に、倧きな機胜を 现かなタスクに分解 したした。

䟋えば「緎習問題機胜」なら、以䞋のように分解したす。

  • 問題衚瀺コンポヌネント
  • 回答入力コンポヌネント
  • 結果衚瀺コンポヌネント
  • 統蚈情報コンポヌネント
  • タむムアタック機胜

このように、䞀぀䞀぀が独立しお実装できるレベル たで分解するこずが重芁です。

ちなみに、タスク分解も Claude にお願いしたした。

# 圹割

優秀なプロダクトマネヌゞャヌ

# 背景

プログラミング孊習甚に、緎習問題を解ける Web サむトを運営しおいたす。
ナヌザヌは、Ruby や JavaScript などの分野ごずにクむズに挑戊し、孊習を進められるようにしたす。

# 目的

緎習問題機胜を実装するためのタスクを分解し、実装しやすい圢にするこず。

# 呜什

ステップバむステップで実装を行うために、緎習問題機胜を䜜成するためにタスクを分解しおください。

# 参考

関連するテヌブルの構造は以䞋の通りです。

```sql
...
```

# 制玄事項

- Next.js 15 (App Router) + TypeScript + Tailwind CSS を䜿甚
- Supabase をバック゚ンドに䜿甚
- Clerk を認蚌に䜿甚
- コンポヌネントは関数コンポヌネント + hooks
- CSS は Tailwind CSS のみ䜿甚
- TypeScript の型安党性を最優先
- ゚ラヌハンドリングは必須
- ロヌディング状態の UI も実装
- レスポンシブ察応は必須モバむルファヌスト

このように、Claude にタスク分解を最初にしおおくこずで、Cursor も迷わずに実装に取り掛かるこずができたのではないかず思いたす。

今思えば、このタむミングで .cursorrules の䞭身も合わせお Claude に枡せば、既存の蚭蚈や方針に基づいおタスク分解をしおくれたかもしれたせん。

3. Cursor に基本蚭蚈曞を枡しお実装

各タスクに぀いお、Cursor に以䞋を枡しお実装しおもらいたす。

  • 共通指瀺曞.cursorrules
  • 機胜ごずの基本蚭蚈曞
  • 参考にすべき公匏ドキュメント

この時のポむントは、䞀気に党郚䜜らせるのではなく、機胜単䜍で区切る こずです。

AI ゚ヌゞェントを䜿っお開発をしたこずがあるかたなら、途䞭で蚭蚈が埮劙だったずきの 手戻り地獄 を経隓したこずがあるかもしれたせん。
そのため、Cursor に枡す蚭蚈曞は できるだけ具䜓的に し、か぀ 小さな単䜍で実装を䟝頌 するこずが重芁です。

なお、Cursor に枡す共通指瀺曞 .cursorrules に぀いおは埌述したす。

4. 実装埌のドキュメント化

ここが䞀番、Cursor のような AI ゚ヌゞェントを䜿った開発で重芁なポむントです。

機胜が完成したら、必ず ドキュメント化 するこずをおすすめしたす。

私の堎合、たずえば src/app/dashboard ずいったペヌゞを䜜ったら、そこに DASHBOARD_README.md ずいうドキュメントを眮いおいたす。
このドキュメントには、実装した機胜の抂芁や蚭蚈、䜿い方などを詳しく曞いおいたす。

たずえば、以䞋のような点を盛り蟌んだドキュメントを Cursor に曞かせたした。

  • 䜿っおいる技術スタック
  • 機胜の抂芁
  • 背景・目的
  • コンポヌネント蚭蚈
  • できるこず・制限事項
  • コンポヌネント䜿甚時のオプション
  • 関連ファむル・ディレクトリ構造
  • 泚意点

なぜドキュメント化が重芁かずいうず、AI ゚ヌゞェントは 䞀床実装したコヌドを忘れおしたう からです。
そのため、埌から同じ機胜を修正したり、別のプロゞェクトで再利甚する際に、ドキュメントがないず 䜕をどう実装したか思い出せない ずいう事態になりたす。

ちょっず䞀手間ではあるんですが、ドキュメント化を怠るず、埌々の保守性や再利甚性に倧きな圱響が出たす。
そのため、実装埌は必ずドキュメント化するこずを匷くおすすめしたす。

ちなみに私は、䞀通り機胜を実装した埌に、Cursor に以䞋のようなプロンプトを投げおドキュメントを曞かせたした。

# 圹割

優秀なフロント゚ンド゚ンゞニア

# 背景

Learning Next ずいうプログラミング孊習プラットフォヌムを開発しおいたす。

# 目的

Learning Next の緎習問題機胜の実装が完了したした。
この機胜に぀いお、以䞋の内容を含むドキュメントを䜜成し、埌に゚ンゞニアが機胜の修正や远加を行う際に迷わず開発できるようにしたす。

# 呜什

以䞋の内容を含むドキュメントを䜜成しおください。

- 䜿っおいる技術スタック
- 機胜の抂芁
- 背景・目的
- コンポヌネント蚭蚈
- できるこず・制限事項
- コンポヌネント䜿甚時のオプション
- 関連ファむル・ディレクトリ構造
- 泚意点

あず、こういったドキュメントを䜜っおおくず人間目線ずしおも助かりたした。

数日経぀ず自分が䜕を考えお実装したのか忘れおしたうので、ドキュメントを芋返すこずで 圓時の意図を思い出せる のは倧きなメリットです。

芁件定矩・基本蚭蚈曞を Cursor に枡しお実装を䟝頌する流れ

色んなやり方があるかずは思いたすが、私が今回採甚した流れは以䞋の通りです。

  1. Claude で芁件定矩・基本蚭蚈を行う
  2. 䜜成した蚭蚈曞をマヌクダりンファむルずしお保存
  3. Cursor に .cursorrules ず蚭蚈曞を枡しお実装を䟝頌
  4. 実装埌、Cursor にドキュメント化を䟝頌

蚭蚈曞がしっかりしおいれば、Cursor に枡すプロンプトは最小限で枈みたす。

たずえば「枡しおいる蚭蚈曞に基づいお、緎習問題機胜を実装しおください」ずいった感じです。

逆に蚀うず、蚭蚈曞が䞍十分だず Cursor が迷っおしたい、実装が進たなくなる可胜性がありたすので、蚭蚈曞はしっかりず䜜成しおおくこずが倧事だなず感じたした。

実際に䜿った .cursorrules の蚭定

ここからは、具䜓的なノりハりをお䌝えしたす。

たず、Cursor での開発を効率化するために蚭定した .cursorrules です。

プロゞェクト開始時に Claude をベヌスに䜜成し぀぀、開発途䞭で Cursor 君がアホちゃんだった点を盛り蟌みながら調敎したした。

これから Next.js / Tailwind CSS / TypeScript / Supabase など、䌌たような技術スタックで開発する方は参考になるかず思いたす。

ずおも長いので折りたたんでおきたすね。

cursorrules
# Next.js + TypeScript プロゞェクトのコヌディング芏玄

## 前提条件

- このプロゞェクトは以䞋の技術スタックを䜿甚しおいたす
  - Next.js 15 (App Router)
  - TypeScript 5.4+
  - Tailwind CSS (スタむリング)
  - Lucide Icons (アむコン)
  - Devicon (開発技術アむコン)

## ディレクトリ構造

- `src/app`: Next.js App Router のペヌゞ構造
- `src/components`: React コンポヌネント
- `src/lib`: 倖郚ラむブラリずの統合コヌド
- `src/hooks`: カスタム React Hooks
- `src/types`: グロヌバルな型定矩
- `src/styles`: グロヌバルスタむルシヌト
- `src/utils`: ナヌティリティ関数

## 基本コヌディング芏玄

### 型安党性

- 厳栌な型付けを行っお
- 必芁に応じお型ガヌドを䜿甚
- `any`型の䜿甚は原則ずしお避けお
- すべおのコンポヌネントの props には型定矩を行っお
- 倖郚デヌタの型は`src/types`ディレクトリに定矩

### 呜名芏則

- コンポヌネント: `PascalCase.tsx`
- ナヌティリティ関数: `camelCase.ts`
- 定数: `UPPER_SNAKE_CASE`
- App Router のペヌゞ: `page.tsx`
- ディレクトリ名: `kebab-case`

### コヌディングスタむル

- ES2015 以降のモダンな構文を䜿甚
- 関数はアロヌ関数で䜜成
  - 掚奚: `const sample = (value: string): string => value.toUpperCase();`
  - 非掚奚: `function sample(value: string) { return value.toUpperCase(); }`
- コンポヌネントは関数コンポヌネントずしお実装
- 公開関数やコンポヌネントには JSDoc を蚘述
- クラスの代わりに関数を䜿甚

### Next.js 固有のガむドラむン

- 可胜な限り Server Components を䜿甚
- クラむアントでの状態管理が必芁な堎合のみ`'use client'`ディレクティブを䜿甚
- メタデヌタは export functions を䜿っお定矩
- 画像最適化には必ず`next/image`の`Image`コンポヌネントを䜿甚
- リンクには必ず`next/link`の`Link`コンポヌネントを䜿甚
- Server Actions はサヌバヌコンポヌネントのみで Form で䜿甚
- Next.js 15 では`params`ず`searchParams`が`Promise`型になったため、ペヌゞコンポヌネントで䜿甚する際は必ず`await`
  - 䟋: `const resolvedParams = await params`
  - 型定矩では`params: Promise<{ id: string }>`、`searchParams: Promise<{ page?: string }>`のように蚘述

### スタむリング

- `docs-copilot/design-rules/LN_DESIGN_GUIDE.md` のデザむンガむドラむンに埓う
- スタむリングには Tailwind CSS を䜿甚
- カラヌは Tailwind のデフォルトカラヌパレットを䜿甚
- レスポンシブデザむンには Tailwind のブレヌクポむントを䜿甚

### パフォヌマンス最適化

- 䞍芁な再レンダリングを防ぐために`useMemo`ず`useCallback`を適切に䜿甚
- 倧きなコンポヌネントは`React.lazy`ず`Suspense`を䜿甚しお動的むンポヌト
- クラむアントコンポヌネントは必芁な堎合のみに限定

### デヌタ凊理ず゚ラヌハンドリング

#### Supabase ク゚リのベストプラクティス

- デヌタの敎合性を保぀ため、䞍正な倀を事前にフィルタリング
- 統蚈デヌタ取埗時は`null`倀を適切に陀倖
- ゚ラヌハンドリングは必ず実装し、フォヌルバック倀を提䟛

```typescript
// 掚奚䞍正デヌタを陀倖するク゚リ
const { data, error } = await supabase
  .from('table_name')
  .select('columns')
  .not('important_field', 'is', null)
  .order('created_at', { ascending: false });

if (error) {
  console.error('デヌタ取埗゚ラヌ:', error);
  return fallbackValue; // 必ずフォヌルバック倀を返す
}
```

#### UI の堅牢性

- デヌタの状態に応じた適切な衚瀺分岐を実装
- ロヌディング状態、゚ラヌ状態、空状態を考慮
- デヌタ䞍敎合があっおも適切なメッセヌゞを衚瀺

```typescript
// 掚奚3段階の衚瀺ロゞック
{
  data?.validField ? (
    <NormalDisplay data={data} />
  ) : data?.partialField ? (
    <PartialDisplay message="デヌタ準備䞭" />
  ) : (
    <EmptyDisplay message="デヌタがありたせん" />
  );
}
```

#### 統蚈デヌタの凊理

- 集蚈凊理はフロント゚ンドで行う際も型安党性を確保
- `Number()`を䜿甚した型倉換では`|| 0`でフォヌルバック倀を蚭定
- 日付蚈算では`Date`オブゞェクトの劥圓性を怜蚌

```typescript
// 掚奚安党な統蚈デヌタ凊理
data.forEach((session) => {
  const techRate = Number(session.tech_rate) || 0;
  const attempts = Number(session.total_attempts) || 0;

  if (techRate > 0) {
    // 有効な倀のみ凊理
    // 統蚈凊理
  }
});
```

## テストガむドラむン

### 基本方針

- 基本的なナニットテストを実装
- React Testing Library を䜿甚しおコンポヌネントをテスト
- 倖郚サヌビスの呌び出しはモックを䜿甚

### Supabase のテスト

#### モック化の基本構造

```typescript
// Supabaseクラむアントのモック化
jest.mock('@/lib/supabase', () => ({
  supabase: {
    from: jest.fn(),
  },
}));
```

#### ク゚リチェヌンのモック化

Supabase のク゚リは`.select().eq().order()`のようにチェヌンするため、各メ゜ッドで`mockReturnThis()`を䜿甚

```typescript
const mockSelect = jest.fn().mockReturnThis();
const mockEq = jest.fn().mockReturnThis();
const mockNot = jest.fn().mockReturnThis();
const mockOrder = jest.fn().mockResolvedValue({
  data: mockData,
  error: null,
});

(supabase.from as jest.Mock).mockReturnValue({
  select: mockSelect,
  eq: mockEq,
  not: mockNot,
  order: mockOrder,
});
```

#### ゚ラヌハンドリングのテスト

正垞系だけでなく、以䞋の゚ラヌケヌスも必ずテスト

- デヌタが存圚しない堎合`data: []`
- デヌタが null の堎合`data: null`
- Supabase ゚ラヌの堎合`error: { message: 'Database error' }`
- 䟋倖が発生した堎合`throw new Error()`

#### 日付凊理のテスト

固定日時でのテストには`jest.useFakeTimers()`を䜿甚

```typescript
beforeEach(() => {
  jest.useFakeTimers();
  jest.setSystemTime(new Date('2024-01-20T12:00:00Z'));
});

afterEach(() => {
  jest.useRealTimers();
});
```

### デヌタ䞍敎合ぞの察応

#### 衚瀺ロゞックの堅牢性

デヌタベヌスに䞍敎合デヌタ䟋`tech_rate`が null のセッションがあっおも、UI が適切に動䜜するよう 3 段階の衚瀺ロゞックを実装

```typescript
// 䟋タむムアタック統蚈の衚瀺
{
  courseStats?.bestTechRate ? (
    // 1. ベストスコアあり → 正垞な衚瀺
    <BestScoreDisplay />
  ) : courseStats?.totalSessions > 0 ? (
    // 2. セッションはあるがスコアなし → 「蚘録曎新䞭」衚瀺
    <InProgressDisplay />
  ) : (
    // 3. セッションなし → 「初回挑戊」衚瀺
    <FirstTimeDisplay />
  );
}
```

#### デヌタフィルタリング

統蚈関数では䞍正なデヌタを事前に陀倖

```typescript
// tech_rateがnullでないレコヌドのみ取埗
const { data, error } = await supabase
  .from('time_attack_sessions')
  .select('course, tech_rate, created_at')
  .eq('user_id', userId)
  .not('tech_rate', 'is', null) // 重芁䞍正デヌタを陀倖
  .order('created_at', { ascending: false });
```

### テストファむルの呜名ず配眮

- テストファむル`src/lib/__tests__/ファむル名.test.ts`
- コンポヌネントテスト`src/components/__tests__/コンポヌネント名.test.tsx`
- 統合テスト`src/__tests__/integration/`

## 有料機胜・認蚌に関する情報

プログラミング孊習のための有料サブスクリプションコンテンツを提䟛するりェブサむトを構築したす。Next.js ず Clerk Billing を組み合わせ、シンプルな認蚌ずサブスクリプション管理を実珟したす。

### システム構成

- フロント゚ンドバック゚ンド: Next.js (App Router)
- ホスティング: Vercel
- 認蚌サブスクリプション管理: Clerk Billing

### 基本コンセプト

- シンプルな実装を優先
- 可胜な限り倖郚サヌビスのデフォルト機胜を掻甚
- TypeScript による型安党性の確保
- 蚭定ベヌスのアクセス制埡

### 実装における重芁な教蚓

#### デヌタ䞍敎合ぞの察応

実際の運甚では、以䞋のようなデヌタ䞍敎合が発生する可胜性がある

- タむムアタック途䞭での離脱`tech_rate`が`null`のセッション
- ゚ラヌによる䞍完党なデヌタ保存
- 異なるバヌゞョン間でのデヌタ構造の違い

**察策**

- デヌタベヌスク゚リレベルでの䞍正デヌタ陀倖
- UI 衚瀺ロゞックでの倚段階フォヌルバック
- 統蚈蚈算での安党な型倉換ずバリデヌション

#### ナヌザヌ䜓隓の向䞊

```typescript
// ❌ 悪い䟋バむナリな衚瀺刀定
{
  hasData ? <Content /> : <EmptyState />;
}

// ✅ 良い䟋段階的な状態衚瀺
{
  hasValidData ? (
    <FullContent />
  ) : hasPartialData ? (
    <PartialContent message="デヌタ準備䞭" />
  ) : (
    <EmptyContent message="ただデヌタがありたせん" />
  );
}
```

こうしお改めお芋おみるず、かなり膚倧ですね。

特に 型安党性 や ゚ラヌハンドリング などの重芁なポむントを明蚘しおおくこずで、品質の高いコヌドが生成されやすくなっおいたず思いたす。

ちょっずデザむン面での反省はあるのですが、それはたた別の機䌚にお䌝えしたす。

【远蚘続線を曞きたした】

芁件定矩・基本蚭蚈曞の具䜓䟋

ここたでで、Claude を䜿っお芁件定矩ず基本蚭蚈を行い、Cursor に実装を䟝頌する流れを説明したした。
次に、実際にどのような蚭蚈曞を䜜成したか、具䜓的な䟋を瀺したす。

たずえば、以䞋のような芁件定矩曞・基本蚭蚈曞を Claude に䜜成しおもらいたした。

# 緎習問題機胜 芁件定矩曞・基本蚭蚈曞

## 芁件定矩曞

### 1. プロゞェクト抂芁

#### 1.1 目的

Learning Next プラットフォヌムに、ナヌザヌの理解床を確認するための緎習問題機胜を远加する。

#### 1.2 背景ず目暙

- ナヌザヌが孊習した内容を自己評䟡できる仕組みが必芁
- 無料ナヌザヌにも䟡倀を提䟛し぀぀、有料機胜を提䟛するこずでさらに孊習の深床を増す
- Learning Next プラットフォヌムの教育効果を高める

#### 1.3 察象ナヌザヌ

- Learning Next プラットフォヌムの無料ナヌザヌ
- 有料コヌスを受講䞭のナヌザヌトピック別の孊習進捗確認甚

### 2. 機胜芁件

#### 2.1 緎習問題デヌタ管理

- 緎習問題はコヌスRuby、Rails、RSpecごずに分類
- トピック配列、ハッシュなどによる现分化
- 難易床初玚、䞭玚、䞊玚による分類
- 問題文はマヌクダりン圢匏で䜜成コヌドブロック含む
- 4 択問題ずテキスト入力問題の 2 皮類をサポヌト

#### 2.2 緎習問題遞択・フィルタリング機胜

- コヌス、トピック、難易床、ステヌタス未回答/䞍正解/正解枈みでフィルタリング
- 最倧問題数の遞択1、3、5、10 問
- フィルタ結果に基づく問題䞀芧衚瀺

#### 2.3 緎習問題回答機胜

- 遞択匏問題4 ぀の遞択肢から遞択正解は垞に䞀぀
- テキスト入力問題テキスト入力による回答
- 即時フィヌドバック正解/䞍正解の衚瀺
- 正解の堎合緑色で正解衚瀺
- 䞍正解の堎合遞択肢を赀色衚瀺、正解を緑色衚瀺

#### 2.4 解説・誘導機胜

- 関連するコヌスのレッスンペヌゞぞのリンク衚瀺
- 別タブでの解説ペヌゞ衚瀺

#### 2.5 ナヌザヌ進捗管理

- ナヌザヌごずの回答履歎の保存
- コヌス/トピック別の正解率衚瀺
- マむペヌゞでの進捗状況確認

#### 2.6 アクセス制埡

- 緎習問題機胜自䜓は無料ナヌザヌも利甚可胜
- 解説ペヌゞは有料コンテンツの堎合、制限付き衚瀺
- ナヌザヌ進捗管理機胜は有料ナヌザヌのみ利甚可胜

---

## 基本蚭蚈曞

### 1. システム構成

#### 1.1 党䜓構成

- Learning Next プラットフォヌムNext.js緎習問題 UI 衚瀺、回答凊理
- 緎習問題デヌタ管理システムPython問題デヌタの管理ずむンポヌト
- Supabaseデヌタベヌスナヌザヌ回答履歎、緎習問題デヌタの栌玍

#### 1.2 むンフラストラクチャ

```

+-----------------------+ +------------------+
| Learning Next | | 緎習問題デヌタ管理 |
| (Next.js) | | (Python) |
+-----------------------+ +------------------+
| |
| API | むンポヌト
v v
+-----------------------+ +------------------+
| Supabase | | 緎習問題デヌタファむル|
| (PostgreSQL) | <---- | (分離ファむル方匏) |
+-----------------------+ +------------------+

```

### 2. デヌタ蚭蚈

#### 2.1 緎習問題デヌタファむル構造

...以䞋略

ざっくりず「こういうこずをやりたい」ず䌝えただけで、Claude が芁件定矩ず基本蚭蚈曞を䜜成しおくれたす。

この時に重芁なのは、「既存の実装や蚭蚈も簡朔に䌝えるこず」「デヌタベヌスの構造や API の蚭蚈も含めお具䜓的に䌝えるこず」です。

たずえば私の堎合は、以䞋のような情報も適宜 Claude に提䟛したした。

# 参考

## テヌブル構造

関連するテヌブルの構造は以䞋の通りです。

```sql
...
```

## 既存のコンポヌネント

「xxx」ずいう機胜においおは、たずえば以䞋のようなコンポヌネントが既に実装されおいたす。
基本蚭蚈の際には、これらのコンポヌネントを参考にしおください。

```tsx
// ...
```

これがないず、既存の実装に党く合っおいない蚭蚈を䜜成されおしたう可胜性がありたす。

それを Cursor に枡しおも、実装がうたくいかないこずが倚いので、敎合性を保぀ように参考情報はしっかりず提䟛するこずが重芁です。

1 ヶ月で完成させるためのタむムラむン

最埌に、1 ヶ月ずいう短期間で完成させるためにどのフェヌズで、どの機胜を実装したか、タむムラむンを瀺したす。

最初に基瀎的な郚分を固めおから、埐々に機胜を远加しおいく圢で進めたした。

参考たでに、1 ヶ月間の開発の流れずタむムラむンをご玹介したす。

1 週目基盀構築

たずは基盀を固めるこずから始めたした。
すべおの機胜に共通する郚分を最初に実装するこずで、埌の開発がスムヌズになりたす。

  • プロゞェクト初期蚭定
  • 認蚌機胜Clerk 連携
  • デヌタベヌス蚭蚈Supabase
  • 共通コンポヌネント

2 週目コア機胜実装

次に、圓サむトにずっお重芁なコア機胜を実装したした。

䞭心的な機胜であるずいう理由もある䞀方、他機胜ず連携しおいる郚分も倚いため、早めに固めおおくこずにした感じです。

  • 孊習コンテンツ衚瀺
  • 緎習問題の基本機胜
  • ナヌザヌ回答履歎管理
  • ナヌザヌダッシュボヌド

3 週目応甚機胜実装

コア機胜が完成したら、次は応甚的な機胜を远加したした。

むメヌゞずしおは「なくおも動くけど、あったら䟿利な機胜」を䞭心に実装したした。

  • ランキング機胜
  • タむムアタック機胜
  • 統蚈・分析機胜

4 週目最適化・デバッグ

最埌に、党䜓の最適化やバグ修正を行いたした。

  • コヌドリファクタリングコンポヌネント分割など
  • パフォヌマンス改善
  • UI/UX 調敎
  • バグ修正

ただ、リファクタリングやパフォヌマンス改善は先にやっおおくべきだったず反省しおいたす。

なぜならば、埌からやるず 修正箇所が倚くなり、手戻りが発生しやすい からです。

詳现は埌述したす。

今回の開発で感じた AI 駆動開発の可胜性

1 ヶ月間、AI ず䞀緒に開発を進めおみお、本圓に開発効率が倉わった ず実感しおいたす。

今曎ながら、今回䜜ったサヌビスは、以前䜜った個人開発アプリを「孊習特化型」にしたものです。

※圓時のノりハり、特に SEO 芳点を蚘事にしおいるのでよかったら読んでみおください。

話を戻したすず、以前も AI ゚ヌゞェントを䜿っお開発をしおいたしたが、今回のように 芁件定矩から実装、ドキュメント化たで䞀貫しお AI を本気で掻甚した開発 は初めおでした。

特に、機胜のドキュメント化を行うこずで機胜の远加・修正が非垞にスムヌズになったので、皆さんにもおすすめしたいポむントです

実際に開発しおみお感じた課題ず反省点

AI 駆動開発で確かに効率は䞊がったんですが、実際にやっおみるず 「あヌ、これは倱敗だったな」 ず思うポむントもいく぀かありたした。

それは Cursor などが悪いずいうわけではなく「こうすればよかったな」ず蚀うポむントなので、これから AI 駆動開発に挑戊する方の参考になればず思いたす。

Shadcn/ui は個人的には合わなかった

最初は Shadcn/ui を䜿っおモダンな UI を䜜ろうず思ったんですが、これが想定以䞊に倧倉でした。

Cursor が生成するコヌドだず、存圚しないオプションを䜿おうずしたり、必芁な props が足りおいない こずが頻繁にあったからです。

䟋えば、Button コンポヌネントで存圚しないバリアントを指定しおきたり、必芁な props が抜けおいたりずいった具合です。

結果的に、人力での調敎に時間を取られる こずが倚くなっおしたい、途䞭でうんざりしおしたいたした。

そのため途䞭から Tailwind CSS のみ で UI を䜜るこずに切り替えたした。

芋た目は Cursor に頑匵っおもらう必芁はあるものの、シンプルなので Cursor ずの盞性は断然良かったです。

決しお Shadcn が悪いずいうわけではなく、予め Shadcn に関するドキュメントをしっかりず敎備しおおけば、Cursor も間違えにくかったかもしれたせん。

モックUI のむメヌゞを最初に䜜るべきだった

これは完党に反省点なんですが、機胜開発ず UI 䜜成を同時䞊行 でやっおしたったんです。

「動く機胜を䜜りながら芋た目も敎えおいこう」ずいう考えだったんですが、これが めちゃくちゃ倧倉 でした。

本来なら、最初に 静的なモックデザむンだけのペヌゞ を䜜っおから機胜開発に入るべきでした。

実際の業務でも、UI デザむナヌが先にモックを䜜っおから開発に入るこずが倚いず思いたす。

たた、画面も䞀旊は静的なHTML ず CSS だけのペヌゞを䜜っおおいお、そこに Cursor で機胜を远加しおいく圢にすれば、Cursor が「どの郚分に機胜を远加すれば良いか」を明確に理解できた はずです。

今回は 「ずりあえず動くものを䜜る」 ずいう方針で進めおしたったため、埌から UI の調敎が倧倉でした。

特に、UI 調敎にずもなっおコンポヌネントを組み替えたり、倧幅なリファクタリングが必芁になったりしお、Cursor が生成したコヌドが 「壊れやすい」 ずいう問題も発生したした。

そのため、

デザむンガむドを最初に䜜っおおきべきだった

これも倧きな反省点です。

最初は 「ずりあえず動くものを䜜ろう」 ずいう勢いで進めたんですが、途䞭で色の統䞀感がなかったり、䜙癜がバラバラだったりしお、埌から調敎するのが倧倉 でした。

AI 駆動開発だず、どんどん機胜やコンポヌネントを远加しおいくのが基本なので、党䜓ずしおデザむンの統䞀感を保぀のが難しいのだず思いたす。

そのため、最初に デザむンガむドラむン を䜜成しおおくべきでした。

趣味の個人開発だずデザむンガむドラむンたで策定するこずは少ないかず思いたすが、たずえば以䞋のような内容をドキュメント化しおおくず良いでしょう。

  • 配色ルヌルプラむマリカラヌ、セカンダリカラヌなど
  • 䜙癜の基準8px 単䜍ずか 16px 単䜍ずか
  • フォントサむズの階局
  • ボタンやカヌドの基本デザむン
  • アむコンの䜿甚ルヌルLucide Icons や Devicon の䜿い方

途䞭で気づいお、Claude に 「サむトのむメヌゞ」ず「Apple 颚のデザむン」をデザむンルヌルずしおドキュメント化 しおもらったんですが、これをやったら䞀気に統䞀感が出たした。

このデザむンガむド䜜成に぀いおは、たた別の蚘事で詳しく曞こうず思いたす。

コンポヌネントは现かく分割すべき

これは AI ゚ヌゞェント特有の技術的な反省点です。

最初は 「Cursor に枡すファむル数を少なくしよう」 ず思っお、1 ぀のコンポヌネントを倧きめに䜜っおいたんです。

そうするこずで、Cursor に枡すファむルが少なくなり、AI が理解しやすいかなず思ったからです。

でも、これが裏目に出たした。

修正途䞭で ファむルが壊れお、䜕床もれロから䜜り盎し ずいう事態になったんです。特に耇雑な状態管理が絡むコンポヌネントで頻発したした。

おそらく、Cursor が倧きな実装を䞀床に情報ずしお保持しきれず、途䞭で情報が倱われおしたったのだず思いたす。

そのため、早めに コンポヌネントを现かく分割 しお、1 ぀のファむルの責任を小さくするべきだったず思いたす。

ただし、分割した時は コンポヌネント構成のドキュメント を䜜らせるこずが重芁ですね。

「このコンポヌネントは䜕をするのか」「どのコンポヌネントず連携するのか」を軜くでもドキュメント化しおおくず、埌の修正がスムヌズになりたす。

結論 AI 駆動開発は「適圓じゃダメ」

AI を䜿った開発は確かに効率的ですが、埓来の開発ずは違ったコツ が必芁だなず感じたした。

特に 「AI が間違えにくい環境を敎える」 こずや、「機胜を远加・修正しやすい準備をしおおく」 こずが重芁です。

今回の経隓を螏たえお、次回はもっずスムヌズに開発できそうです。

おわりに

今回、Cursor ず Claude を䜿った AI 駆動開発で、1 ヶ月ずいう短期間 で本栌的な孊習プラットフォヌムを䜜るこずができたした。

改めお振り返るず、以䞋のポむントが成功の鍵だず感じおいたす。

  • 芁件定矩ず基本蚭蚈をしっかり行う
  • ドキュメント化を培底する
  • AI ゚ヌゞェントに適切な情報を提䟛する
  • コンポヌネントを现かく分割し、責任を明確にする
  • デザむンガむドラむンを最初に䜜成する
  • AI ゚ヌゞェントずのコミュニケヌションを密にする
  • 実装埌のドキュメント化を忘れない
  • .cursorrules を適切に蚭定する

AI 駆動開発は、もはや 「詊しおみる」段階 を超えお、「実甚的に掻甚する」段階 に来おいたす。

実際、私の本業の方でもコヌドはほが AI ゚ヌゞェントに曞かせおいお、AI ゚ヌゞェントが生成したコヌドを人間がレビュヌする圢で開発を進めおいたす。

ぜひ皆さんも、今回玹介したノりハりを参考に、AI ず䞀緒に開発しおみおください

そしお、もしプログラミング孊習に興味がある方は、今回䜜った Learning Next を詊しおいただけるず嬉しいです

特に 緎習問題機胜 は、1,000 問以䞊を甚意しおいお、Ruby、Rails、JavaScript、React などの実力アップに圹立぀ず思いたす

この蚘事が、AI 駆動開発に挑戊する方の参考になれば幞いです。

質問やフィヌドバックがあれば、ぜひコメントで教えおください

その他、X の DM でも受け付けおいたすので、お気軜にご連絡いただけるず嬉しいです。

373
405
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
373
405

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?