
こんにちは!Techouse の人材プラットフォーム事業部でサーバーサイドエンジニアを担当している aki と申します。2024年4月に新卒入社し、現在は事業部内で利用する社内システムの開発を担当しております。
同時に開発者ブログの運営チームにも所属しており、立ち上げ時に基盤構築を担当しました。運用が開始した後も、継続的に運用改善に取り組んでおります。本記事では、Techouse の開発者ブログの運用についてご紹介します。
Techouse の開発者ブログ運用
皆さんの会社では、開発者ブログの運用はされていますでしょうか?Techouse では、2024年の4月に開発者ブログを立ち上げました。
それから2024年8月1日現在までに合計 28 本の記事を投稿しており、月間平均で 7 本の記事が公開されていることになります。なかなか良いスタートダッシュを切れているのではないでしょうか?そこで今回は、 Techouse が開発者ブログを運用する上での取り組みについてご紹介します。
開発者ブログの目的
上述の記事でも述べられているように、開発者ブログの目的は、インプット・アウトプットを促進することによる、 Techouse の各エンジニアのさらなる成長にあります。
Techouse は今年で13期目を迎えますが、エンジニアチームはまだ発足して3年目で、発展途上の段階です。そのため、一貫した組織文化がまだ確立されていません。一方で、積極的にエンジニア採用を行っており、シニアエンジニアや新卒エンジニアに続々とご入社頂いています。人員が急増する中、アウトプットを自然に行う文化を根付かせることは、今後のエンジニア組織の成長に不可欠です。
このような背景の中で、対外的なアウトプットの皮切りとして、今年度から開発者ブログを開始しました。このブログの目的は、継続的にアウトプットを行う文化の基盤とすることとなっております。
ブログを継続するために
しかし、技術的なアウトプットをする文化が形成されていない中で、事業開発で忙しいエンジニアたちが継続的にブログを書き続けることは並大抵のことではありません。そもそも、会社の名前を背負って公開される記事を執筆するというのは、心理的にハードルの高い作業です。加えて、ネタの準備、事前の調査、文章の構成、レビューなど、 1 つの記事を書くためには多くの時間と労力が必要です。
そんな中で、それ以外の部分にストレスがあっては、継続的なブログ運用は困難でしょう。したがって、運営チームでは、持続可能な開発者ブログの更新、SDBs(Sustainable Developers Blogs)を実現するために、立ち上げに取り組んできました。
開発者ブログの運用フロー
我々は上記の目標を達成するために、以下の要件を満たす必要があると考えました。
- ブログを GitHub で管理できること。
- ローカル環境で Markdown で執筆できること。
- 画像のアップロードや OGP 画像の選定などを自動化すること。
1.と2.については、ブログの執筆作業をエンジニアが慣れ親しんでいる環境で行えるようにすることが重要だと考えました。3.については、記事の執筆以外の手間を排除する目的で要件に設定しました。
それでは、 Techouse がどのように開発者ブログを運用しているのか、ブログの執筆開始〜公開までの流れをご紹介します。ワークフローの作成にあたっては、以下の記事を多く参考にさせて頂きました。
執筆者へのリマインド
Techouse では、ブログの公開予定及び初稿の締切日をスプレッドシートで管理しています。しかし、運営チームから毎度リマインドを行っていると、執筆者・運営チーム共に負担が大きく、漏れも生じやすいです。
そこで、リマインダーを GAS で実装しました。初稿の締切日の特定の日数前になると、社内 Slack のブログ用のチャンネルに『ブログ進捗管理マン』からリマインドのメッセージが送信されます。

これにより、リマインドの漏れを無くすことが出来ました。また、リマインドをブログ進捗管理マンに任せることで、お互いに心理的な負担を軽減することが出来ていると思います。
他にも、 Techouse では開発チームのタスク管理に JIRA を使っているのですが、ブログの執筆も JIRA のチケットとして起票することになっています。(Techouse では業務時間にエンジニアがブログを執筆することを推奨しています。)
これらにより、忘れることなくブログのレビュー・リリースを進めることができています。
記事の執筆
ブログの執筆は、 GitHub Actions の実行から始まります。執筆者は、ブログのリポジトリから下書きを作成する Action を選択して実行します。
実行時には、以下の情報を入力する必要があります。
- ブログのパス( Action によって作成される branch、及び公開時の記事のパスに利用されます)
- ブログのタイトル
- 著者名

