Upgrade to Pro — share decks privately, control downloads, hide ads and more …

16年目のピクシブ百科事典を支える最新の技術基盤 / The Modern Tech Stac...

Avatar for ahu ahu
January 12, 2026

16年目のピクシブ百科事典を支える最新の技術基盤 / The Modern Tech Stack Powering Pixiv Encyclopedia in its 16th Year

16年目のピクシブ百科事典のコードベースや、取り組みについての紹介です。
意思が反映しにくいコードでは何が起きるかと、今あるコードにどう向き合うかについて。
具体的なツールの活用事例やしていること、チームでの取り組みについて話します。

社内ブログの関連記事: https://inside.pixiv.blog/2025/11/26/190000
関連イベント: https://findy.connpass.com/event/379759/

Avatar for ahu

ahu

January 12, 2026
Tweet

Other Decks in Programming

Transcript

  1. 2 自己紹介 • 2021年4月 新卒入社 • フロントエンドエンジニア • pixiv本体の、マンガビューア・小説文字数 チェッカー・ストリートの実装などを担当

    • 2024年6月から百科事典チームに参加 • 技術面のリーダーとして、機能開発や基盤整備 の計画・進行・実装・レビューなどを担当 ahu エンジニア
  2. 7 • 「みんなでつくる百科事典」が合言 葉の、オンライン百科事典サービス • 2009年11月10日にβ版をリリース ◦ 今年で16年目に突入!🎉 • バックエンドは

    PHP 8.5 + k8s • フロントエンドは Next.js 15 • Smarty(テンプレートエンジン) → Next.js へ移行中…… ピクシブ百科事典 というサービス https://dic.pixiv.net/
  3. 簡単ではなかった Next.js 化 😭 • 移行を始めたのは2023年の2月頃(自分が参加する1年半前) • コロナ禍によるアクセス数の増加・テンプレートエンジンによる実装の古さ & メンテナンスの難しさなどがあり、Next.js

    へのリプレイスが決まる • 当時のチーム構成はバックエンド1名・フロントエンド1名(兼務)という最小構成 • 主要なページの Next.js 化は実現できたが、表示まで最大で10秒近くかかる場合が あるなど、パフォーマンス面で重い課題が残る状態(負荷で 503 エラーも頻発) • これを改善しないと全面的なリリースは難しい……という状況に 10
  4. 11 • SSR の負荷を下げ表示速度を大幅 に短縮 ⇒ リリースはできた 🎉 • しかし……

    パフォーマンスは 改善できたが…… https://inside.pixiv.blog/2025/11/26/190000 負荷が下がり 503 エラーも激減
  5. 12 • SSR の負荷を下げ表示速度を大幅 に短縮 ⇒ リリースはできた 🎉 • しかし……パフォーマンス面の制御

    が難航した = チームの意思を反映 しにくいコードである ということ • 例えば、ディレクトリ構成や実装 パターンが多様で把握が困難…… という課題は残ったまま パフォーマンスは 改善できたが…… https://inside.pixiv.blog/2025/11/26/190000 負荷が下がり 503 エラーも激減
  6. 15 • Q. 1日に何回デプロイしますか? • 日々書き換わる中で、コードは自然 と多様で無秩序な方向に育っていく = エントロピー増大の法則 •

    複雑度が勝手に下がることはない ⇒ コードの成長に方向性を与えたり、 成長の方向を矯正し続ける必要がある どうして一度の改善 では難しいのか
  7. 1. API の仕様から TS の型定義を生成する • 元々バックエンドでは、ドキュメンテーションのために Swagger が運用されていた •

    しかしフロントエンドではその定義を使っておらず、API に渡す値の型や レスポンスの型定義について、正確さを人が担保する運用だった • 特にレスポンスの型定義が不正確で、リファクタリングで不要な処理が増えたり、 必要な処理を消してしまったりなど、他の全ての改善を妨げていた 19
  8. 1. API の仕様から TS の型定義を生成する • 元々バックエンドでは、ドキュメンテーションのために Swagger が運用されていた •

    しかしフロントエンドではその定義を使っておらず、API に渡す値の型や レスポンスの型定義について、正確さを人が担保する運用だった • 特にレスポンスの型定義が不正確で、リファクタリングで不要な処理が増えたり、 必要な処理を消してしまったりなど、他の全ての改善を妨げていた • 現在は openapi-typescript で swagger.json から TypeScript の型を生成 • パスやパラメーターに間違いがないか型チェック & 補完が効くように ✅ 20
  9. // before: パスやパラメータを手で指定しており、typo や値の変換漏れに気付けない // before: クエリパラメータの指定順を間違えると、SSR 時の fallback を有効に使えない

    // before: 戻り値の型も、パスやパラメータからは引けず手で指定する必要がある export function useComments(tag: string, limit: number) { const { locale } = useLocale(); const encodedTagName = encodeTagName(tag); const { data, error } = useSWR<Comments>( `/get_comments/${encodedTagName}?lang=${locale}&limit=${limit}`, ); return useDataOrError(data, error); } // after: パスやパラメータの補完が効き、typo や値の変換漏れを型エラーとして検出 // after: SSR 時もこの記法が使え、クエリパラメータの指定順を気にする必要がない // after: 戻り値の型も、パスやパラメータから推論され手で指定する必要がない // after: ついでに、2重エンコード防止のため URL エンコード済みの値を幽霊型で区別 export function useComments(tag: TagName, limit: number) { const { locale } = useLocale(); const { data, error } = useQuery("/get_comments/{tagName}", { params: { path: { tagName: tag }, query: { lang: locale, limit } }, }) return useDataOrError(data, error); } 21
  10. 2. ディレクトリ構成の再定義 • 全てのコンポーネントが src/components にあり、文脈が掴みづらい状態だった • 責任の境界が曖昧になり、それぞれのコンポーネントの役割が拡大しがちに • Bulletproof

    React という設計を参考に、features という関心事(ページであった り機能であったり)ごとにディレクトリを区切るよう、全体の構成を見直し • pages 以下を、表示のルーティングとコンポーネントの配置だけをする薄い コントローラーとして扱うことで、Next.js 固有の問題の特定が容易になる効果も 23
  11. 24 src ├── pages # Next.js が特別扱いする場所で、ページ構成を定義する │ ├── a/[tagName]

    │ │ ├── index.page.tsx # データの取得と、 DesktopView と MobileView の出し分けを担当 │ │ ├── DesktopView.tsx # features に実装したコンポーネントを配置して画面を作る │ │ └── MobileView.tsx │ └── ... ├── features # ページや機能など、個別の関心事ごとにコードを置く │ ├── article-common │ ├── article # 記事ページに必要な article-common 以外の部分の実装。再帰的な構造 │ │ ├── components │ │ ├── hooks │ │ ├── utils.ts │ │ └── types.ts │ ├── history # 履歴ページに必要な article-common 以外の部分の実装 │ └── ... ├── lib # 広告や翻訳データなど、外部リソースに関するコードを置く ├── components # 全体で使う汎用的なコンポーネントを置く ├── hooks # React に依存した汎用的な補助関数(フック)を置く │ ├── api │ └── ... ├── utils # React に無関係な、その他の汎用的な補助関数を置く │ ├── ssr # 特に SSR に関する補助関数を置く │ └── … └── types
  12. 25 src ├── pages # Next.js が特別扱いする場所で、ページ構成を定義する │ ├── a/[tagName]

    │ │ ├── index.page.tsx # データの取得と、 DesktopView と MobileView の出し分けを担当 │ │ ├── DesktopView.tsx # features に実装したコンポーネントを配置して画面を作る │ │ └── MobileView.tsx │ └── ... ├── features # ページや機能など、個別の関心事ごとにコードを置く │ ├── article-common │ ├── article # 記事ページに必要な article-common 以外の部分の実装。 再帰的な構造 │ │ ├── components │ │ ├── hooks │ │ ├── utils.ts │ │ └── types.ts │ ├── history # 履歴ページに必要な article-common 以外の部分の実装 │ └── ... ├── lib # 広告や翻訳データなど、外部リソースに関するコードを置く ├── components # 全体で使う汎用的なコンポーネントを置く ├── hooks # React に依存した汎用的な補助関数(フック)を置く │ ├── api │ └── ... ├── utils # React に無関係な、その他の汎用的な補助関数を置く │ ├── ssr # 特に SSR に関する補助関数を置く │ └── … └── types
  13. 27 • Storybook やスナップショットテ ストの対象は、コンポーネント単体 • コンポーネント同士を組み合わせた 際の差分も、視覚的に確認したい! • Playwright

    と reg-cli でビジュア ルリグレッションテストを導入 • CI で実行して PR にコメント 3. VRT の導入と レポートの通知 実際に差分が出ている様子(赤い部分が差分)
  14. 28 • デプロイや CI の設定は、一度作る とあまり更新されず属人化しがち • でも毎日使うものでもある • 設定と

    Docker イメージのビルドを 見直し、CI の待ち時間を1/4に 🎉 • コメントで丁寧めに意図も補足 • 最近は standalone ビルドに移行 4. デプロイ周りの 整理と CI の高速化 全体で10分(それぞれ 3分くらい)
  15. 29 • 使わなかったコードは消し忘れがち • Knip というツールで、未使用の export や宣言、package.json の 不要な記述や記載漏れをチェック

    • 不要なコンテキストを減らせば、 AI によるコード補完の精度向上 なども期待できるはず 5. 不要な実装と 依存関係の整理 https://knip.dev/
  16. 30 • classnames というスタイル関連の ライブラリを、後発でより軽量な clsx/lite に置き換えた • 周辺ツールやエディタの恩恵をより 多く受けられるよう、ts-morph

    などで codemod を作ることも • codemod をレビューすれば、 レビュー量がずっと少なくなる 6. ライブラリの 更新と移行作業
  17. 33 • ピクシブでは、Gemini を使った レビュー bot が運用されている • プロンプトとして、暗黙的だった レビュー基準を整理

    & 言語化 • 単純な実装ミスのサジェストに利用 • セルフレビューが捗り、より本質的 な議論に時間を割けるように 8. 基準の整備と bot によるレビュー