Portfolio logo
🎸

バイブコーディングでポートフォリオサイトとブログを作った話

AI エディタを使った直感的な開発スタイル「バイブコーディング」で、ポートフォリオサイトとブログを構築した実践レポート。Astro + Cloudflare Pages で実現する高速な開発体験。

#astro #vibe-coding #github-copilot #cloudflare

バイブコーディングとは

バイブコーディング(Vibe Coding) とは、GitHub Copilot などの AI アシスタントを使い、自然言語での指示とコード生成を組み合わせながら、直感的に・テンポよく・流れるようにコーディングする開発スタイルです。

従来の「一行ずつ手打ち」ではなく、AI との対話を通じて大まかな構造やロジックを瞬時に具現化し、細部を調整していく感覚。まるでコードと会話しているようなリズミカルな開発体験が特徴です。

主な特徴

  • 自然言語での指示: 「ヘッダーコンポーネントを作って」「ダークモード対応して」など
  • 高速なフィードバックループ: AI が即座に提案 → 確認 → 修正 → 次のステップ
  • コンテキスト理解: プロジェクト全体を把握した上でのコード生成
  • ドキュメント参照: 最新の公式ドキュメントを踏まえた実装

このサイトの技術スタック

今回作成したポートフォリオサイト(flowzenn.com)は以下の構成です:

  • フレームワーク: Astro 5.14.1(SSRモード with Cloudflare Adapter)
  • スタイリング: Tailwind CSS 4.1.13(Vite plugin)
  • デプロイ: Cloudflare Pages
  • コンテンツ管理: Astro Content Collections
  • シンタックスハイライト: Shiki(github-light/dark テーマ)
  • アナリティクス: Google Analytics(Partytown 統合)
  • 開発ツール: GitHub Copilot

バイブコーディングで実装した機能

1. ブログ機能(Content Collections)

最初に作ったのがブログ機能。Astro の Content Collections を使って Markdown で記事を管理します。

AI への指示例

「Astro Content Collections でブログ機能を実装して。記事一覧ページと個別記事ページが必要。タグと説明文も追加したい」

AI が即座に以下を生成:

// src/content/config.ts
import { defineCollection, z } from "astro:content"

const notesCollection = defineCollection({
  type: "content",
  schema: z.object({
    title: z.string(),
    emoji: z.string().optional(),
    tags: z.array(z.string()).optional(),
    published: z.boolean().default(true),
    publishedAt: z.coerce.date(),
    updatedAt: z.coerce.date().optional(),
    description: z.string().optional()
  })
})

export const collections = {
  notes: notesCollection
}

実装のポイント

  • Zod によるスキーマ定義で型安全性を確保
  • published フラグ(デフォルト true)で公開管理
  • emoji フィールドで記事にアイコンを追加
  • tags で記事の分類(複数タグ対応)
  • description で記事概要を表示
  • updatedAt で更新日時も管理可能

2. Markdown統合のシンタックスハイライト

技術ブログには欠かせないコードブロック。Astro の Markdown 統合と Shiki を組み合わせて実装しました。

AI への指示例

「Markdown のコードブロックにダークモード対応のシンタックスハイライトを適用して。GitHub風のテーマで」

生成された astro.config.mjs の設定:

// astro.config.mjs
export default defineConfig({
  output: "server",
  adapter: cloudflare({
    platformProxy: { enabled: true },
    imageService: "cloudflare"
  }),
  markdown: {
    shikiConfig: {
      themes: {
        light: "github-light",
        dark: "github-dark"
      },
      defaultColor: false
    }
  }
})

さらに、Tailwind CSS の prose スタイルでコードブロックを整形:

/* グローバルスタイル */
.prose pre {
  position: relative;
  margin: 1.25rem 0;
  padding: 0.875rem !important;
  overflow-x: auto;
  border-radius: 0.375rem;
  border: 1px solid rgb(228 228 231);
  background-color: rgb(250 250 250);
}

.dark .prose pre {
  border-color: rgb(39 39 42);
  background-color: rgb(32 32 32);
}

実装のポイント

  • Astro の Markdown 統合で自動的にシンタックスハイライトを適用
  • ライト/ダークテーマの自動切り替え(CSS変数ベース)
  • defaultColor: false で明示的なテーマ切り替え
  • Tailwind prose クラスで統一的なスタイリング
  • カスタムコンポーネント不要のシンプルな実装

3. ミニマルなレイアウト

AI への指示例

「シンプルでミニマルなヘッダーを作って。ロゴだけでナビゲーションは不要」

Tailwind CSS のユーティリティクラスを活用した実装が即座に生成されました:

