はじめに:なぜこのシステムを作ったのか
ブログを書くとき、皆さんはどんなエディタを使っていますか?
WordPress標準のGutenbergエディタも便利ですが、長文を書いているとこんな不満が出てきませんか?
- ブラウザがクラッシュしたら書いた内容が消える不安
- 画像管理が煩雑で、同じ画像が重複してアップロードされる
- Markdown記法で書けないもどかしさ
- バージョン管理ができない
私もまさにこの悩みを抱えていました。そこで、VSCodeでMarkdownを書いて、ワンコマンドでWordPressに投稿できるシステムを作ることにしました。
この記事では、その開発の経緯と、遭遇した問題、そして最終的に完成したシステムについてご紹介します。
理想の執筆環境とは
まず、私が目指した理想の執筆環境はこんな感じです:
求めていた機能
1. VSCodeでMarkdownを書きたい
- 慣れ親しんだエディタで執筆
- リアルタイムプレビュー(Ctrl+Shift+V)
- Gitでバージョン管理
2. 画像管理を自動化したい
- ローカルの画像を自動アップロード
- 画像パスを自動で置換
- 同じ画像の重複を避ける
3. ワンコマンドで投稿したい
npm run publishこれだけで完了!
4. 既存記事の更新も自動化したい
- スラッグで既存記事を検索
- 見つかったら自動更新
- 見つからなければ新規投稿
開発の流れ:試行錯誤の連続
Phase 1:まずは基本機能
最初は、シンプルに「MarkdownをWordPressに投稿する」ことを目指しました。
使用した技術:
- Node.js
- WordPress REST API
marked(Markdown → HTML変換)gray-matter(Front Matter解析)
基本的な処理フロー:
1. post.md を読み込み
2. Front Matter解析(タイトル、スラッグなど)
3. Markdown → HTML変換
4. WordPress REST APIで投稿最初のコード(簡略版):
const marked = require('marked');
const matter = require('gray-matter');
const axios = require('axios');
// Markdownファイル読み込み
const fileContent = fs.readFileSync('post.md', 'utf-8');
const { data: frontMatter, content: markdown } = matter(fileContent);
// HTML変換
const htmlContent = marked(markdown);
// WordPress投稿
await axios.post(
`${WP_URL}/wp-json/wp/v2/posts`,
{
title: frontMatter.title,
content: htmlContent,
slug: frontMatter.slug,
status: 'draft'
},
{ auth: { username: WP_USER, password: WP_APP_PASSWORD } }
);この時点では、「動く!」という感動がありました。VSCodeで書いた記事が本当にWordPressに投稿されたのです。
Phase 2:画像アップロードの実装
次に、画像の自動アップロードに取り組みました。
課題:
- Markdown内の相対パス(
images/screenshot.png)を、WordPressのURLに置換する必要がある
解決策:
images/フォルダ内の画像を全てアップロード- アップロード後のURLを取得
- Markdown内のパスを置換
実装:
// 画像アップロード
const imageMap = {};
for (const imageFile of imageFiles) {
const imagePath = path.join(imagesDir, imageFile);
const form = new FormData();
form.append('file', fs.createReadStream(imagePath));
const response = await axios.post(
`${WP_URL}/wp-json/wp/v2/media`,
form,
{ auth: { username: WP_USER, password: WP_APP_PASSWORD } }
);
// ローカルパス → WordPressのURL
imageMap[`images/${imageFile}`] = response.data.source_url;
}
// Markdown内のパスを置換
let updatedMarkdown = markdown;
for (const [localPath, wpUrl] of Object.entries(imageMap)) {
updatedMarkdown = updatedMarkdown.replace(
new RegExp(`\\(${localPath}\\)`, 'g'),
`(${wpUrl})`
);
}これで、画像が自動でアップロードされ、記事内のリンクも正しく置換されるようになりました!
Phase 3:WP Githuber MDプラグインとの出会い
ここで、WP Githuber MDという素晴らしいプラグインを発見しました。
このプラグインを使うと、WordPress側でもMarkdown記法で編集できます。つまり:
VSCodeでMarkdown編集
↓
WordPressに投稿(Markdownのまま)
↓
WordPress側でもMarkdown編集可能!試してみた結果:
最初は「Markdown → HTML変換」していましたが、WP Githuber MDを使えばMarkdownをそのまま投稿できるはず...
しかし、ここで大きな問題が発生しました。
遭遇した問題たち
問題1:Markdownがそのまま表示される
症状:
## 見出し
本文...このMarkdownをそのまま送信すると、WordPress側で:
# タイトル
## 見出し
本文...
Markdownの記号がそのまま表示されてしまいました。
原因:
- WordPress REST APIは、デフォルトでMarkdownを認識しない
- WP Githuber MDのメタフィールドを設定する必要がある
解決策:
// メタフィールドを追加
const postData = {
title: frontMatter.title,
content: markdown, // HTMLではなくMarkdown
meta: {
_is_githuber_markdown: '1',
_is_githuber_markdown_enabled: 'yes'
}
};これで、Markdownがそのまま送信され、WordPress側で正しく表示されるようになりました!
問題2:既存記事がHTMLの場合
次の問題:
既存記事(すでにHTMLで保存されている)を、Markdownで更新しようとすると...
症状:
既存記事(HTML形式)
↓
Markdownを送信
↓
WordPress: 「これはテキストだ」
↓
## 見出し
で保存されるつまり、一度HTMLとして保存された記事は、Markdownとして認識されない!
試行錯誤:
- WordPress管理画面で手動変換 → 面倒
- 記事を削除して再投稿 → 一時的に記事が消える
- モード切替可能にする → これだ!
問題3:画像が増え続ける
最大の問題:
記事を更新するたびに、画像が新規アップロードされる:
1回目: featured.jpg
2回目: featured-2.jpg ← 新規
3回目: featured-3.jpg ← どんどん増えるWordPress Media Libraryが肥大化!
この問題の解決が、今回の開発で最も時間をかけた部分でした。
最大の挑戦:画像管理の最適化
解決策の検討
方法1:画像を再利用
- ファイル名で検索 → 見つかったら再利用
- 問題: 内容が変わっても検出できない
方法2:削除して再アップロード
- 毎回削除 → 再アップロード
- 問題: 遅い、無駄
方法3:ハッシュベース(採用!)
- 画像の内容からハッシュを計算
- ハッシュが一致 → 再利用
- ハッシュが異なる → 削除して再アップロード
ハッシュベースの実装
1. ハッシュ計算:
const crypto = require('crypto');
function calculateFileHash(filePath) {
const fileBuffer = fs.readFileSync(filePath);
const hashSum = crypto.createHash('md5');
hashSum.update(fileBuffer);
const hex = hashSum.digest('hex');
return hex.substring(0, 6); // 最初の6桁
}2. ファイル名にハッシュを含める:
元のファイル名: featured.jpg
↓
ハッシュ計算: a3f2b1
↓
新しいファイル名: article-slug-featured-a3f2b1.jpg3. 既存画像を検索:
async function findExistingImageByPattern(slug, originalFileName) {
const searchPattern = `${slug}-${originalFileName}`;
const response = await axios.get(
`${WP_URL}/wp-json/wp/v2/media`,
{
auth: { username: WP_USER, password: WP_APP_PASSWORD },
params: { search: searchPattern }
}
);
// パターンに一致する画像を探す
for (const media of response.data) {
const fileName = path.basename(media.source_url);
if (fileName.startsWith(searchPattern)) {
return media;
}
}
return null;
}4. 統合処理:
async function uploadOrReuseImage(imagePath, fileName, slug) {
// ハッシュ計算
const hash = calculateFileHash(imagePath);
const newFileName = `${slug}-${fileName}-${hash}.jpg`;
// 既存画像を検索
const existingImage = await findExistingImageByPattern(slug, fileName);
if (existingImage) {
const existingFileName = path.basename(existingImage.source_url);
if (existingFileName.includes(`-${hash}`)) {
// ハッシュ一致 → 再利用
console.log(`♻️ ${fileName} (変更なし)`);
return { id: existingImage.id, url: existingImage.source_url };
} else {
// ハッシュ不一致 → 削除
console.log(`🗑️ 古い画像を削除`);
await deleteImage(existingImage.id);
}
}
// 新規アップロード
console.log(`✅ ${fileName} → ${newFileName}`);
return await uploadImage(imagePath, newFileName);
}結果:
1回目実行:
✅ featured.jpg → article-featured-a3f2b1.jpg
2回目実行(画像変更なし):
♻️ featured.jpg (変更なし) ← 再利用!
3回目実行(画像を更新):
🗑️ 古い画像を削除: article-featured-a3f2b1.jpg
✅ featured.jpg → article-featured-9e8f7d.jpg ← 新しいハッシュ完璧に動作しました!🎉
完成したシステム
最終的なアーキテクチャ
VSCode
↓ Markdown記法で執筆
posts/
├── article-name/
│ ├── post.md
│ ├── featured.jpg
│ └── images/
│ └── screenshot.png
↓ npm run publish
Node.jsスクリプト
├─ Front Matter解析
├─ H1削除
├─ 画像アップロード(ハッシュ管理)
├─ パス置換
├─ HTML変換(HTMLモード)
└─ 既存記事検索・更新
↓ WordPress REST API
WordPress
↓ 表示
ブログ記事主要機能
1. 2つのモード
# HTMLモード(推奨)
mode: html
→ スクリプト側でHTML変換
→ Gutenbergエディタ
# Markdownモード
mode: markdown
→ Markdownをそのまま送信
→ WP Githuber MDエディタ2. 既存記事の自動更新
// 既存記事を検索
const existingPost = await findExistingPost(frontMatter.slug);
if (existingPost) {
// 更新
await axios.post(`/wp-json/wp/v2/posts/${existingPost.id}`, data);
} else {
// 新規投稿
await axios.post(`/wp-json/wp/v2/posts`, data);
}3. ハッシュベースの画像管理
画像変更なし → 再利用 ♻️
画像変更あり → 削除 🗑️ → 再アップロード ✅4. タイトル重複の自動回避
// 最初のH1を自動削除
updatedMarkdown = removeFirstH1(updatedMarkdown);
実際の運用フロー
日常的な記事投稿
# 1. 記事作成
code posts/new-article/post.md
# 2. 画像配置
posts/new-article/
├── post.md
├── featured.jpg
└── images/
└── screenshot.png
# 3. プレビュー確認
Ctrl+Shift+V
# 4. 投稿
npm run publish-single new-article
# 5. WordPress管理画面で確認
→ 公開所要時間:わずか数秒!
既存記事の更新
# 1. VSCodeで編集
code posts/existing-article/post.md
# 2. 画像も更新(上書き保存)
posts/existing-article/images/screenshot.png
# 3. 投稿
npm run test-local
# 4. 出力確認
📸 画像アップロード中...
🗑️ 古い画像を削除: article-screenshot-a3f2b1.png
✅ screenshot.png → article-screenshot-9e8f7d.png
📝 既存記事を更新します(ID: 123)
✅ 更新成功!画像の更新も自動検出!
得られたメリット
1. 執筆効率の向上
Before:
WordPress Gutenbergで執筆
↓ 画像をドラッグ&ドロップ
↓ ブラウザがクラッシュしないか不安
↓ バージョン管理なしAfter:
VSCodeで執筆
↓ Ctrl+Shift+V でプレビュー
↓ Gitでバージョン管理
↓ npm run publish
↓ 完了!2. 画像管理の最適化
Before:
同じ画像が何度もアップロードされる
→ Media Libraryが肥大化
→ 管理が大変After:
画像の内容が変わっていなければ再利用
→ Media Libraryがクリーン
→ 無駄なアップロードなし3. 既存記事の更新が楽
Before:
WordPress管理画面を開く
→ 記事を探す
→ 編集
→ 保存After:
VSCodeで編集
→ npm run publish
→ 完了!4. バージョン管理
Gitで管理:
git commit -m "記事追加:○○について"
git push履歴が残る:
- いつ何を変更したか
- 過去のバージョンに戻せる
- チーム開発も可能
今後の展望
GitHub Actions連携
次のステップは、GitHub Actionsでの自動投稿です:
name: Publish to WordPress
on:
push:
branches: [main]
paths: ['posts/**']
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm install
- env:
WP_URL: ${{ secrets.WP_URL }}
WP_USER: ${{ secrets.WP_USER }}
WP_APP_PASSWORD: ${{ secrets.WP_APP_PASSWORD }}
run: npm run publish運用フロー:
VSCodeで記事作成
↓
git push
↓
GitHub Actions自動実行
↓
WordPressに自動投稿完全自動化!
複数サイト対応
複数のWordPressサイトへの投稿も検討中:
npm run publish-site1
npm run publish-site2バルク投稿
過去記事の一括移行:
npm run publish # 全記事を投稿
まとめ
開発を通じて学んだこと
1. 試行錯誤の重要性
最初から完璧なシステムは作れません。問題に遭遇するたびに、解決策を考え、実装し、改善する。この繰り返しが重要でした。
2. ユーザー体験の大切さ
技術的に可能でも、運用が面倒では意味がありません。「ファイル名を変える」という一手間を省くために、ハッシュベースの仕組みを実装しました。
3. ドキュメントの価値
試行錯誤の過程を記録しておくことで、後から見返したときに「なぜこうしたのか」が分かります。
このシステムを作って良かったこと
- 執筆が楽しくなった
- バージョン管理ができるようになった
- 画像管理のストレスがなくなった
- 記事の更新が簡単になった
そして何より、このシステム自体がブログ記事になりました!
技術スタック
最後に、使用した技術をまとめておきます:
言語・ランタイム:
- Node.js
ライブラリ:
axios- HTTP通信marked- Markdown → HTML変換gray-matter- Front Matter解析form-data- ファイルアップロードdotenv- 環境変数管理crypto- ハッシュ計算
WordPress側:
- WordPress REST API
- WP Githuber MD(オプション)
エディタ:
- VSCode
バージョン管理:
- Git / GitHub
おわりに
VSCodeでブログを書きたい、画像管理を自動化したい、という単純な動機から始まったこのプロジェクト。
試行錯誤を繰り返しながら、最終的には想像以上に完成度の高いシステムになりました。
もし同じような悩みを抱えている方がいたら、この記事が参考になれば嬉しいです。
そして、「自分でツールを作る」という選択肢を持つことで、日々の作業がもっと楽しくなるかもしれません。
Happy Blogging! 🚀
参考リンク
最終更新: 2026-01-11

コメント