注意:このコンテンツは自動翻訳されたものです。 フィードバックを送る

パーソナルポートフォリオサイト

Next.js 16、React 19、TypeScriptで構築したモダンでコンテンツ重視のポートフォリオ。AI支援開発でゼロから構築。

Role: 開発者 & デザイナー2025年12月

何年もの間、個人サイトを作ろうと思っていました。ドメインは埃をかぶったまま。アイデアはメモアプリから出ることはありませんでした。

そして、計画をやめて作り始めました。2日後、このサイトは公開されました。

何が変わったか

以前は、サイトを公開するには何週間もの作業が必要だと思っていました。フォントの選択、レイアウトのデバッグ、しっくりこないコピーの作成。今回は違うアプローチを取りました:AIと一緒に作り、素早くリリースし、後から改善する。

結果:48時間で51コミット。音声ナレーション付きのブログ。実際に機能するコンタクトフォーム。拡張可能なデザインシステム。完璧ではないが、リアル。

驚いたのはスピードではありませんでした。作業の感覚でした。検索が減り、構築が増えた。迷いが減り、リリースが増えた。ツールが面倒な部分を処理してくれたので、重要なことに集中できました。

このページは全ストーリーです。どのように組み立てられたか、内部の仕組み、そして次回何を変えるか。

Cursor IDE
ClaudeアシスタントとCursor IDE

技術スタック

CategoryTechnologyVersionPurpose
FrameworkNext.js (App Router)16.0.10Server components, routing, SSR
UI LibraryReact19.2.3Component architecture
LanguageTypeScript5.xType safety
StylingTailwind CSS4.xUtility-first styling
Componentsshadcn/uiNew YorkPre-built accessible components
Contentnext-mdx-remote5.0.0MDX rendering with frontmatter
Parsinggray-matter4.0.3Frontmatter extraction
IconsLucide React0.561.0SVG icon library
EmailSendGrid8.1.6Contact form delivery
AnalyticsGoogle Analytics 4Traffic and behavior tracking
AudioElevenLabs Audio NativeText-to-speech for blog posts
Dependencies
Package.json dependencies

アーキテクチャ

アプリケーションはNext.js App Routerの規約に従い、ページ、コンポーネント、コンテンツ、ユーティリティを明確に分離しています。

ftchvs_26/
├── app/                    # Next.js App Router pages
│   ├── layout.tsx          # Root layout with fonts and nav
│   ├── page.tsx            # Home page
│   ├── globals.css         # Global styles and design tokens
│   ├── about/              # About page
│   ├── blog/               # Blog listing and [slug] pages
│   ├── projects/           # Projects listing and [slug] pages
│   ├── contact/            # Contact form with API route
│   └── resume/             # Resume page
├── components/             # React components
│   ├── nav.tsx             # Navigation with mobile sheet
│   ├── theme-toggle.tsx    # Dark/light mode toggle
│   ├── tts-player.tsx      # ElevenLabs audio player
│   ├── mdx-content.tsx     # MDX renderer
│   └── ui/                 # shadcn/ui components
├── content/                # MDX content files
│   ├── blog/               # Blog posts
│   ├── pages/              # Static pages (about)
│   └── projects/           # Project case studies
├── lib/                    # Utilities
│   ├── content.ts          # Content loading and parsing
│   ├── constants.ts        # Site configuration
│   ├── schema.tsx          # JSON-LD structured data
│   └── utils.ts            # Helper functions
└── public/                 # Static assets

データフロー

コンテンツはMDXファイルからlib/content.tsを経由してページに流れます:

  1. MDXファイルがYAMLフロントマター付きでコンテンツを保存
  2. gray-matterがフロントマターを型付きオブジェクトに解析
  3. next-mdx-remoteがMDXをReactレンダリング用にシリアライズ
  4. ページコンポーネントがリクエスト時にコンテンツを取得
  5. MDXコンポーネントが最終的なHTMLをレンダリング
GitHub Repo
GitHubリポジトリ構造

タイポグラフィデザインシステム

すべてのページで一貫したスタイリングを実現するため、トークンベースのタイポグラフィシステムを構築しました。各トークンは特定のTailwindクラスにマッピングされています。

TokenCSS ClassStylesUsage
Page Title.text-page-titletext-xl font-medium mb-6H1 headings
Section Title.text-section-titletext-lg font-medium mt-8 mb-4H2 headings
Subsection.text-subsection-titletext-base font-medium mt-6 mb-3H3 headings
Body.text-bodytext-[15px] leading-relaxed text-foreground/90Paragraphs
Body Container.text-body-containerspace-y-4 text-[15px] leading-relaxedMultiple paragraphs
Meta.text-metatext-[14px] text-muted-foregroundDates, tags, labels
Link.text-linkunderline underline-offset-2 hover:text-foregroundInline links
List Item.text-list-itemtext-sm text-foreground/80Card and list text

