はじめまして。東京科学大学理学院数学系修士1年の岩崎丈至です。8月3日から6週間にわたって、LINEスキマニのフロントエンド開発にインターンシップと して参加しました。本稿ではその期間中に行った、メインページにおけるパフォーマンス改善についてご紹介させていただきます。
LINEスキマニとは
LINEスキマニは、LINE 上で仕事探しから応募までを完結できる、スポットワークサービスです。働きたい人と人手を必要とする事業者をつなぎ、日々多くのマッチングを支えています。

背景と課題
LINEスキマニの求人一覧ページは、多くのユーザーのメインページとなっています。しかし、Lighthouse や PageSpeed Insights でメトリクスを見るとパフォーマンスに関して改善を要する指標がいくつかあることが分かります。

さらに、Core Web Vitals には実際の求人情報が表示されるまでの時間が直接現れません。しかしながら、今回の計測では最初の求人情報が表示されるまで約 3,500 ms かかりました。

これらの問題は求人一覧ページが CSR 前提のページであり、SSR によって解決が期待されるものの、それには以下のような技術的制約がありました。
- 検索条件が多様で URL が複雑
- 無限スクロール型のリストに求人情報を表示している
今回はスコープをログインしていない状態での初期表示速度の向上に絞った課題の解決を目標としました。
以前の実装
改修前の、求人一覧が表示されるまでの流れは以下の通りです。
解決策
求人一覧ページに対して以下のような改修を行いました。
LINEスキマニには求人詳細ページなどで使われている既存の SSR 基盤があります。今回はこれを活用し、TanStack Query のリクエストキャッシュに、あらかじめプリフェッチした求人情報を載せる方針としました。
検索パラメータの整理
TanStack Query のリクエストキャッシュを正しく管理するには、取得条件ごとにデータを一意に識別できる queryKey が必要です。しかし、求人一覧ページの検索パラメータは多様で、検索条件に影響するもの・しないもの・UI の状態変化に伴って書き換わるものが混在しており、これらを直接 queryKey に利用することはできません。そこでまず、検索パラメータの整理から着手しました。
具体的には、検索条件に影響しないパラメータをキャッシュから除外したうえで、検索条件に影響するパラメータに対するバリデーションおよび正規化(重複の除去・一意な順序への整列・既定値の省略)を行う処理を実装しました。
?prefectureIds=47,13,1&occupationIds=3,1,3&date=2026-09-09
↓
prefectureIds=[1, 13, 47], occupationIds=[1, 3]
この改修の副次的な効果として、パラメータの表記ゆれが排され、検索条件ごとに一意な URL を定められるようになりました。これにより求人一覧ページへ canonical タグを導入できるようになったため、並行して meta タグの整理も行っています。
求人情報一覧の SSR コンテンツの描画
求人情報一覧は無限スクロール型のリストなので、SSR でプリフェッチしたデータを hydration 後の初期値として引き継ぎ、ユーザー情報と検索条件の解決後に通常のクライアント側でのクエリへ移行します。
キャッシュの導入
上記の実装によって SSR は実現できたものの、今度は TTFB (Time to First Byte) の増大を招いてしま いました。

一般に 800 ms 以下が良好であるとされる(cf. https://web.dev/articles/ttfb)ので許容範囲内ではあるものの、多くのユーザーが訪れる求人一覧ページに影響するのはそれなりに問題です。ローカルサーバ上で検証した、あるリクエストの TTFB の内訳は以下の通りです(実際にはこれに通信レイテンシや DNS 解決、TCP 接続、TLS ネゴシエーションなどが計 100 ms ほど加算されます)。
| 指標 | 値 |
|---|---|
| ローカルサーバでの TTFB | 532 ms |
| サーバ上での SSR 合計 | 529 ms |
| API フェッチ | 403 ms |
| renderToString | 126 ms |
| シリアライズ | 4.9 ms |
| dehydrate など | <1 ms |
API 呼び出しの待ち時間が TTFB の大半を占めていることが分かります。そこで、API のレスポンスをキャッシュすることによってこの待ち時間を削減することを考えました。しかし、TanStack Query では QueryClient をリクエストごとに生成し、異なるユーザーやリクエスト間でデータを共有しないことを推奨しており、このリポジトリもそれに則っています。QueryClient をリクエスト間で共有するには大規模な改修が必要になるうえ、そのキャッシュには個人情報が含まれ得るため、ユーザー間の情報漏洩を防ぐための考慮すべき事項が数多く生じます。加えて、フロ ント側のコードが基本的には isomorphic に書かれていることも、設計上の難しさになっていました。
そこでより簡潔な解決策として、サーバ側にリクエスト関数のデコレーターという形で Stale-While-Revalidate 方式の第二のキャッシュ機構を追加しました。

計測したところ、キャッシュヒット時には API 待ち時間を 1 ms 程度まで削減することができました。
結果
本来であれば本番環境にデプロイし、実ユーザーに対するパフォーマンス改善を数値として見届けたかったのですが、その前にインターン期間の終了を迎えてしまいました。以下は開発環境にデプロイし計測したデータです。

かなり早くなりました!LCP (Largest Contentful Paint) の時点ですでに求人情報まで出せているので、意味のあるユーザー体験の改善になったのではないかと思います。Chrome DevTools を利用して集計した、あるリクエストに対する LCP が以下になります。
| 指標 | 値 |
|---|---|
| TTFB | 226 ms |
| リソース読み込みの遅延 | 4 ms |
| リソースの読み込み時間 | 46 ms |
| 要素のレンダリング | 111 ms |
| LCP 合計 | 387 ms |
感想
まずは6週間にわたり、毎日1on1を実施して日々の進捗報告や技術上の相談に応じてくださったメンターの門野さんに、この場を借りてお礼申し上げます。LINEスキマニには素晴らしいメンバーとカルチャーが揃っており、各々の技術力の高さもさることながら、生産性向上のために努力を惜しまない姿勢や、開発外のメンバーとの緊密な連携など、毎日が刺激となるような環境でした。これが私の初インターンということもあって至らない点も多々ありましたが、メンバーの温かいご支援のもとで多くのことを学び、成長できたと感じています。非常に濃密かつ楽しい6週間でした!


