ストックマークでプロダクトエンジニアとDevRelを兼務している安藤(@ampersand_xyz)です。
テックブログをはてなブログに移行しました
月末にストックマークのテックブログを自社で運用していたHugoからはてなブログへ移行しました。特に何らかの告知などは行わず極めてヌルリ…と静かに移行を行ったのですが、その作業についての記録を共有させていただきます。
移行した目的
なぜブログを移行したのかについてですが、色々と目的がありました。
運用面
- 記事の投稿用エディタがあるブログプラットフォームを使用することで、ブログを書きたい人の記事作成・確認のためのハードルを下げたかった
- 動的生成されたOGP画像を使いたかった
- ブログのタイトル用画像について、生成方法や設定のルールなどが特になく、OGP用の画像を作るために元になるテンプレートの情報を口伝で連絡して対応するという形になっており、作成の手間や連絡の煩雑さ存在しており、こだわる場合以外はタイトルが動的生成されるブログPaaSを使うことでOGP用の画像を気にしないといけない面倒さを軽減したかった
- Hugoでは画像や動画サイズに制限がかからない一方、ファイルサイズを意識する機会がなかったためか、かなり巨大なサイズの画像や動画がそのまま掲載される形になっていた
- 検索を使いたかった
- 目的とするには若干個人的な要素なのですが、Hugoはインクリメンタルサーチを標準では使用できません(実現する方法はあるようです)
- 過去記事のジャンルがわりと多岐に渡っているので、キーワードで検索できると社のナレッジとしても探しやすく便利だなという思いがありました
保守面
- 運用開始から時間が経過しており、社内で運用の責任の所在・管理者がどうなっているのかフワフワした状況になっている
- 例としてTwitterがXと名称変更され、伴うアクション周りの名称が変わったりしたときなど、他プラットフォームの変更影響を受けてブログをメンテナンスするリソースを都度捻出したくはない。Hugoのメンテナンスを自前で行うよりはブログサービスを使用するほうが継続運用をしていくうえで良いのではという判断
- Hugoのフォルダ運用ルールが定められておらず、初期の頃の記事は配置や名称がバラバラになってしまっており、このまま長く運用を続けたときにカオス化してしまうことが予想される状態になっていた
端的に言えば、自社でブログをホスティングする保守運用のリソースやコストを下げることや、執筆ハードルを下げるためにブログSaaSを使うという形にした、という話です。
はてなブログを選定した理由
技術記事執筆プラットフォームでいえばZennやQiitaないしnoteやmediumなどもありますが、はてなブログを選定した理由は以下の内容になります。
- コンテンツ投稿プラットフォーム側のユーザーの風土などが何らかの枷にならないこと
- はてなブログは「技術に関する記事を投稿する」というルールはありますが、テックブログなのでこのルールは特に運用ハードルとなりませんでした
- デザインやカスタマイズの幅があること
- ブログ全体を「ストックマーク株式会社っぽい見た目」にすることが可能なため、ブログを繰り返し閲覧してもらったときに「ストックマークの印象」に馴染んでもらうようにカスタマイズ出来ること
- 中にはものすごい見た目をきれいにカスタマイズしている企業さんもあって、どうやっているのか知りたい…。
- はてなブログは かなり長年の運用実績があることや、企業テックブログの媒体としてスタンダードであること
- 検索の優位性がある(らしい)こと、企業ブログフィードがあること
- 良い記事を書けば読んでもらえるというナイーブな考え🍜👩🦲を捨てたわけではないが、流入導線を増やすことで、より多くの人に届けることができるならする
- 絵文字の意味がわからない方はらーめん才遊記を読んでください
Hugoからはてなブログへ どのように移行作業を行ったか
ここからは既存の50記事をどう移行したかの作業の記録になります。
いうてブログの移行なんて簡単にできるやろ!!とタカを括っていたのですが、意外と失敗や苦戦を繰り返したので、ブログの移行を検討している方はこれを他山の石として頂き、同じ轍を踏まないようにしていただくことを願うばかりです。
コピペでコンテンツ内容の移行(失敗)
50記事を手動でコピー&ペーストする程度の作業であれば、ぼちぼち頑張れば小一時間程度で終わります。な〜んだ、楽勝ですね。
はてなブログのビジュアルエディタに既存のブログのページのコンテンツをコピペして…そして画像が元サーバを参照してしまっているので、はてなフォトライフにアップロードし直せば終わり!・・・と思ったんですが、この段階ではじめてサイズが大きすぎてはてなフォトライフにアップロードできない画像や動画があることと、ビジュアルエディタに既存のブログページ、つまりウェブサイトからコピー&ペーストをすると”Styleが指定されたタグ”が展開されてしまう形になっており、はてなブログのスタイルと整合が取れなくなってしまうことなど他の問題に気づきました。
楽勝と思われましたが、この方法は失敗です。そしてここから先が長くなります。
移行手順を見直す
エディタ上で作業をし続けていると無限に時間を浪費してしまうので、作業方針はHugoのマークダウンを転用する形に確定し、移行のためのCLIツールを作成することにしました。これにあたって、はてなブログのCLIクライアントとして公開されているblogsyncを使わせていただくことにしました。
執筆用プロジェクトの準備
まず作業場所にするプロジェクトを用意します。そのついでにHugoでの執筆体験を失わないようにしようと思い、CLIでの執筆環境として利用できる形にしました。 シェルスクリプトはあまり知見がなかったのでAIに頼りつつなんとか形に仕上げました。一部省略をしていますが構成は以下のような形です。
├── stockmark-tech.hatenablog.com // はてなブログの記事mdが格納されるフォルダ
├── README.md
├── blog_pull.sh // はてなブログの記事をローカルに上書きで取得する
├── blogsync.yaml // 認証情報
├── create_draft.sh // 下書きを作成する
├── encode_code_tags.sh // ブログ記事内のタグのエンコーダ
├── publish.sh // 公開用スクリプト
├── update.sh // 記事更新用スクリプト
└── upload_image.sh // はてなブログに画像をアップロードするためのスクリプト
ここまで準備したのち、以下のような手順で作業を行いました。(実際はめちゃくちゃ七転八倒しながら行っていたので、振り返ってからこんな流れだったな、とまとめています)
- 内容が空の下書きを移行するブログの件数分用意し、移行先となるmdのガワを用意する
- 全記事の本文・メタ情報を移植
- 各記事ごとの調整
- はてなブログ上で下書きプレビューでレイアウト崩れなどが起こっていないか確認・調整
- 移行完了
以上の内容を順番にダイジェストで解説します。
移行する記事分の下書きを用意する
いったん はてなブログ内に下書きを移行する記事分の量産します。理由としてはblogsyncでブログの情報を取ってファイルに展開したさいに_draftsフォルダ以下にすべて格納されるため、移行作業をまとめて行う際に都合が良いからです。
下書きを作成するシェルの中身はこんな内容です。タイトルを適当につけておきます
#!/usr/bin/expect
# コマンドを実行
spawn blogsync post --draft --title=記事タイトルを設定してください stockmark-tech.hatenablog.com
# EOFを送信
send "\004"
expect eof
量産します。
for i in {1..50}; do ./create_draft.sh; done
ブログの記事データを手元に同期する
量産した下書きをマークダウンで手元に用意します。
#!/bin/bash
# y/Nで確認するための処理
function confirm() {
read -p "$1 (y/N) " yn
case "$yn" in
[yY]*) return 0 ;;
*) return 1 ;;
esac
}
# 処理を継続していいかの確認
if ! confirm "stockmark-tech.hatenablog.com以下のフォルダ・ファイルをブログの情報に上書きします。_drafts以下に作業中の下書きなどがないか注意してください。よろしいですか?"; then
echo "処理を中断しました。"
exit 1
fi
cd ./stockmark-tech.hatenablog.com/entry
find . -type d ! -name '' -mindepth 1 -maxdepth 1 -exec rm -rf {} +
cd ../..
blogsync pull stockmark-tech.hatenablog.com
このシェルスクリプトを実行すると、プロジェクト内にあるブログURLのフォルダ以下に、はてなブログ以下にある記事がダウンロードされます。
ここまで来たらあとは気合と根性で全記事を元記事からコピペして修正を繰り返していきますが、鬼門となったのは画像の取り扱いでした。
記事内の画像をどうにかする
いままではブログ用のサーバに静的ファイルが同居していましたが、そのサーバについては将来的に開放したいため、画像ははてなフォトライフを使うことにしました。
かといって、これからブログを書いてもらう人に都度はてなフォトライフを開いて画像を上げてくれというのは面倒をかけるかたちになり、Hugoでの執筆環境より環境が不便になってしまうことになるのでそれは避けようと思い、手元のマークダウンに含まれている画像用タグのパスを取得し、はてなフォトライフにアップロード、そしてアップロードされた画像のURLで元のパスを置換する、という仕組みを作ることにしました。
サラッと書いていますが、はてなフォトライフのAPIが既存エンドポイントが300系リダイレクトされており、シェルから利用するためにはその部分を考慮しないといけないなどのハマりポイントがあったりして結構解決に時間を要したところになります。
ユーザ名やAPIキーは認証情報ファイルから取ってきたかったところですがサボっています…。
#!/bin/bash
# 引数の個数をチェック
if [ "$#" -ne 1 ]; then
echo "使用方法: $0 <ファイルパス>"
exit 1
fi
USERNAME="【ブログユーザ名】"
API_KEY="【APIキー】"
# ファイルパスを取得
file_path="$1"
# ファイル拡張子を取得
file_extension="${file_path##*.}"
# マークダウンファイルのあるディレクトリパスを取得
markdown_dir_path="$(dirname "$file_path")"
# 拡張子がmarkdownかどうかを確認
if [ "$file_extension" != "md" ]; then
echo "指定されたファイル ($file_path) はマークダウンファイルではありません。"
exit 1
fi
echo "指定されたファイル ($file_path) はマークダウンファイルです。"
# マークダウンファイル内の画像パスを抽出
# ![organization](./static/development/202304_organization.png) の ./static/development/202304_organization.png だけ取り出す
markdown_image_paths=$(grep -oE '!\[.*?\]\(([^)]+)\)' "$file_path" | grep -Eo '\.\/[^)]+' | sort | uniq || true)
absolute_markdown_image_paths=$(grep -oE '!\[.*?\]\((/[^)]+)\)' "$file_path" | grep -Eo '/[^)]+' | sort | uniq || true)
# imgタグのsrc属性を抽出
html_image_paths=$(grep -oE 'src\s*=\s*"([^"]+)"' "$file_path" | grep -Eo '(^|[^/])\K/[^"]+' | grep -Ev '^http' | sort | uniq || true)
absolute_html_image_paths=$(grep -oE 'src\s*=\s*"(/[^"]+)"' "$file_path" | grep -Eo '/[^"]+' | sort | uniq || true)
# 相対パスの画像パスを抽出
relative_paths=$(grep -oE 'src\s*=\s*"\.([^"]+)"' "$file_path" | grep -Eo '\.\/[^"]+' | sort | uniq || true)
# 抽出した画像パスを結合
all_image_paths=$( (echo "$markdown_image_paths"; echo "$absolute_markdown_image_paths"; echo "$html_image_paths"; echo "$absolute_html_image_paths"; echo "$relative_paths") | sort | uniq)
# 抽出した画像パスが存在するかチェック
existing_image_paths=()
for image_path in $all_image_paths; do
if [ -f "${file_path%/*}/$image_path" ]; then
existing_image_paths+=("$image_path")
fi
done
# existing_image_pathsから重複を削除
existing_image_paths=($(echo "${existing_image_paths[@]}" | tr ' ' '\n' | sort | uniq | tr '\n' ' '))
echo "マークダウンファイル内の存在するローカル画像パス:"
printf '%s\n' "${existing_image_paths[@]}"
for image_path in "${existing_image_paths[@]}"; do
# 画像のパスと認証情報を変数に設定
image_path="${image_path}"
# 存在する画像パスを表示
if [ ${#image_path} -ne 0 ]; then
# ファイル名
file_name=$(basename "${image_path}")
#拡張子
extension="${file_name##*.}"
#jpgならjpegに変換
if [ "$extension" = "jpg" ]; then
extension="jpeg"
fi
# スクリプトが存在するディレクトリの絶対パスを取得
script_dir="${markdown_dir_path}"
# スクリプトのディレクトリと画像の相対パスを結合
absolute_image_path="$script_dir/${image_path#./}"
echo "アップロードする画像のパス: $absolute_image_path"
# 画像のサイズを取得
image_size=$(wc -c < "$absolute_image_path")
# 画像のサイズが500kbを超える場合は警告を表示してアップロードをスキップ
if [ "$image_size" -gt 512000 ]; then
echo "$file_name 画像のサイズが500kbを超えているため、アップロードをスキップします。"
continue
fi
nonce=$(echo -n "$(date +%s){}$random$$" | openssl sha1 | awk '{print $2}')
now=$(TZ=Asia/Tokyo date "+%Y-%m-%dT%H:%M:%SZ")
digest=$(echo -n "$nonce$now$API_KEY" | openssl sha1 -binary | openssl base64)
credentials=$(printf 'UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"' "$USERNAME" "$digest" "$(echo -n "$nonce" | openssl base64)" "$now")
# APIエンドポイントのURL
API_URL="http://f.hatena.ne.jp/atom/post"
# Base64エンコード(改行除去)
encoded_image=$(base64 -i "$absolute_image_path" | tr -d '\n')
# XMLデータの生成
xml_data="<entry xmlns='http://purl.org/atom/ns#'>
<title>$file_name</title>
<content mode='base64' type='image/$extension'>$encoded_image</content>
</entry>"
# cURLでのPOSTリクエスト
response=$(curl -v \
-H "X-WSSE: $credentials" \
-H "Content-Type: application/xml" \
-d "$xml_data" \
-XPOST -L --post301 --post302 --post303 $API_URL)
# アップロードした画像のURLを取得
value=$(echo "$response" | sed -n 's/.*<hatena:imageurl>\(.*\)<\/hatena:imageurl>.*/\1/p')
# マークダウンの画像パスをアップロードした画像のURLに置換する
sed -i'' -e "s|${image_path}|${value}|g" "$file_path"
else
echo "マークダウンファイル内に存在するローカル画像パスは見つかりませんでした。"
fi
done
記事内のコードをエンコードする
はてなブログって長年技術系のブログプラットフォームとして存在しているので、コードの記述などはそのままサラッとできるものかと思っていたのですが、別にそんなことはなく表示のために記事内のコードのエンコードが必要だったので画像の問題と似たような要領で対応します。
#!/bin/bash
# 引数の数をチェック
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <HTML file>"
exit 1
fi
file="$1"
# 結果を一時ファイルに保存
temp_file="temp_output.html"
: > "$temp_file" # 一時ファイルを初期化
# 変数の初期化
inside_code=0
buffer=""
while IFS= read -r line; do
# 開始タグを検出
if [[ $line =~ '<code ' ]]; then
inside_code=1
echo "$line" >> "$temp_file" # コードブロック外の行をそのまま出力
continue
fi
# 置換対象範囲内の行を処理
if (( inside_code )); then
# 終了タグを検出
if [[ $line =~ $'</code>' ]]; then
inside_code=0
echo "$line" >> "$temp_file"
continue
fi
# 置換処理を行う
processed_line=$(echo "$line" | sed 's/&/&/g')
processed_line=$(echo "$line" | sed 's/</\</g') # '<' を '<' に変換
processed_line=$(echo "$processed_line" | sed 's/>/\>/g') # '>' を '>' に変換
echo "$processed_line" >> "$temp_file"
else
echo "$line" >> "$temp_file"
fi
done < "$file"
mv "$temp_file" "$file"
記事を公開する処理を用意する
記事の公開用シェルを用意します。前項の画像アップロードやエンコードをする処理を仕込んで実行したあとblogsyncで公開を行います。
#!/bin/bash
# 引数の個数をチェック
if [ "$#" -ne 1 ]; then
echo "使用方法: $0 <ファイルパス>"
exit 1
fi
file_path="$1"
#ファイルの存在を確認
if [ ! -e $file_path ]; then
echo "指定されたファイル ($file_path) が存在しません。"
exit 1
fi
# ファイルの中身に Draft: true が含まれている場合はコマンドの実行を中止
if grep -q "Draft: true" $file_path; then
echo "Draft: true が含まれているため、公開できません。Draft: false に変更してください。"
exit 1
fi
source ./upload_image.sh $file_path
source ./encode_code_tags.sh $file_path
# コマンドを実行
blogsync post stockmark-tech.hatenablog.com < $file_path
下書き更新用処理も同様の要領で用意しましたが、内容は割愛します。
記事データの移行完了!
あとは手作業で調整した下書きのマークダウンファイルをすべてターミナルで公開用のシェルスクリプトにかければ移行作業は完了です。ただ、画像アップロードを含むAPIを連打しすぎてしまうと429 Too many request エラーが出てしまうので、これを防ぐために反復処理時には数秒のインターバルを置くようにしました。 記事データの移行と同時に、CLIでのブログ執筆環境が用意できた状態になりました。
ここまでやってからHatena-Blog-Workflows-Boilerplateの存在を知る
知ったときに本当に膝から崩れ落ちるかと思ったのですが、すべてが終わってからこちらのボイラープレートの存在を知り、もっと早く知っていれば…となりました。 情報収集力や色んな人に相談するという基本的な力の欠如を感じた出来事です。今後のブログ運用で改善すべき点が出てきた時、再度載せ替えを検討する先として覚えておこうと思っています。
はてなブログの見た目の調整
もうこの段階でわりとお腹いっぱいだとおもうのですが、まだブログの記事データの移植が終わっただけです。続いて、はてなブログの見た目調整をしていきます。
はてなブログは任意のCSSを差し込むことができ、かなり柔軟に見た目を変更できます。企業ブログフィードでいろんな企業のブログを眺めているとわかりますが、すごいきれいにしている会社さんがいっぱいあります。
既存のブログから改善したかった点
いくつか改善をしたいなというポイントが有り、いったんここのクリアを目標にしました。
既存のブログはHugoのテーマ mainroad の少し古いバージョンをベースに作られており、スタイルの調整は英字で読む際に最適化されていたものだったので、日本語を読む際の認知負荷を下げることやレイアウトの調整を施しました。
大きいフォーカス点としては、以下の3点です。
- フォントサイズを16pxにし、行高さをすこしゆとりをもたせる
- 画面下部にシェア導線と著者情報が常に表示されていた形をやめ、画面領域を本文のために使う
- 見た目を自社のブランドガイドラインに合わせる
CSS調整がキツイ
ここまでくれば楽勝かと思っていたのですが、案の定そんなことはありませんでした。 はてなブログはいくつかの見た目のテーマがあるのですが、必ず何らかのテーマは使わないといけない(テーマを使わない、ということができない)んですよね。
なので、必ず何らかのテーマを用いて、そのテーマに対するオーバーライドをする形でCSSを書く必要があります。どうしてもセレクタ優先度で殴り合いをしたり、important指定が多くなるCSS苦行は避けられません(私がいい方法を知らないだけだったらご教示いただけると嬉しいです)。
テーマを自作するということもできるようなのですが、流石にそのようなチョコレートを作るためにカカオの木を栽培するところから始めるぐらいの覚悟と作業をキメるのは難しいため、今回はスタイルを上書きする方法で対応しています。
ブログタイトル画像の罠
ブログの上部にあるタイトル部分、ヘッダ画像なのですが、デザインカスタマイズ画面にヘッダ画像をアップロードする箇所があり、これで対応でき・・・・・・・・ると思ったんですが、この機能を使うと、アップロードしたさいに推奨サイズにリサイズが行われ、それに伴い画質がめちゃくちゃ下がってしまい、非常にぼんやりした画像しか使うことが出来ません。困る…。
と、困っていたのですが この問題を解決する方法として #blog-title-inner
の background-imageのCSSスタイルをリサイズされていない別の画像で上書きすれば任意の画像を使えることに気づきました。はてなブログのタイトル画像がガビガビして困っている方はぜひお試しください。
レスポンシブデザインにチェックをしておかないとスマートフォンで表示した時にオーバーライドできないCSSが読み込まれる
設定する箇所は以下の画像のチェックボックスの部分です。
このチェックを行わないと、スマホのユーザーエージェントのある環境で閲覧した時にテーマが持っているスマホ用のCSSが読み込まれる かつ 管理画面で設定したカスタムCSSは読み込まれません(head内でCSSのlinkタグを設定している場合はそれは読み込まれます)。
この挙動は画面幅を狭くしてブレークポイントを切り替えただけでは気づくことができません。 今回使用したテーマではpreタグの色や余白などがスマホ用のCSSに上書きされることに運用し始めた後に気づいたためめちゃくちゃ焦りました。
はてなブログのデザインをカスタマイズして、プレーンではない状態で運用するのならばここのチェックをONにし、スマホ用CSSが適用されないようにするほうが良いと思います。
コードハイライトにhighlight.jsを使う
はてなブログにはデフォルトのシンタックスハイライトもありますが、カラースキームにatom-one-darkを使用したかったため、highlight.jsを用いる形にしました。また、highlight.jsを使用することで行番号表示プラグインが使えるため、これを併用して行番号つきのシンタックスハイライトを実現することにしました。
headタグにシンタックスハイライト用のスクリプトとスタイルを追加します。CDNがあって非常にありがたい限りです。
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/highlightjs-line-numbers.min.js"></script>
<script>hljs.highlightAll(); hljs.initLineNumbersOnLoad(); </script>
この設定にあたってはこちらのブログを参考にさせていただきました。
これだけでは行番号の表示が崩れる部分もでるため、適宜スタイルは調整します。
作業項目は他にもなんだかんだありましたが、これでようやく移行は終わり、はてなブログにブログを載せ替えて発進させることができる状態になりました!
移行してみてどうたったか
ブログを書く環境が変わって、めちゃくちゃ盛り上がった・・・!!という話にはさすがになりませんが、今まで執筆していない方も、過去書いてくださっていた方も、ブログ記事の公開で迷うことなどはなく、公開作業などを行っていただけたので、執筆の環境については少なくとも悪くなったということは無いんじゃないかなと思います。公開フローを簡易化したことについては今のところ目立ったデメリットはない状態です。
とはいえブログ運用にあたってはこれからが本番なので、いろいろな面でのどうだったか、についてはこれからの話になりそうです。 執筆環境の改善点がバンバン浮き彫りになるぐらいブログ執筆が活発になったらそれはそれで嬉しいですね。
あとCLI環境は用意したものの今のところ自分を含め誰にも使われていないので、なくてもいいのではないかなと思います。
長い文章になりましたが、ここまでお付き合いくださりありがとうございました。
開発メンバー募集中です
Stockmarkでは一緒にプロダクトと組織を成長させていただける方を広く募集しています カジュアル面談からぜひお気軽にご連絡ください。