フォントスタック

  • Geist Sans — 本文とUIのプライマリ書体
  • Geist Mono — コードブロックとターミナルロゴ

両フォントは最適なパフォーマンスのためnext/font/google経由でdisplay: swapで読み込まれます。

カラーシステム

色は知覚的に均一な調整のためにOKLCH色空間を使用しています。システムには以下が含まれます:

  • ライトモード — 控えめなアクセントを持つニュートラルグレースケール
  • ダークモード — ハイコントラストテキストを持つ深い背景
  • セマンティックカラー — Primary、Secondary、Muted、Accent、Destructive、Success

主な機能

ダークモード

テーマ設定はlocalStorageに保存され、初回訪問時はシステム設定を尊重します。<head>内のブロッキングスクリプトが間違ったテーマのフラッシュを防ぎます。

<script dangerouslySetInnerHTML={{
  __html: `(function(){
    var t = localStorage.getItem('theme') ||
      (matchMedia('(prefers-color-scheme:dark)').matches ? 'dark' : 'light');
    if (t === 'dark') document.documentElement.classList.add('dark')
  })();`,
}} />

テキスト読み上げ

プレーヤーはハイドレーションの問題を避けるため動的に読み込まれます:

const TTSPlayer = dynamic(
  () => import('@/components/tts-player').then((mod) => mod.TTSPlayer),
  { ssr: false }
);

ElevenLabsでボイスクローンを作成する

オーディオプレーヤー用の独自のボイスクローンを作成するには:

  1. ElevenLabsにサインアップまたはログイン
  2. ダッシュボードのVoicesセクションに移動(左サイドバー)
  3. Add a new voiceをクリック
  4. Professional Voice Clone(最高品質推奨)またはInstant Voice Clone(より高速、必要な音声が少ない)を選択
  5. 音声サンプルをアップロード:
    • Professional Voice Clone: 最低1時間の高品質音声(最良の結果には2-3時間が理想的)
    • Instant Voice Clone: 最低30秒のクリーンな音声
    • 長い録音は〜30分のサンプルに分割してアップロードを容易に
    • 背景ノイズ、エコー、不要な音のないクリーンで高品質な録音を使用
  6. オプションでRecord yourselfを使用してインターフェースで直接録音
  7. ボイスクローンに名前とラベルを付ける
  8. 声をクローンする権利と同意があることを確認
  9. Save voiceをクリックしてモデルをトレーニング
  10. トレーニング完了後、音声設定からVoice IDをコピー
  11. lib/constants.tsELEVENLABS_VOICE_IDを更新してプロジェクトに設定

ボイスサンプル

クローンした声のサンプルはこちら:

ボイスサンプル
0:00 / 0:00

音声サンプルを生成するには、ElevenLabs Text to Speech playgroundを使用できます。クローンした声を選択し、テキストを入力し、Generateをクリックして、ファイルをpublic/audio/ディレクトリにvoice-sample.mp3として保存してください。

ブログの音声ナレーション(ローカルのKokoro)

アップデート — 2026年4月。 ブログ記事とAboutページは、Kokoro-82Mを使うようになりました。Apache 2.0ライセンスのTTSモデル(パラメータ82M、ボイスaf_heart)で、kokoro-onnx経由でCPU上でローカルに動作します。生成は決定論的で無料、外部APIにも依存しません。上のボイスサンプルはオリジナルのElevenLabsでクローンした声のまま残しています — 移行したのはブログのナレーションだけです。

初期設定は一度だけ:npm run setup:kokoroがgitignoreされた.kokoro/ディレクトリにPython venvとONNXモデル(約340 MB)を作成します。その後npm run generate-audioで英語コンテンツすべてをナレーションし、public/audio/manifest.jsonを記事ごとのハッシュで更新します。Apple M4 Maxではリアルタイムの約5倍の速度で動き、3分の記事が約30秒で生成されます。

コンタクトフォーム

コンタクトフォームはSendGridを使用してメール配信を行います:

  • クライアントサイドバリデーション
  • /contact/apiのサーバーサイドAPIルート
  • 直接返信用のReply-toヘッダー
  • ユーザーフレンドリーなメッセージでのエラーハンドリング

SEO

すべてのページに含まれるもの:

  • Metadata — タイトル、説明、キーワード
  • Open Graph — ソーシャル共有用の画像とテキスト
  • Twitter Cards — Twitter専用フォーマット
  • JSON-LD — Person、Website、Article、Breadcrumb用の構造化データ
  • Sitemap/sitemap.xmlに自動生成
  • Robots — 検索エンジンのクロールルール
