DEV Community

kanta13jp1
kanta13jp1

Posted on

Claude Code Schedule Supabase Edge Functions で技術ブログを毎日自動生成・Qiita/dev.to に自動投稿する仕組みを作った

ブログ下書き 2026-04-10

タイトル案

  1. 「Claude Code Schedule × Supabase Edge Functions で技術ブログを毎日自動生成・Qiita/dev.to に自動投稿する仕組みを作った」
  2. 「月$20の Claude Code で $200 相当の開発を自動化 — ブログ投稿・CS対応・競合監視まで全部 AI に任せた話」
  3. 「Flutter Web アプリの LP を44→52機能に拡張しながら、技術ブログも自動でクロスポストできるようになった話」

投稿先候補

  • [x] Zenn
  • [x] Qiita
  • [x] note
  • [x] はてなブログ
  • [x] X Article
  • [x] Medium
  • [x] dev.to
  • [x] Hashnode
  • [x] Substack
  • [x] GitHub Pages
  • [x] NOTION

本文下書き(約2500字)

はじめに

「コードを書くだけで手一杯で、技術ブログを書く時間がない」

これはほぼすべての個人開発者が抱える悩みです。特に一人で21競合SaaSを超えるアプリを開発している場合、ブログどころか休む時間もありません。

私が開発している 自分株式会社https://my-web-app-b67f4.web.app/)は、Notion・Evernote・MoneyForward・Slack・Amazon など21競合を1つに統合する Flutter Web + Supabase のAIライフマネジメントアプリです。今週、このアプリに「技術ブログ自動生成・自動クロスポスト」システムを実装し、Qiita と dev.to への初投稿に成功しました。

本記事では、Claude Code Schedule + Supabase Edge Functions で構築したブログ自動化の全体アーキテクチャと、実装の詳細を共有します。


実装した全体アーキテクチャ

Claude Code Schedule (毎日 08:00 JST)
  ↓ git log で直近7日のコミット確認
  ↓ 開発内容からブログ下書きを AI 生成
  ↓ docs/blog-drafts/YYYY-MM-DD.md に保存
  ↓ Zenn / GitHub Pages / クロスポスト版も生成
  ↓
Supabase REST API (blog_posts テーブル)
  ↓ draft として登録
  ↓
blog-auto-publisher Edge Function (Deno)
  ↓ Qiita API → 投稿 → URL 取得
  ↓ dev.to API → 投稿 → URL 取得
  ↓ blog_posts.status = "posted" に更新
  ↓
git commit & push → GitHub Actions → Firebase Hosting 自動デプロイ
Enter fullscreen mode Exit fullscreen mode

すべて 自律的に動作します。私がやることは「コードを書くこと」だけ。


Step 1: blog-draft スケジュールタスク(Claude Code Schedule)

Claude Code Schedule の blog-draft タスクは毎日 08:00 JST に実行されます。

git log --oneline --since="7 days ago"
Enter fullscreen mode Exit fullscreen mode

コミットが0件なら処理終了。開発活動があった日だけブログが生成されます。

Claude は git log の結果から最も技術的に面白いトピックを選び、以下の構成で Markdown を生成します:

## タイトル案(3案)
## 投稿先候補(11プラットフォーム)
## 本文(はじめに / 実装方法 / 詰まったポイント / まとめ)
Enter fullscreen mode Exit fullscreen mode

Step 2: blog-post-manager Edge Function で Supabase に登録

生成した下書きを blog_posts テーブルに登録します。

POST https://smmkxxavexumewbfaqpy.supabase.co/rest/v1/blog_posts
{
  "title": "タイトル",
  "draft_path": "docs/blog-drafts/2026-04-10.md",
  "status": "draft",
  "target_platforms": ["qiita", "devto", "zenn", ...]
}
Enter fullscreen mode Exit fullscreen mode

blog_posts テーブルのスキーマ:

id uuid PRIMARY KEY,
title text NOT NULL,
draft_path text,
status text DEFAULT 'draft',  -- draft / posted / skipped
target_platforms text[],
posted_at timestamptz,
url text,
content_preview text,
created_at timestamptz DEFAULT now()
Enter fullscreen mode Exit fullscreen mode

Step 3: blog-auto-publisher Edge Function(本命)

今週最も力を入れた実装が、この blog-auto-publisher Edge Function です。

Qiita API 投稿実装

async function publishToQiita(
  title: string,
  body: string,
  tags: string[],
): Promise<{ url: string }> {
  const tagObjects = tags.slice(0, 5).map((t) => ({ name: t.slice(0, 20) }));
  const res = await fetch("https://qiita.com/api/v2/items", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${QIITA_ACCESS_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title,
      body,
      tags: tagObjects,
      private: false,
    }),
  });
  const data = await res.json();
  return { url: data.url };
}
Enter fullscreen mode Exit fullscreen mode

dev.to API 投稿実装

async function publishToDevto(
  title: string,
  body: string,
  tags: string[],
): Promise<{ url: string }> {
  const res = await fetch("https://dev.to/api/articles", {
    method: "POST",
    headers: {
      "api-key": DEVTO_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      article: {
        title,
        body_markdown: body,
        tags: tags.slice(0, 4),
        published: true,
      },
    }),
  });
  const data = await res.json();
  return { url: data.url };
}
Enter fullscreen mode Exit fullscreen mode

auto_publish アクションで一括投稿

case "auto_publish": {
  const results: Record<string, unknown> = {};
  for (const platform of post.target_platforms ?? []) {
    if (platform === "qiita") {
      results.qiita = await publishToQiita(post.title, body, tags);
    } else if (platform === "devto") {
      results.devto = await publishToDevto(post.title, body, tags);
    }
  }
  const anySuccess = Object.values(results).some((r: unknown) => !(r as Record<string,string>).error);
  if (anySuccess) {
    await supabase.from("blog_posts")
      .update({ status: "posted", posted_at: new Date().toISOString() })
      .eq("id", id);
  }
  return json({ results });
}
Enter fullscreen mode Exit fullscreen mode

詰まったポイント

1. Qiita のタグ制約

Qiita は1記事につき最大5タグ、各タグ名は20文字以内。tags.slice(0, 5).map(t => ({ name: t.slice(0, 20) })) で対応。

2. blog_posts テーブルに content_preview カラムがなかった

CLAUDE.md のスキーマに記載はあったが実テーブルには存在せず。マイグレーション追加:

ALTER TABLE blog_posts ADD COLUMN IF NOT EXISTS content_preview text;
Enter fullscreen mode Exit fullscreen mode

3. GITHUB_TOKEN はブランチ保護をバイパスできない

GitHub Actions から直接 git push する際、ブランチ保護ルールで弾かれることが判明。最終的に「直接 push で問題ないブランチのみ CI からコミット」という方針に。


実際の投稿成果


まとめ

Claude Code Schedule × Supabase Edge Functions で 技術ブログの生成から投稿まで完全自動化 できることを実証しました。

現時点での対応状況:

プラットフォーム 状態
Qiita 実装済み
dev.to 実装済み
Zenn 半自動(GitHub連携)
note / はてな / Medium 等 未実装(今後対応予定)

自分株式会社は「21競合SaaS を1つに」を目指して開発を続けています。

サービス URL: https://my-web-app-b67f4.web.app/


FlutterWeb #Supabase #ClaudeCode #buildinpublic #個人開発

Top comments (0)