この記事はjsys Advent Calendar 2025 19日目の記事です。気づけばもうクリスマスですね。かなり遅刻してしまいました。
こんにちは、にと(@nito_008)と申します。
私は今年、筑波大学学園祭実行委員会の情報メディアシステム局(jsys)というところで、ネットワーク部門Web担当長をやっておりました。Web担当長という役職は、その名の通りWorld Wide Webに関わることをすべてやる…と見せかけて、事実上Webフロントエンドリーダー的な立ち位置でした。
jsysでは局内向けの物品管理システム、委員会と学園祭に出店する学生とのやり取りをサポートする雙峰祭オンラインシステム、そして来場者の方に向けた特設サイト、企画検索システムなど様々なシステムをすべて内製で開発・運用しています。
今回はその中でも、私が主に担当した特設サイトと企画検索システムのフロントエンドで使われている技術について解説していきたいと思います。
特設サイト
まず最初に解説するのは、雙峰祭直前期に公開される特設サイトです。来場者向けの情報や注意事項が掲載されています。私は全体設計・ライブラリ選定・基本的な部分の実装、そして一年生に実装タスクを振る役割を担いました。今年は、実装を担当してくれた一年生の皆さんの協力もあり、雙峰祭が行われる約一か月前に公開することができました。
トップページはこんな感じです。