下書きを作成すると、 Pull Request が作成されます。

この間、GitHub Actions のランナー上で以下の処理を行っています。
- OGP 画像の生成
- はてなブログ上での下書き記事の作成
- branch と Pull Request の作成
1. のOGP画像の生成は、Cloudinaryという CDN サービスを利用しています。
画像のホスティングを行うにあたって Amazon S3 や Google Cloud Storage などのサービスも検討しましたが、 Cloudinary は画像の簡単な編集機能が API として提供されており、開発者の負担を軽減できると判断しました。Cloudinary ではSDK も様々な言語で提供されており、 Techouse では Ruby SDK を利用して OGP 画像の生成処理を実装しています。
具体的には、以下の Ruby スクリプトを GitHub Actions 上で実行しています。
# ogp_uploader.rb # frozen_string_literal: true require 'rubygems' require 'bundler/setup' require 'cloudinary' require 'cloudinary/search' require 'cloudinary/uploader' require 'cloudinary/utils' require_relative './image_uploader' require 'uri' class OGPUploader def self.call(title, path, author) new.call(title, path, author) end def call(title, path, author) original_ogp_url = Cloudinary::Search.expression("public_id=ogp_base").execute["resources"][0]["url"] ImageUploader.call(original_ogp_url, path, "ogp", options(title, author)) end private def options(title, author) [ {:width=>1200, :height=>630, :crop=>"scale"}, {:overlay=>{:font_family=>"notosansjp.ttf", :font_size=>60, :font_weight=>"bold", :text_align=>"left", :text=> title }, :color=>"#FFFFFF", :width=>1000, :height=>300, :quality=>"auto", :crop=>"fit"}, {:flags=>"layer_apply", :gravity=>"north_west", :x=>100, :y=>150}, {:color=>"#FFFFFF", :overlay=>{:font_family=>"notosansjp.ttf", :font_weight=>"bold", :font_size=>40, :text_align=>"left", :text=> author }}, {:flags=>"layer_apply", :gravity=>"south_west", :x=>100, :y=>75} ] end end
ImageUploader クラスの実装は以下の通りです。
# image_uploader.rb # frozen_string_literal: true require 'rubygems' require 'bundler/setup' require 'cloudinary' require 'cloudinary/search' require 'cloudinary/uploader' require 'cloudinary/utils' require 'uri' class ImageUploader # @param [String] image_path アップロードする画像のパス # @param [String] folder アップロード先のフォルダ名 # @param [String] public_id アップロード先のファイル名 # @param [Array] transformation Cloudinaryの画像変換オプション # @return [String] アップロードされた画像のURL def self.call(image_path, folder, public_id, transformation = []) new.call(image_path, folder, public_id, transformation) end def call(image_path, folder, public_id, transformation) options = { :folder => folder, :public_id => public_id, :transformation => transformation } upload = Cloudinary::Uploader.upload(image_path, **options) upload["url"] end end
OGPUploader クラスは、OGP 画像の生成処理を行うクラスです。事前にアップロードしてある OGP 画像のベース画像に、記事のタイトルと著者名をテキストオーバーレイすることで、記事ごとに異なる OGP 画像を生成しています。
ImageUploader クラスは、単なる Cloudinary SDK の画像をアップロードするメソッドのラッパークラスになっています。
このように、たった数十行の Ruby スクリプトで OGP 画像の自動生成処理を実装することができました。
少し注意が必要な点として、 Cloudinary のテキストオーバーレイは、デフォルトでは Sawarabi Gothic のみのフォントを提供しています。他のフォントを利用する場合は、そのフォントを事前にアップロードする必要があります。
今回は Noto Sans JP を利用するため、事前にそのフォントファイルのアップロードを行っています。この際、フォントファイルの Delivery type を Authenticated に設定する必要があります。
Delivery type は、ファイル毎のアクセス制御を行うための設定です。Authenticated に設定することで、ファイルへのアクセスに認証を必要とするようになります。この設定は Cloudinary のコンソール上から変更することが出来ず、API を利用して設定する必要があります。私の場合は、 ローカルマシンから Cloudinary CLI を利用して、以下のようにフォントファイルのアップロードを行いました。
$ cld upload notosansjp.ttf --type authenticated
次に、2. のはてなブログへの下書きのアップロードです。
Techouse では、はてなブログをブログのホスティングサービスとして利用しています。はてなブログは Markdown での記事の執筆がサポートされている他、 API が提供されているため、 下書きの更新や記事の投稿を自動化することが可能です。
今回は GitHub Actions を利用することで、 GitHub 上でのイベントをトリガーとした自動化を実現しました。また、 API の利用にははてなブログの CLI クライアントである blogsync を利用しています。
下書きのアップロードの前に、 GitHub Actions 上で以下のようなディレクトリを作成します。
${PATH}
├── entry.md
└── images
└── .gitkeep
ここで、${PATH} は Action の実行時に入力したブログのパスです。このディレクトリは、執筆者の作業ディレクトリになります。entry.md は記事の本文を記述するファイルで、 images/ ディレクトリは記事内で利用する画像を配置するディレクトリとなっています。
その後、 entry.md を下書きとしてはてなブログにアップロードします。実行時に入力された記事のタイトルとパスを blogsync の post コマンドのオプションとして渡すことで、 指定された内容の下書き記事を作成しています。
$ ~/go/bin/blogsync post --draft --title="${TITLE}" --custom-path="${PATH}" "${DOMAIN}" < draft_template.md
これによって、 Workflow の実行の際に渡したタイトルとパスに基づいて、はてなブログに下書き記事が作成されます。
最後に、 branch と Pull Request を作成します。Pull Request には簡単なチェックリストと、 はてなブログのプレビュー用のURL をコメントに記載するようにしています。
記事の更新
下書き記事を更新する際には、執筆者はローカルでの変更をリモートに push するだけで、自動的にはてなブログの下書き記事が更新されるようになっています。具体的には、 push をトリガーとして、以下の処理が GitHub Actions で実行されます。
- 画像のアップロード
- textlint による文章のチェック
1. では、記事内で利用する画像を Cloudinary にアップロードしています。はてなフォトライフを利用することも検討しましたが、画像の管理を一元化するため、画像のホスティングは Cloudinary に統一することにしました。
下書きの際に利用する画像については、全て images/ ディレクトリ以下に配置して頂く運用になっています。それらの画像を上述の ImageUploader クラスを利用してアップロードしています。ImageUploader クラスの .call メソッドは返り値としてアップロードされた画像の URL を返すため、 entry.md でローカルの画像を参照していた URL を置換することで、記事内の画像を Cloudinary にアップロードした画像に差し替えています。
2. では、 textlint による文章のチェックを行っています。
記事の誤字脱字や、文章に関する指摘は本質的ではない上、レビューの往復回数が増える原因となり、執筆者と運営チームの双方にとって負担になります。そこで、 textlint による文章のチェックを GitHub Actions で自動化し、文章の品質を向上させることを目指しました。textlint の実行結果は Pull Request のコメントとして出力されます。

lintの修正はルールの選定が難しく、未だ的確なルールセットをみつけられていません。よって、 textlint はレビューとしてはつけずに、執筆者側で lint の修正に従うかどうかを選択できるようにしています。
レビューと記事の公開
下書きが完成したら、 Pull Request のレビュアーに運営チームのメンバーを指定してレビューを依頼します。
レビューは GitHub 上での一般的なコードレビューと同様に行う事が出来ます。Slack 等でやりとりするよりもレビューのログがたどりやすく、情報が揮発しにくい点でかなり良い体験が得られていると思います。

レビューが完了し、Approveされた後に Pull Request をマージすると、 GitHub Actions がトリガーされ、記事が公開されるようになっています。Techouse の多くの開発チームで採用されている GitHub Flow に準拠する形になっており、執筆者が記事公開までの流れを直感的に理解しやすいフローになっています。
リリースが完了すると、運営チームがブログの公開を全社に通知します。この通知は人の温かみを残すため、手動で行っています。

今後の展望
以上が Techouse における現在の開発者ブログの運用方法となっています。
約4ヶ月が経過し、運用も安定化してきたところですが、今後の運用改善として、markdonwnlint を導入することを検討しています。
はてなブログの Markdown は Daring Fireball Markdown に厳格に準拠しているため、 無意識に記述ミスをしてしまう事例が散見されています。( ol、 改行の記法など)これらの指摘を自動化し、レビュアーの負担を軽減したいと考えています。
今後も継続的に運用改善を行い、絶やすことなくこのブログを更新していきたいと思います!
Techouseでは、社会課題の解決に一緒に取り組むエンジニアを募集しております。 ご応募お待ちしております。