<!-- src/components/Header.astro -->
<header class="h-16 mx-auto max-w-2xl px-6 flex items-center justify-between text-sm text-zinc-700 dark:text-zinc-300">
  <a href="/" class="flex items-center gap-2 hover:opacity-90">
    <img src="/favicon.svg" alt="Portfolio logo" class="h-6 w-6" />
  </a>
</header>

ヒーローセクションもシンプルでスタイリッシュに:

<!-- src/components/Hero.astro -->
<section class="mx-auto max-w-2xl px-6 pt-16 sm:pt-24 pb-10">
  <div class="flex items-end gap-3">
    <h1 class="text-[40px] sm:text-[48px] leading-none font-medium italic text-zinc-800 dark:text-zinc-100">
      Ryo
    </h1>
  </div>
  <div class="mt-1 h-[6px] w-28 sm:w-32">
    <svg viewBox="0 0 160 12" class="w-full h-full">
      <path d="M2 10 Q35 2 70 9 T158 7" fill="none" stroke="currentColor" 
        stroke-width="2" class="text-zinc-700/40 dark:text-zinc-300/40" />
    </svg>
  </div>
  <p class="mt-8 text-[15px] sm:text-base leading-7 text-zinc-700 dark:text-zinc-300">
    I'm ryo, a full‑stack engineer in Japan. I build small, focused web apps and keep shipping.
  </p>
</section>

デザインのポイント

  • 最大幅 max-w-2xl で読みやすさを重視
  • text-zinc-* カラースキームで落ち着いた印象
  • イタリック体の大きな見出しでインパクトを演出
  • SVG による手書き風のアンダーライン
  • レスポンシブなフォントサイズとスペーシング

4. 記事一覧ページ

公開済み記事をソートして表示する一覧ページ。タグや説明文も表示します:

---
// src/pages/notes/index.astro
import { getCollection } from "astro:content"
import Layout from "../../layouts/Layout.astro"

const allNotes = await getCollection("notes")
const publishedNotes = allNotes
  .filter((note) => note.data.published)
  .sort((a, b) => b.data.publishedAt.getTime() - a.data.publishedAt.getTime())

const pageTitle = "Notes | Portfolio"
const pageDescription = "技術や開発に関する記事を投稿しています"
---

<Layout title={pageTitle} description={pageDescription}>
  <div class="mx-auto max-w-2xl px-6 py-12 sm:py-16">
    <div class="mb-10">
      <h1 class="text-xl font-semibold tracking-tight text-zinc-800 dark:text-zinc-100">
        Notes
      </h1>
      <p class="mt-3 text-sm sm:text-base text-zinc-600 dark:text-zinc-300 leading-relaxed">
        技術や開発に関する記事を投稿しています
      </p>
    </div>

    <div class="grid gap-4">
      {publishedNotes.map((note) => {
        const formattedDate = note.data.publishedAt.toLocaleDateString("ja-JP", {
          year: "numeric", month: "long", day: "numeric"
        })
        
        return (
          <article class="group">
            <a 
              href={`/notes/${note.slug}`}
              class="block p-4 rounded-md border border-zinc-200 dark:border-zinc-800 
                hover:border-zinc-300 dark:hover:border-zinc-700 transition-colors"
            >
              <div class="flex items-start gap-3">
                {note.data.emoji && (
                  <div class="text-2xl flex-shrink-0">{note.data.emoji}</div>
                )}
                <div class="flex-1 min-w-0">
                  <h2 class="text-base font-medium mb-1 text-zinc-800 dark:text-zinc-200">
                    {note.data.title}
                  </h2>
                  
                  {note.data.description && (
                    <p class="text-[13px] text-zinc-600 dark:text-zinc-400 mb-2 line-clamp-2">
                      {note.data.description}
                    </p>
                  )}
                  
                  <div class="flex flex-wrap items-center gap-x-3 gap-y-1.5">
                    <time class="text-[11px] sm:text-xs text-zinc-500">
                      {formattedDate}
                    </time>
                    
                    {note.data.tags && note.data.tags.length > 0 && (
                      <div class="flex flex-wrap gap-1.5">
                        {note.data.tags.map((tag) => (
                          <span class="text-[10px] sm:text-[11px] px-1.5 py-0.5 rounded 
                            border text-zinc-600 dark:text-zinc-400 border-zinc-200 dark:border-zinc-800">
                            #{tag}
                          </span>
                        ))}
                      </div>
                    )}
                  </div>
                </div>
              </div>
            </a>
          </article>
        )
      })}
    </div>
  </div>
</Layout>

