QiitaとHexoで同時投稿するには
最初に
これは、あくまで、Qiita over Hexo
であることに注意したい。私が、最初にHexo
で投稿するようになって、のちに、Qiita
で投稿しようとしたため、ディレクトリ階層などは、Hexo
が主軸になる。
HexoとQiitaの概略
どちらもMarkDown形式のファイルを自動化されたcss、javascriptと合わせて、最終的に、htmlに変換するサービスを提供している。npm
パッケージリポジトリにそれらをCLIから操作できる便利なツールが出ているため、先に、npm
使える状況にないよって人は以下を参考にしてね。
私的ArchLinux開発環境構築 >> nvmによるnpm/node環境構築
Hexo
インストール
npm i --save-dev hexo-cli -g
これを実行した後は、どのディレクトリ階層でも、hexo
コマンドが使えるようになっていると思う。次に、ディレクトリ階層を作っていくが、それを1から構成するのは骨が折れるので、以下を実行し、必要ファイルをhexo
に生成してもらう。
ファイル配置
# 作業ディレクトリを作ってその中で実行する。
hexo init
記事の作成
hexo new "<title>"
これにより、source/_posts/
内に<title>.md
が追加されるので、それを編集する。
ローカルサーバー起動(テスト)
規定では、http://localhost:4000
で起動される。
hexo server
カスタマイズ
デプロイヤー
以下、Githubをデプロイ先として考える。
現状、source/_posts/
に*.md
ファイルを配置し、hexo server
をすることで、ローカルでそれらを確認できるが、実際は、リモートで配信するためのプッシュ(デプロイ)が必要になる。別にGithubのワークフローでも自動プッシュが可能。しかし、私はコマンド派。
npm i hexo-deployer-git
_config.yml
の変更場所
# デプロイ先のレポジトリ名が`<user>.github.io`の場合
url: https://<user>.github.io/
deploy:
type: git
repo: https://github.com/<user>/<user>.github.io
branch: master
テーマ
私は、jerryc127/hexo-theme-butterflyを使っている。他にも、テーマはたくさんあるので好みのものを選ぼう。
https://hexo.io/themes/
リンク簡略化〜永久リンクを短くする〜
デフォルトの設定では、投稿後のリンクは、https://<デプロイ先のURL>/2025/04/28/<title>/
となるが、以下を導入することで、https://<デプロイ先URL>/posts/8821/
というURLでアクセスできるようになる。
npm i hexo-abbrlink --save
_config.yml
permalink: posts/:abbrlink/
Qiita(Qiita-CLI)
インストール
npm i @qiita/qiita-cli --save-dev
これにより、npx
コマンドを経由して、qiita
コマンドが使えるようになった。以下、初回に限り実行するもの。
npx qiita init
npx qiita login
Hexoで作ったmdファイルをQiitaへ投稿するまで
開発での苦難
ここで私は困った。
このqiita
コマンドでは、--root
引数により、ルートディレクトリは設定できるものの、public
ディレクトリは必要なのだ。
一方、hexo
では、*.md
ファイルをsource/_posts/
ディレクトリに配置する。ここをうまいこと統合する必要がある。
そこで、私が、その、hexo
からqiita
への橋渡しとなるバッシュスクリプトを開発した。それが以下である。
deploy_to_qiita.sh
の紹介
deploy_to_qiita.sh
#!/bin/bash
ROOT_QIITA="qiita"
SOURCE="source/_posts"
cd "$(dirname "$0")"
current_quotepath=$(git config --get core.quotepath)
if [ "$current_quotepath" != "false" ]; then
echo "Setting core.quotepath to false to handle Japanese filenames correctly."
git config --global core.quotepath false
fi
mkdir -p "${ROOT_QIITA}/public"
if [[ $# -gt 0 && $1 == "force" ]]; then
CHANGED_FILES=$(git ls-files "${SOURCE}/*.md")
else
CHANGED_FILES=$(git diff --name-only | grep "${SOURCE}/.*\.md$")
fi
if [ -z "$CHANGED_FILES" ]; then
echo "No .md files changed since last commit."
else
echo "Changed .md files:"
while IFS= read -r file; do
echo "- $file"
source_file="$file"
target_file="${ROOT_QIITA}/public/$(basename "$source_file")"
if [[ -f "$target_file" ]]; then
echo "File exists: $source_file -> $target_file"
temp_file="${target_file}.temp"
mv "$target_file" "$temp_file"
cp "$source_file" "$target_file"
TEMP_FRONT_MATTER=$(head -n 999 "$temp_file" | grep -E "^updated_at:|private:|id:|organization_url_name:|slide:|ignorePublish:")
TEMP_UPDATED_AT=$(echo "$TEMP_FRONT_MATTER" | grep -oE "^updated_at: (.*)$" | sed -E "s/^updated_at: //")
TEMP_PRIVATE=$(echo "$TEMP_FRONT_MATTER" | grep -oE "^private: (.*)$" | sed -E "s/^private: //")
TEMP_ID=$(echo "$TEMP_FRONT_MATTER" | grep -oE "^id: (.*)$" | sed -E "s/^id: //")
TEMP_ORG_URL=$(echo "$TEMP_FRONT_MATTER" | grep -oE "^organization_url_name: (.*)$" | sed -E "s/^organization_url_name: //")
TEMP_SLIDE=$(echo "$TEMP_FRONT_MATTER" | grep -oE "^slide: (.*)$" | sed -E "s/^slide: //")
TEMP_IGNORE=$(echo "$TEMP_FRONT_MATTER" | grep -oE "^ignorePublish: (.*)$" | sed -E "s/^ignorePublish: //")
if [ -n "$TEMP_UPDATED_AT" ]; then
sed -i "1a updated_at: ${TEMP_UPDATED_AT}" "$target_file"
fi
sed -i "2a private: ${TEMP_PRIVATE}" "$target_file"
if [ -n "$TEMP_ID" ]; then
sed -i "3a id: ${TEMP_ID}" "$target_file"
fi
if [ -n "$TEMP_ORG_URL" ]; then
sed -i "4a organization_url_name: ${TEMP_ORG_URL}" "$target_file"
fi
sed -i "5a slide: ${TEMP_SLIDE}" "$target_file"
sed -i "6a ignorePublish: ${TEMP_SLIDE}" "$target_file"
rm "$temp_file"
else
echo "New file: $source_file -> $target_file"
cp "$source_file" "$target_file"
FRONT_MATTER=$(head -n 999 "$target_file" | grep -E "^title:|tags:|abbrlink:|date:")
UPDATED_AT=$(echo "$FRONT_MATTER" | grep -oE "^date: (.*)$" | sed -E "s/^date: //")
sed -i "1a updated_at: \"${UPDATED_AT}\"" "$target_file"
sed -i "2a private: false" "$target_file"
sed -i "3a id: null" "$target_file"
sed -i "4a organization_url_name: null" "$target_file"
sed -i "5a slide: false" "$target_file"
sed -i "6a ignorePublish: false" "$target_file"
fi
md_basename=$(basename "$source_file" .md)
if [ -f "mapping.sh" ]; then
. mapping.sh
each "$ROOT_QIITA/public" "$md_basename"
fi
npx qiita publish --root "${ROOT_QIITA}" "$md_basename"
done <<<"$CHANGED_FILES"
fi
これにより、Qiitaで投稿する際に必要になる、キーupdated_at;private;id;organization_url_name;slide;ignorePublish;
などがhexo new "<title>"
コマンドで生成されたsource/_posts/
内にある*.md
ファイルに対して、自動で追加され、そのまま投稿・更新できるようになる。
なお、一度、qiita/
ディレクトリを作り、そこに、source/_posts/
内の*.md
ファイルをコピーした後で、sed
コマンドによるファイル操作を行うので、元の*.md
ファイルが汚染されることはない。
最新のdeploy_to_qiita.sh
については、以下を参照してほしい。更新があれば、記事の方も更新するようにするので、同じ内容になるかとは思う。(一応)
https://github.com/verazza/blog/blob/master/deploy_to_qiita.sh
deploy_to_qiita.sh
の簡単な説明と使い方
おおまかな使い方
具体的な使い方を説明しよう。
これは内部で、git diff
コマンドによる、source/_posts
内の*.md
ファイルに差異があれば、それをqiita/public
にコピーして、front-matterを処理するので、git push
する前に、deploy_to_qiita.sh
を実行する必要がある。
投稿前に文字列置換を行いたい場合
また、もし、qiita
投稿時に、特定の*.md
ファイルに対して、文字列置換を行いたい場合は、mapping.sh
をdeploy_to_qiita.sh
と同じ階層に配置すれば、deploy_to_qiita.sh
が自動で読み込んでくれる。今回、それは、ここには掲載はしないが、興味があれば、以下を見てほしい。
https://github.com/verazza/blog/blob/master/mapping.sh
置換だけ行いたい場合は、以下を実行すれば、source/_posts/
内のすべての*.md
ファイル(Git管理化にあるもの)に対して、deploy_to_qiita.sh
を実行できる。
./deploy_to_qiita.sh force
今後のdeploy_to_qiita.sh
をどうするか
にしても、git push
する前に、実行しなければいけないというのは、ユーザーエクスペリエンスの観点で良くないと思う。だから、まずは、これをコミット履歴やもしくはデータベースなどを駆使することで、差異があることを確認するなどして、git diff
に依存しない形を取る必要がある。
別途、zenn
にも投稿できるようにしたい。
同時投稿での注意点
Qiita
とHexo
で同時投稿・同時配信するために、画像リンクなどは、おそらく、Qiita
ベースのほうが良い。
理由は、Hexo
での画像表示するときのMARKDOWN形式での書き方は、
というように、ルートに/images
ディレクトリがある前提でpublic/images
に画像を保存するのだが、Qiita
ではそのようなものはサポートされていないためだ。
じゃあ、どうするのかというと、一度、WEBの方で、Qiita
で画像を貼り付けると、https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/
から始まる画像リンクが得られるので、これをHexo
のソースである*.md
ファイルにも貼り付けることで、両方で画像を表示することが可能になる。
めんどうだけどね。
いや、そんなことしなくてもいいのだ。文字列置換があれば、例えば、Qiita
にデプロイするときには、/image/sample.png
という文字列をhttps://qiita-image-store.s3.ap-northeast-1.amazonaws.com/
に変換すればいい。すなわち、mapping.sh
にその旨を書けばよい。
package.json
のタスク
以下、参考までに。
"scripts": {
"server": "hexo clean && hexo generate && hexo server",
"deploy": "hexo clean && hexo generate && hexo deploy",
"deploy2": "./deploy_to_qiita.sh",
"deploy-all": "npm run deploy && npm run deploy2",
"qiita-sync": "npx qiita pull --root qiita"
}
最後に
今回は、記事作成および投稿にhexo
を使っていた私がqiita
にも、一応投稿しておくか!ってことで、Qiita-CLI
を使いましたが、良い機会になりました。やっぱコマンド触ってるだけでエンジニアっぽい...
追記:
おそらく、これを応用すれば、zenn
にも投稿できると思うので、またいずれ...
参考
jerryc127/hexo-theme-butterflyテーマを使うときに
https://sj-note.com/hexo-butterfly-upgrade
hexo
の有用プラグインの検索に
https://qiita.com/ORCHESTRA_TAPE/items/a0c795904e33cdf043d7