Vercel Dashboard
Vercelダッシュボード

構築プロセス

フェーズ1: プロジェクトセットアップ

App Routerテンプレートを使用してcreate-next-appで開始。TypeScript、Tailwind CSS 4を追加し、shadcn/uiをNew Yorkスタイルバリアントで設定。

npx create-next-app@latest ftchvs_26 --typescript --tailwind --app
npx shadcn@latest init

フェーズ2: コアレイアウト

Geistフォントとレスポンシブナビゲーションでルートレイアウトを構築。ナビゲーションにはデスクトップメニューとshadcn/uiのモバイルシート(スライドアウトドロワー)が含まれます。

ターミナルロゴ(>_)はライトモードとダークモードに適応するシマーアニメーションを使用。

フェーズ3: コンテンツシステム

MDX読み込みを処理するlib/content.tsを作成:

  • getAllPosts() — 日付順にソートされた全ブログ記事を取得
  • getPostBySlug() — シリアライズされたMDXで単一記事を取得
  • getAllProjects() — 全プロジェクトを取得
  • getProjectBySlug() — シリアライズされたMDXで単一プロジェクトを取得
  • getPageBySlug() — Aboutなどの静的ページを取得

各関数はファイルシステムから読み取り、gray-matterでフロントマターを解析し、next-mdx-remoteでコンテンツをシリアライズ。

フェーズ4: ページ

6つのメインルートを構築:

  • Home — 最近の記事リスト付きイントロダクション
  • About — MDXから読み込まれる職業経歴
  • Blog — 記事一覧と個別記事ページ
  • Projects — プロジェクトショーケース(このページ)
  • Resume — 職務経歴とスキル
  • Contact — SendGrid連携フォーム

フェーズ5: インテグレーション

SendGrid: コンタクトフォームからメールを送信するAPIルートを設定。送信者はSendGridの認証設定で検証が必要。

Google Analytics 4: @next/third-parties経由で追加し、NEXT_PUBLIC_GA_IDに基づく条件付き読み込み。

フェーズ6: デザインシステム

globals.cssにタイポグラフィデザイントークンを実装。ライトモードとダークモード用のOKLCHカラー変数を追加。CSSカスタムプロパティでシマーアニメーションを作成。

フェーズ7: SEO

すべてのページにメタデータを追加。サイトマップとrobots.txtジェネレーターを作成。Person、WebSite、Article、BreadcrumbListスキーマ用のJSON-LD構造化データを実装。

フェーズ8: デプロイ

GitHubにプッシュしてVercelにインポート。環境変数を設定:

  • NEXT_PUBLIC_SITE_URL — Canonical URL
  • NEXT_PUBLIC_GA_ID — Google Analytics
  • SENDGRID_API_KEY — メール配信
  • CONTACT_EMAIL_TO — 受信者アドレス
  • CONTACT_EMAIL_FROM — 検証済み送信者
Vercel Deployment
Vercelデプロイ詳細

変更履歴

Gitコミット履歴から抽出した開発の詳細タイムライン。新しい順。

開発統計

  • 総コミット数: 51
  • 開発期間: 約2日
  • 主要機能: 8
  • バグ修正: 12
  • リファクタリング: 15
  • スタイル更新: 16

2025年12月13日 — 2日目: 仕上げと機能追加

夜: 最終調整

CommitDescription
20107f4Major: 包括的なSEO改善
e3ec3c2Aboutページのイントロを簡素化
c21eb7bVoice ID設定を一元化
9311bc6カスタムElevenLabsボイスを設定
f8533bfMajor: APIコスト削減のためElevenLabsを最適化
f8d31cfTTSプレーヤーをサマリーの上に配置
7da002aブログリストのタイポグラフィを更新
872a6ba明確さとストーリーテリングのためブログ記事を改訂

夕方: オーディオ最適化

CommitDescription
fc9cea4refを使用してHTMLを挿入(ハイドレーション修正)
fb9303fAudio Nativeにiframe埋め込みを使用
7384478MDX互換性のためSuspenseラッパーを復元
ff9ca71Major: フルコンテンツプロジェクト付きAudio Native API
76e6e08SSR対応Audio NativeのSuspenseを削除
36c512cuseEffect経由でAudio Nativeを読み込み
5073332パブリックユーザーIDをハードコード
ea4e5c7Audio Native読み込みにnext/scriptを使用
8a5ee20Audio Native統合を簡素化
32fa6caオーディオキャッシュクリアスクリプトを追加
61382caAPI呼び出し削減のためオーディオキャッシュを追加
0ab2855ページ読み込み時にTTS音声を事前生成
d59988fTTSプレーヤーにデバッグログを追加

