はじめに
Zennの記事をブラウザのエディタではなく、手元のエディタとGitで書きたいと考えました。GitHubリポジトリと連携すると、pushするだけでZennに反映されます。
本記事のまとめです。
- 連携はZennのダッシュボードからGitHub Appを入れるだけで終わる。リポジトリ側に設定ファイルを置く必要はない
- 連携できるリポジトリは最大2つ。GitHub Appの画面は初期状態で全リポジトリを選んでいるので、必ず切り替える
- 公開済みの記事は、slugを変えなければ上書きになる。URL・いいね数・公開日はそのまま残る
- 捨て記事でデプロイ経路を確かめてから、公開済みの記事を取り込む。順番を逆にすると公開中の記事を壊しうる
- 私がつまずいたのは2箇所で、GitHub Appの選択範囲とGitHub側だけ入ってZennが未連携のまま残ったことだった
公開中の記事1本を、URLといいね数を保ったままGit管理下へ移すまでに確かめたことを書きます。

連携の全体像と、先に知っておく制約
作業はGitHub側・Zenn側・手元の3つに分かれます。それぞれで何をするかは次のとおりです。
- GitHub側: 記事を置くリポジトリを1つ作る
- Zenn側: ダッシュボードからGitHub Appを入れ、そのリポジトリを連携する
- 手元: リポジトリをcloneし、zenn-cliを入れて記事を書く
私はリポジトリをTerraformで作りましたが、この記事では触れません。GitHubの画面から作っても結果は同じです。
手を動かす前に、ZennとGitHubリポジトリの連携に書かれている制約を4つ確かめておきます。
| 制約 | 影響 |
| 連携できるリポジトリは最大2つ | 3つ以上を選ぶと連携そのものが失敗する |
articles/ と books/ はリポジトリのルートに置く | 既存リポジトリのサブディレクトリに同居させられない。専用リポジトリが要る |
| 投稿の同一性はslugで決まる | slugが同じなら上書き、変えると別記事が増える |
| 投稿の削除はダッシュボードからのみ | GitHub側でファイルを消しても次回デプロイで復活する |
slugは記事のURLの末尾に入る文字列で、12〜50字、使える文字はa-z0-9とハイフン・アンダースコアだけという決まりがあります。ブラウザのエディタで書いた記事には自動でslugが振られますが、その自動slugがこの規則を満たしているとは限りません。移行の前に確かめてください。
環境の前提も1つあります。記事を書くためのコマンドラインツールであるzenn-cliは、engines.nodeが>=22.12.0です(2026年11月に確認)。参考記事でよく見かける「Node.js 14以上」はこれより古い情報なので、気をつけてください。
![「GitHubに遷移します」のダイアログ。「[Only select repositories]にチェックを入れ、連携するリポジトリを選んでください。最大2つまで連携できます」と書かれている](https://data-atelier.net/wp-content/uploads/2026/10/zenn-connect-dialog-max-two-repositories.png)
ZennとGitHubを連携する
連携の操作はブラウザだけで終わり、3分もかかりません。ダッシュボードの「GitHub連携」を開き、「リポジトリを連携する」を押すと、GitHub Appのインストール画面に移ります。
「All repositories」のまま進めない
インストール画面は、開いた時点で「All repositories」が選ばれています。そのままInstall & Authorizeを押すと全リポジトリが連携の対象になり、3つ以上あれば上限を超えて連携が失敗します。「Only select repositories」に切り替えて、記事を置くリポジトリだけを選んでください。
切り替えるとSelected 1 repository.と表示され、選んだリポジトリ名が下に出ます。要求される権限はRead access to code, metadata, and pull requestsだけです。

デプロイ対象ブランチを確かめる
連携が済むと、ダッシュボードのタブが「デプロイ履歴」と「リポジトリ設定」に変わります。リポジトリ設定を開いて、どのブランチへのpushでデプロイが走るかを確かめてください。私の環境では次のようになっていました。
| 項目 | 値 |
| 連携状態 | 1つのリポジトリと連携中 |
| デプロイ対象ブランチ | main |
| リポジトリへのリンク | 表示しない |
| プルリクエストで下書きデプロイ | 無効 |
私はmainへのpushで運用しています。プルリクエストで下書きをデプロイする設定もありますが、有効にしていません。
GitHub側は入ったのにZennが未連携のままになる
GitHub Appのインストールが成功しても、Zennのダッシュボードが未連携のままになることがあります。私は1回目の試行でこれを踏みました。ダッシュボードを開いても「リポジトリを連携する」ボタンが出続け、GitHub側ではアプリが入っている、という状態です。
このとき私がGitHub Appを入れたブラウザのプロファイルは、Zennにログインしていませんでした。インストール画面の最下部には、遷移先としてhttps://zenn.dev/api/github_app/install/callbackが書かれています。戻り先がZennなので、ログインしていないブラウザでは誰の連携なのかを記録できなかったのだと考えています。ただし試したのは1回だけで、ログイン状態だけを変えて確かめたわけではないので、これは推測です。
対処は、GitHubの設定からZenn Connectを一度アンインストールし、Zennにログイン済みのブラウザで最初から通し直すことです。アンインストールしてやり直す点は、Zennもダッシュボードの下部で案内しています。私はこれで解決しました。
デプロイが走らないときは、GitHubの Settings → Applications → Installed GitHub Apps を開きます。Zenn ConnectのRepository accessが、目的のリポジトリを含んでいるかを見てください。この項目は目で見るしかありません。GitHub CLIで確かめようとすると、GitHub App用のトークンではないため403が返ります。
You must authenticate with an access token authorized to a GitHub App
zenn-cliを入れて、捨て記事でデプロイ経路を確かめる
連携が済んでも、公開済みの記事をいきなりpushしてはいけません。frontmatterの書き方を1つ間違えるだけで、公開中の記事のタイトルや公開日が変わります。先に捨て記事を1本流して、デプロイが走ることだけを確かめます。
リポジトリをcloneして、zenn-cliを入れます。
git clone https://github.com/<ユーザー名>/zenn-contents.git
cd zenn-contents
npm init --yes
npm install zenn-cli
npx zenn initnpx zenn initがarticles/とbooks/をリポジトリのルートに作ります。この2つはルートに置く決まりなので、サブディレクトリへ寄せないでください。
ここでnpm auditがmoderateを5件返しました。すべてzenn-cliが間接的に依存しているパッケージなので、こちら側では直せません。npm audit fix --forceはzenn-cliを古い版へ戻す変更になるため、実行していません。
捨て記事を1本作り、ローカルで見てからpushします。
npx zenn new:article --slug zenn-github-deploy-smoke-test --title "連携確認" --type tech
npx zenn preview # http://localhost:8000
git add . && git commit -m "Set up zenn-cli and add a deploy smoke test draft"
git push origin mainfrontmatterのpublishedはfalseのままにしておきます。こうしておけばデプロイは走りますが、記事は公開されません。
---
title: "連携確認"
emoji: "🔧"
type: "tech"
topics: []
published: false
---pushしたら、デプロイ履歴に成功が1件増え、投稿管理に下書きバッジ付きで記事が出ることを確かめます。公開されていないことまで見てください。

npx zenn previewで開いたローカルプレビュー。左のサイドバーでarticlesとbooksがルートにあることと、記事が読めることを確認できます。この画面は移行後に撮ったものなので、捨て記事ではなく移行した記事が写っています公開済みの記事をGit管理下に移す
ブラウザのエディタで書いた記事は、markdownで書き出してリポジトリに置き直します。書き出し口は2つあり、移行する本数で使い分けます。
- 1本だけなら記事単位。記事の管理で対象記事の「⌄」を開き、「内容をmarkdownで出力」を選ぶ
- 全部まとめてならアカウント設定。設定画面の最下部にある「データのエクスポート」からzipで落とす
私は1本だけだったので記事単位で取りました。「⌄」のメニューには「削除する」が隣接しているので、押し間違えないでください。
公開APIでは代用できませんでした。記事の情報はログインなしで読めますが、返るのは組版済みのHTMLだけで、markdownの原文は含まれていません。
curl -s "https://zenn.dev/api/articles?username=<ユーザー名>&order=latest"
curl -s "https://zenn.dev/api/articles/<slug>?username=<ユーザー名>"HTMLからmarkdownへ戻すと、リンクカードやZenn独自の記法が壊れます。書き出しはダッシュボードから行ってください。ただしこのAPIは、移行の前後で記事が変わっていないかを照合するのには使えます。
書き出したファイルはarticles/<slug>.mdとして置きます。ファイル名をslugに一致させ、どちらも変えないでください。変えると上書きではなく別記事として増え、いいねが付いた元の記事がそのまま残ります。置いたらfrontmatterを開いて3点を見ます。
title/emoji/type/topicsが公開中の記事と一致しているpublishedがtrueになっているpublished_atに元の公開日時が入っている
3つ目は落とすと公開日が変わります。過去日時は一度設定すると変更できないので、空のままpushしないでください。私の場合は書き出した内容に最初から入っており、手で足す必要はありませんでした。
npx zenn previewで表示が崩れていないことを見てから、pushします。
git add articles/<slug>.md
git commit -m "Import the existing Zenn article into Git management"
git push origin mainpush後に公開APIで前後を照合した結果です。URL・いいね数・公開日は変わらず、記事の由来だけがGitHub側に切り替わりました。
| 項目 | push前 | push後 |
| 記事のパス | 変更なし | 同じ |
liked_count | 3 | 3 |
published_at | 2025-02-16T15:32:58+09:00 | 同じ |
source_repo_updated_at | null | pushした時刻 |
body_letters_count | 7433 | 7434 |
最後の行だけ、字数が1増えています。ローカルの表示と公開中の表示を288行すべて比べたところ、目に見えるテキストは完全に一致していました。改行などの見えない文字が1つ増えただけだと考えられますが、push前のHTMLを控えていなかったため特定できませんでした。次に記事を移すときは、pushの前に公開中のHTMLを保存してから作業します。

画像はリポジトリに持ち込まなくてよい
ブラウザのエディタでアップロードした画像は、markdownの中では絶対URLとして書かれています。リポジトリ側に画像ファイルを持ち込む必要はありませんし、書き出したファイルをそのまま置いてもリンクは切れません。
ただしmarkdownの原文に書かれているホストと、公開ページのHTMLに出てくるホストは違います。Zennが表示するときに書き換えているためです。移行の前後で画像を突き合わせるときは、ホスト名ではなくファイル名で照合してください。
捨て記事はGitHubとダッシュボードの両方から消す
リポジトリからファイルを消しても、Zennの投稿は残ります。私はファイルを消してpushしたあともダッシュボードに下書きが残っているのを見て、制約に書かれていたとおりだと確かめました。
完全に消すには、記事の管理から投稿を削除し、リポジトリからもファイルを消します。片方だけでは消えません。
まとめ
順番さえ守れば、公開済みの記事はURL・いいね数・公開日を保ったままGit管理下へ移せます。連携したあとは、mainへのpushだけでZennに反映されます。
気をつける点を並べておきます。
- GitHub Appは「Only select repositories」に切り替える。初期状態は全リポジトリで、上限の2つを超えると連携が失敗する
- インストールは、Zennにログイン済みのブラウザで通す。未ログインのブラウザで進めたときは、GitHub側だけ入ってZennが未連携のまま残った
- 捨て記事でデプロイ経路を確かめてから、公開済みの記事を取り込む
- slugとファイル名を変えない。変えると上書きではなく別記事が増える
- 投稿を消すときは、ダッシュボードとリポジトリの両方から消す
- 移行の前に、公開中のHTMLを控えておく
次の入口はZenn CLIをインストールするとZenn CLIで記事・本を管理するです。途中から連携を始める手順はZennで途中からGitHubリポジトリ連携をはじめるときの手順にまとまっており、ツールの中身はzenn-editorで読めます。
なお、ダッシュボードの画面構成、zenn-cliのバージョン要件、連携できるリポジトリ数の上限は変わります。実際に設定するときは、最新を公式ドキュメントで確認してください。

