この記事は CyberAgent Developers Advent Calendar 2022 の 12 日目の記事です。
株式会社 WinTicket の竹内 (@dora1998) です。2022 年 7 月に開催した技術カンファレンス「CA BASE NEXT 2022」の Web サイトにおいて、テックリードとしてサイト構成の設計・実装などを担当しました。
本記事では、カンファレンス Web サイトでどのように登壇者やセッションの情報を取りまとめ、運用を行なっていたかを紹介します。
TL;DR
- Google スプレッドシートをデータベースとして扱い、登壇者やセッション情報を管理しました
- Google Apps Script で、シートから JSON 出力してデータを Git リポジトリに同期する仕組みを実装しました
- 静的サイトとしてビルドする際に、データ全体を直接 import せずに一部を Props として渡すことでパフォーマンス最適化ができます
CA BASE NEXT とは
CA BASE NEXT とは、サイバーエージェントの 20 代のエンジニア・クリエイターが中心となって創り上げる技術カンファレンスです。初めて開催された 2021 年度に続き、今回が 2 回目の開催となりました。
一般的な登壇のほか、バーチャル参加型クイズや対談などの新しい試みも多数行っています。各セッションはアーカイブ動画が公開されていますので、まだの方は是非ご覧ください。
カンファレンスの Web サイトは社員有志によって内製しており、今回は各部署から集まった 5 名のエンジニアで開発を行いました。
スクロールに連動したコンセプトを体現するデザインなどが注目を浴び、「SANKOU!」や「Web Design Clip」といった参考サイト集にも掲載いただきました。
技術選定とその背景
今回採用したコンテンツ管理フローを示したのが以下の図になります。
データソース: Google スプレッドシート
今回 Google スプレッドシートを大元のデータソースとし、登壇者・セッション・ジャンルとシートを分け、正規化したテーブルのような形で用意しました。
開発にあたっては扱いやすい反面、外部キーを手入力するとミスが起こりやすいため、以下のように VLOOKUP
関数や ARRAYFORMULA
関数などを用いて、入力したキーをもとに名前などが隣に表示されるように配慮しました。
データ同期: Google Apps Script から JSON 形式で Git コミット
今回のフローの肝である「シートからJSON 出力してデータを Git リポジトリに同期する」処理の実行環境としては Google Apps Script (GAS) を選択しました。
スプレッドシートからデータを取得することは Google Sheets API を使えばどのような言語・実行環境でも実現できますが、これには GCP のプロジェクトが必要となってしまいます。
昨年からサイトは AWS で運用されていたこともあり、この同期処理のためだけに GCP プロジェクトを各所と連携して新規に作成することは非現実的でした。そのため、今回は GCP プロジェクトによる認証が不要である GAS を選択しました。
GAS では、 onOpen
内で addMenu
を実行するとメニュー項目を追加できるため、ここに GitHub に同期するボタンを設置しました。
function onOpen(){
var sheet = SpreadsheetApp.getActiveSpreadsheet();
sheet.addMenu("メニュー", [{name: "GitHubに反映", functionName: "main"}]);
}
これを実行すると、以下のような Pull Request が作成されます。
サイト実装: ページに必要な項目だけを Props として渡して静的出力
Web サイト側では先述の GAS でコミットした JSON ファイルを import する形でデータを使いました。このとき、クライアントで実行されるコードで import してしまうと、JSON ファイル全体がバンドルされてしまいます。
本サイトでは Next.js を採用していたため、JSON ファイルを import するコードは全て getStaticPaths
, getStaticProps
のみに記述するルールを設け、ページは Props をもとに描画する形を取りました。
また、トップページではタイムテーブルで全セッションを表示する必要がありますが、ここでは概要文等は不要なのでそれらを削った形式で渡すといった工夫をしています。
セッションごとの詳細ページを例に、簡略化したコードのイメージはこのようになります。
import data from './data.json'; // スプレッドシートから出力した JSON
export type Props = {
session: Session;
};
export const getStaticProps: GetStaticProps<Props> = async (ctx) => {
const { slug } = ctx.params ?? {};
// このコードはビルド時のみ実行され、バンドル結果には含まれない
const sessionRepository = createRepository(data.sessions);
const session = sessionRepository.findOne({ slug });
return { props: { session } };
};
// props を使ってページを描画
const SessionDetail: NextPage<Props> = ({ session }) => {
return (
<div>
<h1>{session.title}</h1>
</div>
);
};
export default SessionDetail
よかったこと
サイトの更新にかかる手間がなかった
ワンクリックで同期できることから誰でもデータの反映が行えるようになり、セッションのタイトルやタイムテーブルなどの情報をすぐ更新することができました。
終了後もアーカイブ動画を随時追加したこともあり、フロー構築から全アーカイブ動画の公開完了に至るまでなんと50回以上も更新フローが実行されていました。
更新のハードルが下がることで、より視聴者に伝わりやすいセッションタイトルへ変更を行ったり、開催当日に一部アーカイブ動画を公開したりといった柔軟な運営が可能となりました。
新たなアカウントの発行・管理をせずに済んだ
Google Workspace や GitHub のみで完結でき、これらはメンバーが既にアカウントを持っていました。短期間のみ必要になる今回のケースにおいて、新たなアカウントの発行や管理が一切不要だったのは大きなメリットでした。ヘッドレス CMS を採用するパターンも考えられますが、今回は終了後もランニングコストを極力かけずにサイトは残したかったため、採用しませんでした。
課題
画像の取り込みは自動化できていない
今回の取り組みでセッション情報などのテキストで入稿する情報は同期可能となりましたが、付随する登壇者のプロフィール画像といった素材の同期は実装しませんでした。
これらの素材は Google ドライブで管理していたため、同様に API で自動化は可能でしたが以下の理由から手動でダウンロードや変換を行い、Git 管理下にコミットする形をとっていました。
- 全画像を取り込むと遅いが、差分更新を実装する工数に見合うほど頻繁に素材は更新されない
- WebP, AVIF 変換を毎ビルド時に行うと、全体のビルド時間がかなり長くなってしまう
更新のためにリリースできる状態を保つ必要がある
データはビルド時にページに反映されることから、一部文言の差し替えが発生した場合はリリースを行う必要があります。
そのため、当日まで出さない機能などは main ブランチに取り込まないか、本番環境では出ないように分岐するなどして、常にリリースできる状態を保つようにしていました。
おわりに
ここまで、本記事では主に Google スプレッドシートにある登壇者やセッションの情報を実際のサイトに同期する仕組みを紹介しました。
テックカンファレンスにおいて主役はあくまで当日のセッションですが、特にオンラインカンファレンスでは Web サイトも参加者の視聴体験を左右する重要な要素だと改めて認識しました。普段の開発とはまた異なり、特性に合った技術選定を考えるのはとても楽しかったです。
開発にあたっては、他社のカンファレンス関連の開発事例も大変参考にさせていただきました。この場を借りて御礼申し上げます。