背景には昨年度の雙峰祭の映像を映像部門長が編集してくれた動画が埋め込まれています。(映像はネットワーク部門長セレクションだったりします。)ffmpegでVP9コーデックによる驚異の圧縮が行われています。また、今年の雙峰祭のテーマである「むすんで、ひらけ」のロゴは、これまた映像部門長に作成してもらったアニメーションがGIFで埋め込まれています。映像部門長には頭が上がりません。(お願いして1、2日で作ってくれました。本当に感謝。)
技術スタック
- Astro
- Fontsource
- Embla Carousel
メインのフレームワークはAstroを採用しました。Astroは軽量・高速なWebフレームワークで、ビルド時にJavaScriptを極限まで減らすのが特徴です。jsys内で静的サイトを作る際のデファクトスタンダードとなっていて、知見があったことも大きいです。
フォントは全てFontsourceから読み込むようにしています。npmパッケージとして手軽に追加でき、かつセルフホストなのでCDN経由より高速です。Fontsourceの変なバグを踏んでしっぽり明朝が適用されない事件などもあったりしましたが…
また、カルーセル部分にはEmbla Carouselという軽量なカルーセルライブラリを使っています。ライブラリが持っているのはカルーセルのモーション部分だけであり、スタイルを自由に記述できる点が気に入っています。
Linter/Formatter
- Prettier
- ESLint
- Stylelint
jsysではVSCodeユーザーが比較的多いため(もちろんNeoVim等のエディタを使っている方もいますが…)、VSCodeの設定でLint/Formatが自動で走るようにしていました。
{ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll": "explicit", "source.fixAll.eslint": "explicit", "source.fixAll.stylelint": "explicit" }, "stylelint.validate": ["css", "astro"]}こうすることで、ファイル保存時に自動でLint/Formatが走ります。これのおかげでCIが落ちる回数がかなり減った気がします。
開発支援系
- Plop
- Astrobook
Plopはテンプレートからコンポーネントの雛型を自動生成することができるジェネレータ、AstrobookはStorybookのAstro代替です。
Astrobookではこんな感じにstories.tsを書いてあげることで、Storybookのようにコンポーネントのカタログが見れます。
import type { ComponentProps } from "astro/types";
import Component from "./Component.astro";
type Props = ComponentProps<typeof Component>;
export default { component: Component,};
export const Default = { args: {} satisfies Props,};slotを文字列で渡す必要がある、GUIでPropsを変更できないなど、まだ未成熟ではありますが、単に静的サイトのコンポーネントを見るだけなら使えるだろうということで導入してみました。
今回は各コンポーネントを並行して実装していたので、レビューするときにそれぞれのコンポーネントを分けて確認できたのはかなり楽でした。ただ、Reactと違ってAstroではコンポーネントをフォルダごとに分けて管理するのが難しい(Component/index.astroとComponent.astroを同じように扱えない)ため、ファイルツリーで見たときに把握しづらくなってしまったのは課題でした。今回のような小規模なサイトなら問題ないですが、規模が大きくなると難しそうです。

Plopではテンプレートファイルを置いておくことで、対話形式でコンポーネントの雛型を作成できます。
---interface Props { text?: string;}
const { text } = Astro.props;---
<div> {text}</div>
<style> /* スタイルはここに追加 */</style>import type { ComponentProps } from "astro/types";
import Component from "./{{pascalCase name}}.astro";
type Props = ComponentProps<typeof Component>;
export default { component: Component ,};
export const Default = { args: {} satisfies Props,}npm run plop
> [email protected] plop> plop
? コンポーネントを作成したいフォルダを入力 (例:common) common? コンポーネント名を入力 (例:Footer) Component✔ ++ \src\components\common\Component.astro✔ ++ \src\components\common\Component.stories.ts企画検索システム
次に解説するのは、雙峰祭企画検索システムです。今年は昨年度より大幅に機能が追加され、お気に入り機能、投票機能、地図機能などが追加されました。これにより、フロントエンドもかなり複雑なものになりました。私は全体の設計・ライブラリ選定を担当し、実装の大部分は他の二年生が担当しました。
技術スタック
- React
- Tanstack Router
- jotai
- Panda CSS
- React Reaflet
- React Hot Toast
メインのフレームワークはReact、ルーティングライブラリはTanstack Routerを採用しました。Tanstack Routerは型安全なルーティングライブラリで、パスだけではなくクエリパラメータなども型安全に扱うことが可能です。また、ファイルベースのルーティングにも対応しているため、簡単にルーティングすることができます。
グローバルな状態管理にはjotaiを使っています。結局contextとjotaiとqueryの使い分けベストプラクティスは分からずにいます。どうするのがいいんでしょうか。
また、CSSライブラリとしてPanda CSSを採用しました。Panda CSSはスタイルをTSで型安全に書ける半面、インラインスタイルのようにも書けてしまうので可読性が難点でした。以下の記事を参考に、ファイルを分ける運用を試してみました。
地図にはReact Reafletを使いました。地図の背景となっている建物の画像は、デザイン担当が丹精込めて作成したものだったりします。地図上のピンも局員が一つ一つ丁寧にGoogle Mapで入力したものを変換しています。

投票時のトースト表示にはReact Hot Toastを使っています。
Query系
- Tanstack Query (旧React Query)
- openapi-typescript
- openapi-fetch
- openapi-react-query
バックエンドAPIから取得したデータの管理にはTanstack Query (旧React Query)を活用しました。これは非同期の状態管理ライブラリであり、fetch部分はopenapi-fetch、OpenAPIスキーマからTypeScriptの型を自動生成する部分はopenapi-typescriptを利用しています。
React Queryとの連携部分はopenapi-react-queryを使っているのですが、日本語の情報がほとんどなく、公式ドキュメントの情報も少ないので試行錯誤しながら使う部分もありました。このあたりの話は、別で記事にまとめているので気になる方はぜひご覧ください。
バックエンドのSwagggoが吐き出すopenapi.yamlがOpenAPI 2.0で、openapi-typescriptが対応している3.0に闇の変換をかます必要があり困ったりしました。
openapi-react-queryは薄いWrapperとして実装されています。OrvalやKubbといったオールインワンの型・APIクライアント生成ツールに比べてバンドルサイズが小さく、自分の使いたい機能を後から追加していくスタイルで気に入っています。最近はMock Service Workerと連携できるopenapi-mswを試したりしています。
Linter/Formatter
- Prettier
- ESLint
特設サイトとほぼ同じですが、Panda CSSを導入したためStylelintのみ消えています。Panda CSSはStylelintでできるCSSプロパティの並び替えなどがうまくできないのが難点ですね。eslint-plugin-perfectionistなどを使うとうまくできるんでしょうか。
開発支援系
- Plop
- Storybook
こちらでもPlop、そしてStorybookを導入しています。各コンポーネントは以下のようなフォルダ構造となっています。
components└─Component ├─index.tsx // Reactコンポーネントファイル ├─styles.ts // Panda CSSスタイルファイル └─index.stories.tsx // StorybookファイルPlopによるコンポーネントの雛型生成自動化はこちらの方が恩恵を感じました。ReactではComponent.tsxとComponent/index.tsxを同一に扱えるので、フォルダごとにコンポーネントを管理しやすいです。
あとがき
今年度の特設サイト、そして企画検索システムには私の持てるフロントエンド技術をほぼ全て注ぎ込みました。プロジェクトリーダーとしてプロジェクトを引っ張っていくのは初めてであり、技術面・マネジメント面でトラブルに直面してばかりでしたが、同時に大きな成長を感じた一年でした。
Webフロントエンドは技術の進歩が速く、新しい技術が次から次へと出てくる印象です。私が本格的にフロントエンドに触れ始めた1年前から比べてもかなり進歩しているように感じます。新しい技術に触れるのは好きなので、今後も新たな技術に触れつつ、より早く使いやすいWebサイトを作れるようになりたいものですね。