実装のポイント

  • カード型のレイアウトで記事を表示
  • line-clamp-2 で説明文を2行に制限
  • flex-wrap でタグを折り返し表示
  • group-hover でホバー時のインタラクション
  • 日本語フォーマットの日付表示

バイブコーディングの実践例

実際の開発フローを紹介します。

ステップ 1: 大枠の生成

👤 「Astro でポートフォリオサイトを作りたい。ヘッダー、ヒーロー、About セクション、フッターを含むトップページを作って」

🤖 [Layout.astro, Header.astro, Hero.astro, About.astro, Footer.astro を生成]

AI が数秒で基本構造を生成。各コンポーネントが適切に分離されています。

ステップ 2: 細部の調整

👤 「ヒーローセクションにグラデーション背景を追加して、もっと目を引くデザインにして」

🤖 [Tailwind のグラデーション設定とアニメーション付きの Hero コンポーネントに更新]

デザインの微調整も自然言語で指示するだけ。

ステップ 3: 機能追加

👤 「記事詳細ページに目次(TOC)を追加したい」

🤖 [Markdown の見出しから自動生成される目次コンポーネントを実装]

必要な機能を思いついたらすぐに追加できるスピード感。

ステップ 4: 最適化

👤 「Cloudflare Pages へのデプロイ設定を教えて。wrangler の設定も含めて」

🤖 [astro.config.mjs と wrangler.jsonc の適切な設定を提示]

インフラ設定も AI がサポート。

バイブコーディングのメリット

1. 圧倒的なスピード

従来なら数時間かかる実装が、数十分で形になる。特に:

  • ボイラープレートコードの生成
  • 複数ファイルにまたがるリファクタリング
  • 設定ファイルの作成

2. 学習曲線の緩和

新しいフレームワーク(今回は Astro)でも、AI がベストプラクティスを提示してくれるため、ドキュメントを読み込む時間を削減できます。

3. 品質の向上

AI は:

  • 型安全性を考慮したコード
  • アクセシビリティ対応
  • レスポンシブデザイン
  • セマンティック HTML

などを自動的に適用します。

4. 反復作業からの解放

似たようなコンポーネントを複数作る際、パターンを学習した AI が一貫性のあるコードを生成してくれます。

バイブコーディングの課題と対策

課題 1: 生成コードの理解

対策: 生成されたコードは必ず確認し、理解してから使う。不明点は AI に質問。

課題 2: プロジェクト固有のルール

