0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

QiitaとHexoで同時投稿をするために

Last updated at Posted at 2025-04-29

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

以下、実行時のログ
exec_deploy_to_qiita_sh.png

これにより、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.shdeploy_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にも投稿できるようにしたい。

同時投稿での注意点

QiitaHexoで同時投稿・同時配信するために、画像リンクなどは、おそらく、Qiitaベースのほうが良い。
理由は、Hexoでの画像表示するときのMARKDOWN形式での書き方は、![](/images/sample.png)というように、ルートに/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

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?