📦

AppBrewのテックブログをはてなブログからZennにお引越ししました!

に公開

最近リアルでもお引越しすることになったktyshmtです!
今回はタイトル通り、はてなブログからZennへテックブログのお引越しを行ったので、その記録を残しておきます!

経緯

もともとZennには主に開発組織・開発体制技術スタック・開発フローに関する資料を置いていく予定でした。しかし既存のテックブログがはてなブログに残っている以上、情報発信が分散してしまうというデメリットがあり、統合することを決意。

Zennについて改めて調べてみたところ、

  • GitHubによる連携が可能で、エンジニアがコンテンツを管理できる
    • Gitによるバージョン管理や、普段の開発と同様にレビューが行えて便利!
  • Publicationに投稿する形式にすれば、個人のアウトプットとして残せる
    • メンバーによる発信へのモチベーションUP!
  • GitHub連携とZenn CLIを活用することで、ローカルのエディタで執筆を行いやすい
    • 特に最近だとLLMによるサポートを受けやすくて嬉しい!

といった長所をもっており、はてなブログよりも継続的に発信していきやすいと考え、過去記事の移行を行うことになりました。
それでは実際に行った作業手順を紹介していきます!

作業手順

ZennのGitHub連携とディレクトリ構造

今回は AppBrewのZennアカウント を作り基本的にはそこに転載、希望するメンバーについては個人のアカウントでPublicationに投稿するという形を取ることにしました。
普段使っているGitHubのOrganization内にプライベートリポジトリを作成し、公式の記事を参考に連携とZenn CLIの導入を行います。

https://zenn.dev/zenn/articles/connect-to-github

Zenn CLIのディレクトリ構造に関するルールは以下にまとまっていて、とってもシンプルです。

https://zenn.dev/zenn/articles/zenn-cli-guide

articles/ books/ images/ 以外であれば自由にディレクトリを作ることができるので、今回の移行に用いるスクリプトなどもまとめてGit管理できちゃいます。

はてなブログからのエクスポート

はてなブログは記事データを管理画面から簡単にエクスポートでき、Movable Typeという形式で書き出されます。

https://help.hatenablog.com/entry/export

この形式について全く知見がなかったのですが、中身は基本的にはHTMLなので、以下を参考に Turndown を使用して変換を行いました。

https://secon.dev/entry/2020/10/09/190000-hatana-to-markdown/

Front Matterの準備

記事のエクスポートが完了したらテキストデータとして扱えるので、LLMに見てもらい放題です。
Zennでは記事のメタデータを、Front Matterと呼ばれるMarkdown上部にyaml形式のテキストで指定します。
今回は以下の要素をLLMに適宜生成してもらいながら埋めていきました。

---
title: "タイトル" # はてなブログのときのものをそのまま採用
emoji: "📦" # LLMにより生成
type: "tech" # "idea" か "tech" かを人の目で判断。なんとCursorが大体意図通りに埋めてくれました!
topics: ["はてなブログ", "zenn", "zenncli"] # LLMにより生成しつつ、人力修正
publication_name: appbrew # ここでどのPublicationに投稿するか指定できます。今回は固定
published: false # 一旦下書きで作成
published_at: # 過去の投稿日時を入れることで、ちゃんとその時に投稿された記事として扱われます! 一度アップロードすると変更できないようなので注意!
---

さらに、Zennは記事のURLの末尾をslugと呼んでいて[1]、これにはMarkdownファイルの名前が使われることになります。
slugもLLMにより生成して、記事のメタデータが揃いました!

スクリプトによる画像保存

TurndownでざっくりMarkdownに変換したら、画像を images/ ディレクトリに保存していきたいです。
ここもCursorにざっくり書いてもらったものに sleep など人の手による温かみ[2]を加えつつ、変換と保存をスクリプト内で完結できるようにしていきます。
images/ ディレクトリ内の構造は自由ですが、記事ごとに分かれていたほうが管理しやすいため、先ほど生成したslugをサブディレクトリとして作り、その中に画像を格納するとよいでしょう。

ZennのMarkdonw記法への対応・細かい修正

Markdownに対応している各種プラットフォーム同様、Zennでも色々な記法を使うことができ、以下にまとまっています。

https://zenn.dev/zenn/articles/markdown-guide