午後遅く: オーディオ統合スプリント

CommitDescription
d018e8eプレーヤーからElevenLabsフッターを削除
156eda5速度セレクターをミニマリストに
b87be4fプレーヤーからElevenLabsヘッダーを削除
1d6af2fフルオーディオプレーヤーUIを即座に表示
1a90ebfElevenLabs UIコンポーネントでTTSプレーヤーをアップグレード
738ebb4Audio NativeをTTS APIに置き換え
fa6fd42Cursor IDE設定とルールを追加
5152aecReact 19用にMDXRemoteをSuspenseでラップ
c7b54b2Major: ElevenLabs Audio Native TTS統合を追加

午後: タイポグラフィ&履歴書

CommitDescription
2ff75d4ブログ記事の学習記述を更新
0c9b17b履歴書のタグラインを更新
3e6b185スキルと学歴項目にダッシュプレフィックスを追加
3371a03履歴書から「求めるもの」セクションを削除
649010d履歴書のタイポグラフィと配置を改善
f03743eリスト項目、リンク、メタテキストのフォントサイズを更新

午前遅く: コンテンツ戦略

CommitDescription
c5d35e4Aboutページにターゲットオーディエンスを追加
45e61a2「自分でツールを作る」メッセージをトーンダウン
b7a8cd8新しいポジショニングのためREADMEを更新
ae9f3a3全ページでグロースマーケターポジショニングを洗練
701a36f「コードが書けるグロースマーケター」としてサイトを再ポジショニング
db41419プロジェクトページを「Coming Soon」プレースホルダーに置き換え

朝: デザインシステム

CommitDescription
4b7f5ffタイポグラフィトークン、SendGrid、GA4ドキュメントでREADMEを更新
d850b7eナビメニューを並び替え: About, Blog, Projects, Resume, Contact
a17536bMajor: タイポグラフィデザイントークンシステムを実装
a91398fファビコンをターミナルプロンプトアイコン(>_)に更新
c85a8c8ターミナルロゴのフォーカスリングボーダーを削除
af50e11ライト/ダークモード対応のためシマーアニメーションをリファクタリング

2025年12月12日 — 1日目: 基盤構築

CommitDescription
0b4e0a2Major: 強化UIでSendGridコンタクトフォームを統合
5bf8d6aターミナルロゴクリック時のテキスト選択を修正
2ab20b1MDXの静的生成を無効化(React 19互換性修正)
0d1a813ページ、コンポーネント、コア機能を追加
891648cREADMEドキュメントをマージ
08bc5e0Major: ブログとプロジェクト構造付き初期Next.jsポートフォリオサイト
7850991GitHubでリポジトリを初期化
20aed18Create Next Appからの初期コミット

主要マイルストーン:

  • 6ルートの完全なApp Router構造
  • gray-matter解析付きMDXコンテンツシステム
  • shadcn/uiコンポーネントライブラリ統合
  • メール配信付きコンタクトフォーム
  • モバイルドロワー付きレスポンシブナビゲーション
Commit History
GitHubコミット履歴

スクリーンショット

Homepage Light
ホームページ ライトモード
Homepage Dark
ホームページ ダークモード
Mobile Nav
モバイルナビゲーション
Contact Form
コンタクトフォーム
Blog Post
オーディオプレーヤー付きブログ記事

学んだこと

AI支援開発は本物。 CursorとClaudeがボイラープレートを処理し、エラーをキャッチし、一人では見つけられなかったパターンを提案してくれました。ボトルネックはタイピングから思考へとシフト。

TypeScriptは早期に効果を発揮。 型エラーがブラウザに届く前にバグをキャッチ。初期セットアップ時間が何時間ものデバッグを節約。

デザイントークンはスケールする。 タイポグラフィクラスを一度定義すれば、常に判断せずとも一貫したスタイリングが可能。変更は自動的に伝播。

MDXは柔軟。 フロントマタースキーマがUIを駆動。新しいフィールドの追加はリファクタリングなしで数分で完了。


次のステップ

  • ケーススタディとしてプロジェクトを追加
  • ブログ記事の検索とフィルタリングを実装
  • ブログ購読者向けRSSフィードを追加
  • コンテンツ更新のためのISR(Incremental Static Regeneration)を検討

Outcomes

  • AI支援開発(Cursor + Claude)でゼロから構築
  • カスタムタイポグラフィデザイントークンシステムを実装
  • ElevenLabs Audio Nativeによるテキスト読み上げを統合
  • SendGridによるコンタクトフォーム機能を設定
  • 包括的なSEO対策でVercelにデプロイ

Links