対策: .github/instructions/ でプロジェクトの規約を明示。今回は:

  • Biome でフォーマット統一
  • Tailwind CSS のクラス順序
  • ファイル命名規則(kebab-case.astro

課題 3: 過度な依存

対策: 基礎知識は自分で持つ。AI は「加速装置」として使う。

実際の開発時間

従来の手法(想定): 2-3 日 バイブコーディング: 約 4 時間

内訳:

  • 基本構造: 30 分
  • ブログ機能: 1 時間
  • デザイン調整: 1.5 時間
  • デプロイ設定: 30 分
  • 細かい修正: 30 分

GitHub Copilot の活用

プロジェクト固有のルールは /.github/instructions/portfolio.instructions.md で管理:

## Technology Stack
- Framework: Astro 5.14.1 (SSR mode with Cloudflare Adapter)
- Styling: Tailwind CSS 4.1.13
- Content Management: Astro Content Collections
- Syntax Highlighting: Shiki
- Deployment: Cloudflare Pages

## Code Standards
- Formatting: Biome
- File naming: kebab-case.astro
- Component naming: PascalCase

## Architecture
- Component-based design
- Content Collections for blog posts
- Minimal client-side JavaScript

これにより、GitHub Copilot は一貫したコードスタイルを維持し、プロジェクト固有のベストプラクティスに沿った提案をしてくれます。

5. SEO対応とアナリティクス

AI への指示例

「SEO対応を強化して。OGP、構造化データ、sitemap、Google Analyticsも統合したい」

生成されたレイアウトコンポーネントの一部:

---
// src/layouts/Layout.astro
const domain = "flowzenn.com"
const protocol = "https"
const canonicalURL = new URL(Astro.url.pathname, `${protocol}://${domain}`).toString()

const structuredData = {
  "@context": "https://schema.org",
  "@type": "Person",
  name: "Developer",
  url: `${protocol}://${domain}`,
  description: description
}
---

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width" />
  <link rel="canonical" href={canonicalURL} />
  
  <!-- OGP -->
  <meta property="og:title" content={title} />
  <meta property="og:description" content={description} />
  <meta property="og:type" content={type} />
  
  <!-- Structured Data -->
  <script type="application/ld+json" set:html={JSON.stringify(structuredData)} />
  
  <!-- Google Analytics (Partytown) -->
  {GA_ID && (
    <script type="text/partytown" src={`https://www.googletagmanager.com/gtag/js?id=${GA_ID}`} />
  )}
</head>

SEO機能

  • カノニカルURL設定
  • OGP メタタグ(記事タイプ対応)
  • 構造化データ(JSON-LD)
  • robots.txt と sitemap.xml
  • Partytown によるパフォーマンス最適化

Cloudflare へのデプロイ

Astro の Cloudflare Adapter を使った SSR 設定:

// astro.config.mjs
import cloudflare from "@astrojs/cloudflare"
import partytown from "@astrojs/partytown"
import tailwindcss from "@tailwindcss/vite"
import { defineConfig } from "astro/config"

export default defineConfig({
  output: "server",
  adapter: cloudflare({
    platformProxy: { enabled: true },
    imageService: "cloudflare"
  }),
  vite: {
    plugins: [tailwindcss()]
  },
  integrations: [partytown()]
})
// wrangler.jsonc
{
  "name": "portfolio-frontend-prd",
  "main": "./dist/_worker.js/index.js",
  "compatibility_date": "2025-09-27",
  "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
  "assets": {
    "binding": "ASSETS",
    "directory": "./dist"
  },
  "route": "flowzenn.com/*",
  "vars": { 
    "PUBLIC_GA_ID": "G-XXX",
    "PUBLIC_GOOGLE_ADS_ID": "ca-pub-XXX"
  }
}

デプロイコマンド一つで本番環境へ:

pnpm deploy

デプロイの特徴

  • Cloudflare Workers 上で SSR 実行
  • エッジでの画像最適化(Cloudflare Image Service)
  • 環境変数の安全な管理
  • グローバルな CDN 配信

まとめ

バイブコーディングは、AI を相棒として開発する新しい体験です。

このプロジェクトで使った技術

カテゴリ技術バージョン
フレームワークAstro5.14.1
スタイリングTailwind CSS4.1.13
コンテンツContent Collections-
シンタックスハイライトShiki3.13.0
デプロイCloudflare Pages-
アダプター@astrojs/cloudflare12.6.9
アナリティクスPartytown + GA2.1.4

バイブコーディングが向いている場面

  • ✅ プロトタイプ開発
  • ✅ 新しいフレームワークの習得
  • ✅ 定型的な実装
  • ✅ ボイラープレートの生成
  • ✅ コンポーネントベースのUI開発
  • ✅ 設定ファイルの生成

バイブコーディングが向いていない場面

  • ❌ 複雑なビジネスロジック(要人間の判断)
  • ❌ パフォーマンス最適化(要計測と分析)
  • ❌ セキュリティが重要な部分(要専門知識)
  • ❌ 独自性の高いアルゴリズム設計

開発時間の比較

従来の手法(想定): 2-3 日 バイブコーディング: 約 4-5 時間

内訳:

  • プロジェクト設定: 30 分
  • 基本レイアウト: 1 時間
  • Content Collections 設定: 30 分
  • ブログページ実装: 1 時間
  • SEO・アナリティクス: 1 時間
  • デザイン調整: 1 時間

結論: バイブコーディングは開発を劇的に加速させるが、基礎知識と判断力は依然として必須。AI は強力なツールだが、最終的な責任は開発者にあります。

このポートフォリオサイト自体が、バイブコーディングの成果物です。ぜひ flowzenn.com で実際の動作を確認してみてください!


プロジェクト構成

実際のディレクトリ構造:

services/portfolio/frontend/
├── src/
│   ├── components/        # 再利用可能なコンポーネント
│   │   ├── Header.astro
│   │   ├── Hero.astro
│   │   ├── About.astro
│   │   ├── Footer.astro
│   │   └── Changelog.astro
│   ├── content/          # Content Collections
│   │   ├── config.ts     # スキーマ定義
│   │   └── notes/        # ブログ記事(Markdown)
│   ├── layouts/          # ページレイアウト
│   │   └── Layout.astro
│   ├── pages/            # ルーティング
│   │   ├── index.astro   # トップページ
│   │   ├── privacy.astro
│   │   ├── sitemap.xml.ts
│   │   └── notes/
│   │       ├── index.astro   # 記事一覧
│   │       └── [slug].astro  # 記事詳細
│   └── styles/
│       └── global.css
├── public/
│   ├── favicon.svg
│   └── robots.txt
├── astro.config.mjs
├── wrangler.jsonc
└── package.json

参考リンク: