こんにちは。PR TIMES のフロントエンドエンジニアをしています岩元 (@yoiwamoto) です。
今回は、先日改善を行ったフロントエンドのプレビュー環境の自動生成の構成について紹介します。
PR TIMES のフロントエンド構成
本稿での「フロントエンド」は、React 実装にリプレイスして現在進行形で機能追加・改修が行われているページのことを指しており、これらのページはどれも SPA 構成をとっています。

そのため、ここでいう「環境」の実態はバンドルされた JavaScript と、その特定のバンドルを読み込める状態であり、SSR サーバーなどのいわゆる動作環境ではないことをご留意ください。
プレビュー環境とは
まず、本稿における「プレビュー環境」について、経緯と合わせて定義します。
PR TIMES では、一定以上の規模の機能開発ではフィーチャートグルを活用していて、一般のユーザーに隠蔽された部分の変更は、いわゆるリリースブランチを切らずに未完成であっても細かく本番にリリース (main にマージ) していく体制をとっています。
それぞれの変更は、リリースして問題ないかどうか QA チームによって検証が行われます。これは、社内からのみアクセス可能な環境にそのブランチの変更をデプロイして行われています。
ここで、それぞれの変更の確認を行うには、ブランチ単位で検証環境が用意できる必要があります。本稿ではこのトピックブランチ単位の検証環境のことを「プレビュー環境」とします。
実は、この環境を社内では現状「マルチステージング」と呼んでいるのですが、一般的なステージングの概念とはずれている可能性があるため、ここでは敢えて「プレビュー環境」としています。
ちなみに、フィーチャートグルの運用についてはバックエンドエンジニアの江間さん (@app1e_s) の PHP カンファレンス登壇時のスライドをご参照ください。
課題感
今回のブランチ単位のプレビュー環境を作る前も、もちろん同じ体制で QA が必要でした。
どのような構成を取っていたかというと、QA 担当者単位で S3 バケットを用意し、確認時に担当者がコマンドで自分のバケットに対してデプロイを行い、(Slack からデプロイできるように簡易化されています) バックエンドサーバーのフロントエンド環境切り替え画面で自分の環境を指定することで、自分のバケットの JavaScript が読み込まれるようになり、検証を行える、というものでした。

この構成は、これまでのところ問題なくワークしていましたが、開発組織のスケールにあたっては以下のような点に課題を抱えていました。
環境の変更にコストが生じる
それぞれに専用の S3 バケット、Fastly サービスなどがあり、これらは terraform で静的に管理されています。そのため、環境を増やしたいまたは減らしたい場合、SWE がマニュアルでクラウドリソースを更新する必要があります。
動作確認のたびに手動でデプロイをトリガーする必要がある
バケットが担当者単位で用意されているため、あるブランチをデプロイするとその環境のそれまでの状態は上書きされてしまいます。そのため、確認を行うブランチをあらかじめデプロイしておくようなことはできず、担当者は確認の直前に必ず手動でデプロイをトリガーして環境を更新する必要があります。
ステージング環境へのデプロイの時間は、別の取り組みで最近大幅に改善されたのですが、当時のデプロイは5分程度かかっていました。あるチケットの QA 前に必ず5分のオーバーヘッドが生じることは課題であったと言えます。
複数人で共用している環境で順番待ちが発生する
また、担当者単位とありますが、最近は QA メンバーだけではなく SWE もプレビュー環境での確認を行うことが増えています。リソースが静的に管理されている都合上、全 SWE 分の環境を用意するわけにはいかず、QA メンバーほど頻繁に使われるわけでもないということもあり、共用の環境を用いてこの確認を行なっていたりしました。
この共用の環境は複数人が使うので、自分が確認を行いたいタイミングに、別のメンバーが別のブランチをデプロイし確認作業を行なっていた場合、次にプレビュー環境が使いたいメンバーはその作業が終わるまで待たなければなりません。
このように、これまでの構成では、プレビュー環境での確認作業が他のメンバーの確認作業をブロックしてしまうことがありました。
どのように変更したか
上記のような課題感から、以下のように変更を行いました。

環境をフォルダで分けるように変更
環境を静的に管理していると、変更のたびに少なからず工数が発生してしまうので、terraform 管理するようなレイヤのリソースは切らず、一つの S3 バケット内で、フォルダで環境を分けるようにしました。
環境を担当者単位ではなくブランチ単位に変更
これによって、確認のタイミングに関わらず、あらかじめ全てのブランチをデプロイしておくことが可能になり、QA 直前の手動デプロイが省略できます。
また、ブランチ単位で、環境はどちらにしろ自動で最新の状態に更新されるため、どの環境を使用しても、他のメンバーの確認作業をブロックしてしまうことがなくなりました。
バックエンドサーバーでは、環境切り替え画面で Cookie にブランチ名を set し、以下のような PrTimesFrontend::getUrl()
で得られた URL を script の src として設定しています。
class PrTimesFrontend {
private CookieManager $cookie;
public static function getUrl()
{
$branch = $cookie->get('branch');
return PRTIMES_FRONTEND_URL . '/' . urlencode($branch);
}
}
また、合わせて以下のような改善を行いました。
Pull Request の作成・更新時にブランチを自動でデプロイ
GitHub Actions の pull_request イベントでブランチをデプロイするようにしています。
抽象化した job の定義は以下のようになります。
preview:
name: Preview 🚀
runs-on: ubuntu-latest
needs: [build]
steps:
- name: Checkout
uses: actions/checkout@master
# build step でキャッシュされたビルド生成ファイルをリストア
- name: Restore
uses: actions/cache@v3
id: restore
with:
path: ./apps/prtimes/dist
key: prtimes-ci-build-artifact-cache-${{ github.run_id }}
- name: Exit when no cache hit
if: steps.restore.outputs.cache-hit != 'true'
run: exit 1
- name: Deploy
run: >
aws s3 cp --recursive
apps/prtimes/dist s3://staging-bucket/${{ github.event.pull_request.head.ref }}/
--acl public-read
--cache-control "public, max-age=60"
--metadata-directive REPLACE
型検査や lint などのその他の CI に組み込まれており、Pull Request の作成・更新からおよそ1分程度でデプロイが完了しています。
ちなみにこれが1分で終わるようにするために、Webpack → Vite への移行や、CI の並列化などを行なっています。これについては別で記事を公開する予定です。

Pull Request のクローズ時にプレビュー用のフォルダを削除
ブランチ単位でプレビュー環境を作成するようにしたので、これ以降二度と使われないフォルダが無限に増えていくことになります。そのため、Pull Request がクローズした時に、対象環境のフォルダを削除する workflow を動かすようにしています。
定義は以下のようになります。
(※本題に関係ない部分は省略しています。また URL はサンプルです。)
name: Clear artifacts for Preview
on:
pull_request:
branches:
- main
types: [closed]
jobs:
clear:
runs-on: ubuntu-latest
steps:
- name: Clear ✨
if: github.event.pull_request.head.ref
run: >
aws s3 rm --recursive
s3://staging-bucket/${{ github.event.pull_request.head.ref }}/
結果
この変更によって、QA メンバーがプレビュー環境で QA を行う際に、手動でデプロイをトリガーして5分間待つ必要がなくなり、切り替えページにブランチを入力するとすぐに作業に入れる様になりました。
また、プレビュー環境が自動で生成されるようになったことで、実装者やレビュワーが手軽にプレビュー環境で動作確認をすることができるようになりました。
コードレビュー時に挙動が確認されるべきかはさておき、実装者やレビュワーがコードだけでなく動作に関心を持ちやすくなることは、当たり前かもしれませんがいいことだと思っています。
今後
今回の変更によって、プレビュー環境へのアクセスはある程度容易にはなりましたが、残っている大きな課題として、「環境を指定したプレビュー用の URL が発行できない」という問題があります。
例えば、あるブランチで作成した UI をデザイナーにブラウザで確認して欲しい時、今はブランチ名を伝えて、デザイナーがブランチ切り替え画面にそれを入力・submit しないと、対象の環境確認できません。
これは部署内であれば大きな問題ではないかもしれませんが、普段ステージング環境を触らないようなメンバーに同じような確認を依頼したい場合、まだハードルが高いように思います。
フロントエンドで (HTML の) 配信サーバーを持っているなら、環境ごとにサブドメインで branch-a.staging.com、branch-b.staging.com のように URL で環境を指定する構成も可能ですが、現在はそれが難しい状態です。
実は一度、似たような発想で、クエリパラメータにブランチを指定して URL を踏む (GET) ことで自動でブランチを切り替える以下のようなエンドポイントをバックエンドサーバーに用意しました。
/set_branch?branch=feature/add-button
Cookie の操作のような副作用のある処理を GET で実行できるのはよくないという懸念はありましたが、あくまで社内ツールであるということと、環境を指定した URL を発行できるメリットが勝ると考えました。
ただ、同じドメインでこのようなエンドポイントがあると、「例えば feature/add-button ブランチの確認作業中に、ブラウザバックやタブ変更などの際にいつの間にか切り替え URL を踏んでしまい、feature/update-modal ブランチに切り替わっていた」のような、品質保証的に大きく問題のある事象が発生する可能性があったため、この方法は避けました。
このような URL を発行する方法を検討し、導入していくのが今後の課題です。
一緒に働く仲間を募集しています!
開発本部では現在、フロントエンドエンジニアを含め、以下のような複数ポジションで積極的に採用を行っています。
- バックエンドエンジニア
- フロントエンドエンジニア
- デザイナー
- PdM
- QA エンジニア
- インフラエンジニア
- コーポレートエンジニア
また、先日 2/1 には、福岡にサテライトオフィスを開設しており、福岡での現地採用、および U・Iターン希望のエンジニアの採用も行なっていきます!

興味を持っていただけた方は、ぜひ以下より情報をご確認ください!