せっかくならこれらを活用して整った記事にしたいので、あとはひたすら調整です。もとの記事の不備などもまとめてここで修正してしまいました。処理の一部を列挙します。

  • 見出しレベルを調整する関数
    • 記事によって見出しレベルがH1スタートだったりH3スタートだったりしていましたが、Zennの推奨見出し開始レベルは H2(##[3]だったので、それに合わせました
  • Google Chart APIの数式を処理
    • 一部の数式がGoogle Chart API(廃止済み)のURLとしてエクスポートされていたので、取り除き、適宜インライン・ブロック形式の数式へ
  • コンテンツの埋め込みに対応
    • URLだけを貼り付ける形式にしたり、各プラットフォームに合わせた対応が必要です[4]。もとの記事のデータがiframeになっていたりするので、分岐を入れて調整となります。
  • はてなキーワードのリンクを除去。はてなIDのリンクを処理
    • コンテンツの埋め込み対応の前にやらないと、はてなキーワードが丁寧に埋め込まれていきます👶 はてなのユーザーIDもリンクになっており、ちょっと処理が必要でした。
  • 目次を削除。画像のキャプション、注釈、脚注を変換
    • 見落としがちです[5]が、このあたりも要調整

Turndownは addRule で変換時のルールを追加できるので、こちらを活用しています。一部の処理はTurndownに入れる前のHTMLの段階で変換してしまったほうがやりやすいところもあったので、そこも含めてちまちま調整にはなりました。
とはいえ具体的な処理はだいたいLLMがやってくれるので、人間の仕事は主ににらめっこと指示出しと、最後は気合です。[6]

公開手順

アップロードと公開

GitHubの連携が済んでいれば、あとはpushしてdeployを待つだけです。本当に体験が良い!
ただし今回は念の為 published: false としていたため、一旦記事は下書き状態となります。下書きで問題ないことが確認できたら、 published: true としてpushしましょう!
なお一気に公開[7]しようとすると意図的に途中でストッパーがかかるようになっているようで、最終的にはポチポチ手動で公開することになりました[8]👶

自分で公開するメンバーへの共有

上記のような細かい変換を各自に任せるのは大変なので、変換済みのものを配って公開できるようにしていきます。希望するメンバーの中には非エンジニアもいたため[9]、できるだけ整った状態でパスすることに。

一括で生成していた記事データを、メンバーごとにブランチに分けていきます。
ブランチ名と対象のslugを指定することで、必要な記事と画像データのみを持つブランチを一気に生成するスクリプトを用意して走らせます[10]

その後ZennとGitHubのアカウントをヒアリングし、ZennのPublicationと、GitHubのプライベートリポジトリにそれぞれ招待します。
各自のブランチ名と、そこからの連携方法をレクチャー[11]すれば、あとは無事公開できることを祈るだけ!招待リンクの有効期限切れ以外はほぼトラブルなしでした!
事前にこちらで面倒なMarkdown作りを終わらせておいたので、比較的スムーズに済んだかなと思います!

注意点

同じPublicationに別のユーザーが同一slugで下書きを作っていると、衝突してアップロードできずエラーになってしまいます。共有前に消しておく必要がありました[12]
少なくとも下書き状態であれば、アップロード後に削除することで別のユーザーによる投稿は問題なく可能でしたが、削除はWebの管理画面から行う必要があるので、記事が多い場合はたくさんポチポチしないで済むようそもそもアップロードしないほうが良さそうです。

まとめ

サクッと終わるだろうと思って宣言したテックブログ移行でしたが、地味に細かいフォーマット周りで苦労しました。
こういう場面でのLLMの強みと、Zennの体験の良さを感じられて良かったです。
Zennで発信、やっていき!

https://open.appbrew.io/b8ee07444fe243e2ab5b37f60572dbba

脚注
  1. Zennのスラッグ(slug)とは - Zenn公式 ↩︎

  2. アクセス先に迷惑をかけないようにするというぬくもり ↩︎

  3. ZennのMarkdown記法一覧 見出し - Zenn公式 ↩︎

  4. ZennのMarkdown記法一覧 コンテンツの埋め込み - Zenn公式 ↩︎

  5. 見落としていました。僕だけかもしれない ↩︎

  6. MCPなどを活用することで、にらめっこも代替できたのかも...? ↩︎

  7. 今回は6記事以上でストップがかかりました ↩︎

  8. 数が膨大な場合はお問い合わせするようにと記載がありました ↩︎

  9. 非エンジニアでも簡単なGit操作ならできちゃうメンバーがいるAppBrewはとても良い環境だと思います! ↩︎

  10. こういうちょっと手間がかかることをLLMにサクッとやってもらえるのは本当に良いですね! ↩︎

  11. 当該ブランチをcloneしてmainブランチにしてpushする、というスニペットを共有しました ↩︎

  12. これはテストとして自分の記事をアップロードするときに気づけたのでトラブルとしてはノーカン! ↩︎

AppBrew

Discussion