「記事は書いた。で、Zenn用に書き直して、Qiita用にも書き直して、X用の告知文作って、LinkedIn用に長めに書いて...」
このコピペ地獄、経験ありませんか?
playparkでは、MDX記事を1つ書いたらZenn・Qiitaへの記事転載とX・LinkedIn含むSNS 5媒体への告知配信を自動化するパイプラインをClaude Code Skillsで構築しました。ブログ運用の全体自動化については以前紹介しましたが、今回はその中でも記事転載(cross-post)パイプラインとSNS配信パイプラインの2つの設計を深掘りします。
この記事で学べること
- MDX → Zenn/Qiita変換で「同じ記事の使い回し」を避ける設計
- プラットフォームごとにHow型/Why型で切り口を変える戦略
- 5プラットフォームのSNS告知文を1コマンドで生成する仕組み
- Zernio CLIによる一括予約投稿の設計パターン
前提条件
- Claude Code Skillsの基本を知っていること(Skillsの設計思想参照)
- Zenn/Qiitaへの投稿経験があること
- SNS運用の「めんどくささ」を身をもって知っていること(必須)
パイプライン全体像
5つのスキルが2つのパイプライン — Cross-Post(Zenn/Qiita転載)とSNS Distribution(5媒体告知配信)— として連携し、1記事 → 7プラットフォーム配信を実現します。
blog-cross-post: 「同じ記事のコピペ」問題を解く
なぜ同じ内容をコピペしちゃダメなの?
ZennとQiitaに同じ記事を投稿する。一見ラクだけど、SEO効果が分散します。Googleは重複コンテンツを嫌う。どちらか片方が検索結果から消える可能性がある。
さらに言えば、ZennユーザーとQiitaユーザーでは求めているものが違う。同じ記事を読まされる側の気持ち、考えたことありますか?(私はなかった)
How型 vs Why型の切り分け
blog-cross-postの設計思想は 「コア版 + 深掘り導線」 。公式ブログの完全コピーではなく、その場で学びが完結するコア版を作りつつ、公式への自然な導線を設ける。
しかもZennとQiitaで切り口を変える:
| Zenn | Qiita | |
|---|---|---|
| 読者層 | モダン技術志向、実装重視 | 幅広い技術者、背景理解重視 |
| 切り口 | Deep How + 持論 | Why(技術選定理由) |
| 冒頭 | 課題共感 → 実装+判断理由 → 持論 | 背景 → 課題 → 選択肢 |
| 読後感 | 「深い、しかも使える」 | 「なるほど、そういうことか」 |
たとえば「Claude Code Skillsの設計」という記事なら:
- Zenn版: 「Skills設計パターンと判断基準」→ 実装+なぜその設計にしたか+書き手の持論
- Qiita版: 「なぜSkillsでこう設計するのか」→ 課題→比較検討→判断基準
同じ技術トピックなのに読後感が違う。これが「コピペじゃないcross-post」の設計です。
アーキテクチャ: プラットフォーム知識の分離
以前はblog-cross-postがZenn/Qiita両方のテンプレートとSEOルールを内包するモノリシック設計でした。現在はプラットフォーム固有の知識を各publishスキルに分離しています:
blog-cross-post(共通ロジックのみ)
├── cross-post-strategy.md ← 差別化原則、コア版設計
├── MDXコンポーネント変換ルール
└── canonical URL対策
zenn-publish/references/
└── content-guide.md ← Zenn専用テンプレート、変換ルール、SEO
qiita-publish/references/
└── content-guide.md ← Qiita専用テンプレート、変換ルール、SEO
新しいプラットフォームを追加するとき、blog-cross-post本体を触る必要がない。Open/Closedの原則がスキル設計にも効いてます。
変換で何をやっているか
元記事(MDX)
├── frontmatter変換(Zenn: emoji/topics、Qiita: tags/private)
├── コンポーネント変換(Mermaid、InteractiveDemo等)
├── 画像パス → 絶対URL化
├── 内部リンク → 絶対URL化
├── コア部分の抽出 + 深掘り導線の生成
└── canonical URL代替のテキストリンク追加
地味だけど重要なのがcanonical URL対策。ZennもQiitaもcanonical URLのfrontmatterをサポートしていない(ZennはIssue #78で要望あるけど未実装)。だからテキストリンクで「この記事はplaypark Blogからの転載です」と明示する。これを忘れるとSEO事故が起きる。
コア版の設計原則
「続きは公式で」は読者を裏切る行為。blog-cross-postではその場で学びが完結することを最優先に設計しています。
コア版で提供するもの:
├── 問題提起(読者の共感)
├── 解決アプローチの概要
├── 動作するコード例(1-2個)
├── 主要な結論
└── 【ここで記事として完結】
深掘り導線で示すもの:
├── 「さらに詳しく知りたい方へ」
│ ├── 複数パターンの比較
│ ├── 実運用での知見・失敗談
│ └── 応用・発展的な内容
└── 具体的に何が読めるかを明示
Zenn版は1,500〜2,500字、Qiita版は1,200〜2,000字が目安。公式が5,000字超の記事でも、コアだけ抜き出せば収まります。
cross-post-publish: オーケストレーターの設計
cross-post-publishは「記事選択 → 変換 → 投稿」を一気通貫で実行するオーケストレーター。途中で止まらない設計です。
Phase 1: 記事選択(ユーザー入力はここだけ)
↓ 止まらない
Phase 2: blog-cross-postで変換(Zenn版 + Qiita版)
↓ 止まらない
Phase 3: zenn-publish + qiita-publishを並列実行
対象カテゴリのフィルタリング
全記事をcross-postするわけじゃない。tech-tipsとlab-reportsだけが対象。
| カテゴリ | Cross-post | 理由 |
|---|---|---|
| tech-tips | 対象 | エンジニア向け、Zenn/Qiita適性が高い |
| lab-reports | 対象 | 技術実験、Zenn読者に刺さる |
| solutions | 対象外 | ビジネス層向け、プラットフォーム不適 |
| case-studies | 対象外 | ビジネス層向け、プラットフォーム不適 |
「せっかく書いた事例記事もcross-postしたい」という気持ちはわかる。でもZennに「シフト管理自動化事例」を投稿しても誰も読まない。(悲しいけど事実)
list-articles.shの賢さ
記事選択時、単にファイルを並べるだけじゃない:
# 直近10件の対象記事を取得
bash ~/.claude/skills/cross-post-publish/scripts/list-articles.sh --recent 10
このスクリプトが3つのフィルタを自動適用:
- カテゴリフィルタ: tech-tips/lab-reportsのみ
- 日付フィルタ: 公開日が今日以前(未来記事を除外)
- 既存チェック:
post/cross-post/<slug>/が存在する記事を除外
3番目が地味に便利。「あれ、この記事もうcross-postしたっけ?」を防いでくれる。(記憶力に自信がない人向け。つまり私向け)
sns-announce: SNS 5媒体の告知文生成
ここまではcross-post(記事転載)の話。ここからはもう1つのパイプライン — SNS配信の本丸です。
プラットフォームごとの「違い」が面倒すぎる
| プラットフォーム | 文字数上限 | スタイル |
|---|---|---|
| X | 実質120字(日本語) | Hook + URL + ハッシュタグ2-3個 |
| 1,300字 | Professional、箇条書き、ハッシュタグ4-6個 | |
| Google Business | 1,500字 | ハッシュタグ非対応、CTA重視 |
| 500字推奨 | カジュアル、シェア促進 | |
| Bluesky | 300字 | X風だがよりカジュアル |
Xで120字に収めた文章をLinkedInにコピペしたらスカスカ。LinkedInの文章をXに貼ったら文字数オーバー。手動でやると1記事あたり30分は溶ける。(その30分でコーヒー2杯飲める)
sns-announceの仕組み
# MDXファイルから全プラットフォームの告知文を一括生成
/sns-announce content/blog/2026-04-23-cross-post-pipeline-design.mdx
内部の処理フロー:
1. load-config.sh → 設定読み込み(プラットフォーム有効/無効、文字数制限)
2. extract-metadata.sh → MDXからタイトル・description・タグ・URLを抽出
3. get-posting-time.sh → プラットフォーム別の最適投稿時間を取得
4. AI生成 → 各プラットフォーム向けに最適化された告知文
5. 出力 → JSON(Zernio API形式)or Markdown
最適投稿時間の自動計算
各プラットフォームに「いつ投稿すべきか」のデータが組み込まれています:
{
"x": {
"weekday": ["07:30", "12:00", "19:00"],
"weekend": ["12:00", "14:00", "16:00"]
},
"linkedin": {
"weekday": ["08:30", "12:00"],
"best_days": ["tue", "wed", "thu"]
}
}
Xは朝・昼・夜の3回が狙い目。LinkedInは火〜木の午前中。平日にLinkedInで投稿して、週末にXで投稿みたいな戦略が自動で組める。
dedupeオプション: 二重投稿を防ぐ
# すでにスケジュール済みのプラットフォームをスキップ
/sns-announce content/blog/article.mdx --dedupe
Zernio APIに問い合わせて、すでに予約済みのプラットフォームをスキップ。「あれ、LinkedInにはもう投稿したっけ?」問題を解決します。しかもAIトークンの節約にもなる。不要なプラットフォームの文章を生成しないから。
zernio CLI: 一括予約投稿
なぜZernio CLIなのか
SNS予約投稿サービスはBufferやHootsuiteが有名。でもAPIが公開されているのはZernio(旧Late)だけ($19/月〜)。
自動化パイプラインにとって「UIがリッチ」は価値ゼロ。APIがあるかないかが全て。ターミナルから離れたくない人間にとって、これは譲れない条件です。
以前はTypeScriptで書いたlate-schedule-postスキルでAPI呼び出ししていましたが、現在はzernio CLIに移行。専用CLIツールになったことで、スキル側のメンテが不要になりました。
JSON入力 → 一括予約
sns-announceの出力がそのままzernio CLIの入力になる。フォーマット変換不要。
[
{
"content": "【🔧 cross-postパイプライン設計】\n\n1記事書いたら5プラットフォームに自動拡散...",
"schedule": "2026-04-23 12:00",
"platforms": ["x"]
},
{
"content": "ブログ記事を書いたら、Zenn・Qiita・X・LinkedIn...",
"schedule": "2026-04-23 08:30",
"platforms": ["linkedin"]
}
]
# dry-runで確認してから本番投稿
zernio post --json posts.json --dry-run
# 問題なければ本番
zernio post --json posts.json
X、LinkedIn、Facebook、Google Business、Blueskyに一括予約。1コマンドで5プラットフォーム。手動でやったら何分かかるか...(計算したくない)
プラットフォームのエイリアス設計
zernio CLIのプラットフォーム名と、人間が使いたい名前は微妙に違う:
| 入力 | 実際のプラットフォーム |
|---|---|
x, twitter | X (Twitter) |
google, gbp | Google Business Profile |
bsky | Bluesky |
fb |
「googlebusinessって打つの長い」問題をgoogleやgbpで解決。ちいさな気遣い、でかい効果。
generate-thumbnail: サムネイルも自動生成
パイプラインのおまけ(でも地味に重要)。Gemini APIでMDXのfrontmatterからサムネイルを自動生成します。
# MDXファイルからサムネイル生成 → WebP変換まで一気に
~/.claude/skills/generate-thumbnail/scripts/generate_thumbnail.sh \
content/blog/2026-04-23-cross-post-pipeline-design.mdx --optimize
1枚約$0.13。Canvaを開いて15分かけるか、$0.13で自動生成するか。時給換算すると答えは明白です。
パイプライン全体の設計判断
なぜスキルを分割するのか
cross-post-publishが全部やればいいのでは?と思うかもしれない。でもスキルを分割しておくと組み合わせの自由度が上がる:
| やりたいこと | 使うスキル |
|---|---|
| Zennだけに投稿 | blog-cross-post → zenn-publish |
| SNS告知文だけ欲しい | sns-announce |
| 予約だけやり直したい | zernio CLI |
| サムネだけ再生成 | generate-thumbnail |
「全部入り」のスキルは一見便利だけど、部分的にやり直したいとき詰む。Unix哲学の「一つのことをうまくやれ」は、AIスキルの設計でも生きています。
skill-config.jsonによる3層設定
設定はスクリプト内蔵デフォルト → グローバル → プロジェクトの3層マージ:
スクリプト埋め込みデフォルト
← ~/.claude/skill-config.json (グローバル: 全プロジェクト共通)
← <project>/.claude/skill-config.json (プロジェクト: 最優先)
blog-cross-postの場合:
{
"blog-cross-post": {
"base_url": "https://www.playpark.co.jp",
"content_dir": "content/blog",
"blog_path_prefix": "/blog/",
"cross_post_categories": ["tech-tips", "lab-reports"]
}
}
プロジェクトごとにbase_urlやカテゴリを変えられる。OSS開発と自社ブログで設定を分けたいとき、グローバル設定とプロジェクト設定のマージが効く。
注意点・Tips
canonical URL対策は絶対に忘れない
ZennもQiitaもcanonical URLをfrontmatterでサポートしていません。テキストリンクでの帰属表示が唯一の手段。これを省略すると、Googleが元記事とcross-post先のどちらを正規と判断するか分からなくなる。
cross-post対象カテゴリは絞る
「せっかくだから全記事cross-postしたい」は罠。ビジネス層向けの事例記事をZennに投稿しても、読者にとってノイズでしかない。プラットフォームの読者層を尊重すること。
dry-runを習慣にする
zernio CLIでの予約投稿は取り消しが面倒。--dry-runで内容を確認してから本番投稿する癖をつけましょう。一度投稿した「恥ずかしいtypo入りの告知文」は消せても、フォロワーの記憶からは消えない。
まとめ
1記事書いたら7プラットフォームに自動配信する仕組み。中身を分解すると、Cross-Postパイプライン(blog-cross-post → zenn/qiita-publish でZenn=How型/Qiita=Why型に変換して転載)と、SNS Distributionパイプライン(sns-announce → zernio CLIで5媒体に告知配信)の2本立てです。
手動でやると1記事あたり1時間以上かかる転載 + SNS配信が、パイプライン化すると数分。しかも各プラットフォームの特性に最適化された内容で配信される。
「記事を書く」という本質的な作業に集中して、配信はパイプラインに任せる。これが私たちの結論です。



