フロントエンドエンジニアがペーパープロトタイピングを通じて得た気づき

株式会社リゾーム 業務ソリューション事業グループの水谷です。BOND WORKSのフロントエンドエンジニアです。

今回は、私の所属する開発チームで取り組んだペーパープロトタイピングの内容と、そこで得られた気づきを紹介します。

背景

これまでの開発では、主にテキストベースの仕様書をもとに画面を作成していました。

しかし、特に既存機能とは異なる画面構成や操作フローを持つ機能を開発する際には、

  • 実際に画面を作り始めてから疑問点が見つかる
  • レビューで仕様の認識違いが発覚する
  • 実装後に画面構成そのものへの改善案が出てくる

といったことが少なくありませんでした。

また、実装が進んだ状態では画面設計に対する改善提案もしづらくなり、修正コストも高くなってしまいます。

こうした課題を解決するため、実装前の段階で画面イメージを共有しながら仕様や操作フローを確認できるよう、ペーパープロトタイピングを実施することになりました。

取り組み内容

ペーパープロトタイプの作成

今回はペーパープロトタイプの作成に Excalidraw を利用しました。

excalidraw.com

一般的なペーパープロトタイピングでは紙とペンを使用しますが、私たちのチームは基本的にリモートワークで開発を行っています。 そのため、今回はオンライン上で共有しやすく共同編集も行いやすい Excalidraw を選択しました。

手書き感のある表現ができるため、細かなデザインに意識が向きすぎず、画面構成や操作フローの検討に集中できたのも良かった点です。

以下は、従業員向けの優待(クーポンのようなもの)を利用する機能を開発する際、実際に作成したペーパープロトタイプです。

チームでのレビュー

各担当者が作成したペーパープロトタイプをオンライン会議で共有し、チームメンバーで確認を行いました。

レビューでは、

  • 仕様の解釈に違いはないか
  • ボタン等のコンポーネントの配置は適切か
  • 画面遷移をはじめとする操作フローに違和感はないか

といった観点で議論を行いました。

自分が意識したこと

コンポーネント配置をできるだけ具体的に描く

今回は画面構成の確認だけでなく、仕様の認識合わせも重要視していました。 そのため、「ここにボタンを置く」「ここに一覧を表示する」といった大まかなレベルではなく、実際の画面をイメージできる程度まで具体的に描くことを意識しました。

例えば、ボタンはどの位置にどの程度の大きさで表示するのか、項目のテキストは何行程度まで表示される想定なのか、といった点まで考えながら作成しました。 また、ラベルやメッセージについても曖昧なものではなく、実際に表示されるような文言をできるだけ記載するようにしました。

このように具体的に描いていくと、仕様書を読んでいるだけでは気づけなかった疑問点が見えてきます。 一方で、細かく描けば描くほど作成には時間がかかります。 そのため、認識合わせに必要な情報はしっかりと表現しつつ、作りこみ過ぎないバランスを取ることが難しく感じました。

操作フローを意識する

ペーパープロトタイプを作成する際は、画面単体だけではなく、ユーザーの操作の流れを意識するようにしました。

実際に画面遷移や操作の流れを追いながら考えてみると、「この操作の結果を表示する画面が必要ではないか」といった気づきがありました。

画面単体で考えているだけでは見落としてしまいがちなことも、操作フロー全体で考えることで発見できる場合があることを実感しました。

補足情報をメモとして残す

画面イメージだけでは表現しづらい仕様もあります。

例えば、

  • 条件によって表示内容が変わる
  • ボタンの活性・非活性の条件がある
  • 特定のタイミングでメッセージが表示される

といった仕様です。

こうした内容については、ペーパープロトタイプの下あたりにメモとして補足を記載しました。

レビュー時に仕様書を何度も確認する必要がなくなり、よりスムーズに議論できたと感じています。

効果と気づき

仕様や機能全体への理解が深まった

今回最も大きな効果を感じたのは、仕様や機能全体への理解が深まったことです。

作成したプロトタイプをチームで共有しながらレビューを行ったことで、各担当者が個別に仕様を確認するだけでなく、チーム全体で仕様について確認しながら認識を揃えることができました。 その結果、自分では見落としていた観点に気づくことができ、仕様への理解も深まりました。 また、画面のイメージや仕様の認識をメンバー間で共有できたことで、実装前の段階で認識違いを解消しやすくなったと感じています。

加えて、画面構成についてチームで話し合う機会を持てたことも良かった点です。 今までは、どうしても機能要件や実装の詳細に関する議論が中心になりがちでした。 しかし、ペーパープロトタイプを導入することで、使いやすさや分かりやすさについての議論も活発になり、より利用者の視点に立った意見交換がしやすくなりました。

また、他メンバーの担当画面を確認することで、機能全体の流れや画面同士の繋がりへの理解も深まりました。 結果として、自分の担当画面だけではなく、機能全体を意識しながら開発を進められるようになったと感じています。

実装後のレビューでの指摘や手戻りが減った

ペーパープロトタイピングを実施したことで、実装後のレビューで画面構成そのものに対する指摘を受けるケースは以前より少なくなった印象があります。

特に効果を感じたのは、完成に近い段階になってから画面設計を大きく見直す場面が無くなったことです。 画面イメージや操作フローについて事前にレビューできたことで、認識を揃えた状態で実装に着手できるようになりました。

実装後に根本的な課題が発覚する前に認識を揃えられたことは、今回の取り組みの大きな成果だったと感じています。

おわりに

ペーパープロトタイピングを通じて、実装前の段階で画面イメージや操作フローを共有しながら認識を揃えることの重要性を実感しました。 また、仕様の理解を深めるだけでなく、チームでUI、UXについて議論する良い機会にもなりました。
今後も複雑なUI設計が必要な場合には、ペーパープロトタイピングを積極的に活用していきたいと思います。

業務で読んだ本・役に立った本を紹介

株式会社リゾーム 業務ソリューション事業グループの富田です。BONDWORKSのバックエンドエンジニアを担当しています。

2025年に入社してからこれまでに、業務で読んだ本や業務の役に立った本をご紹介します。

入社以前に読んだ本

イメージ&クレバー方式でよくわかる かやのき先生のITパスポート教室

ITパスポートを勉強するために参考にした本です。これからITの知識が必要な方やIT関連の会社で将来働きたい方にもおすすめです。この本の一番の特徴は「イメージ&クレバー方式」で、「○○とくれば××」という連想で知識を覚えられる点です。例えば、「PDCAとくれば業務改善」「ファイアウォールとくれば不正アクセス対策」のように関連するキーワード同士を結び付けて学べるため、ITパスポート試験の勉強がしやすくなっています。ITの技術だけではなく、経営やビジネスの仕組み、プロジェクト管理についても学ぶことができました。

キタミ式イラストIT塾 基本情報技術者

基本情報技術者試験を受ける時に参考にした本です。基本情報技術者試験はITエンジニアとしての基礎知識や技能を問う試験ですが、決して簡単ではなく、しっかりと勉強しないと受からない資格です。このキタミ式はイラスト重視で説明が書かれており、ITエンジニアの知識が少ない方にも読みやすいです。イラストを参考に何度も読み返すことで、ITエンジニアの基礎知識を得ることができ、実際に私が働く際にも役に立ちました。

入社1年目に読んだ本

プロを目指す人のためのRuby入門

Rubyの学習のため業務で読んだ本です。チェリー本という愛称で呼ばれています。Rubyのことが体系的に書かれていて、RubyやRuby on Railsを仕事で活用している人におすすめの本です。コードを書きながらテストについても学ぶことができます。私は現在もRubyの辞書として活用しています。

最新SC業界の動向とカラクリがよ~くわかる本

SC(ショッピングセンター)業界の理解を深めるために読みました。リゾームではショッピングセンターを対象とした製品を提供しているため、開発を行う上でもお客様の業務や業界の仕組みを理解することが大事だと感じ、この本を選びました。SC業界の基礎と現状、国内外の事例、未来への戦略が書かれた本です。SC業界の構造や運営の仕方を知ることができ、今後の業務の参考になる一冊でした。ショッピングセンターのことをあまり知らない方や、業界に興味がある方に読んでいただきたい本です。

世界の一流は「雑談」で何を話しているのか

タイトルに惹かれて読んだ本です。世界と日本の雑談の違いや雑談の4つの目的などについて書かれていました。仕事の雑談で何を話せばいいのか、営業先との雑談は準備が大事でそれが商談の成功に直結するといった面白い内容でした。海外の雑談は日本の雑談とは異なり、明確な目的や意図を持って行われるという内容が印象的でした。日本の会社や組織でも活用できる考え方が多く、今後のコミュニケーションの参考になりました。

心理的安全性のつくりかた

会社で行われている読書会で読んだ本です。心理的安全性とは、組織やチームの中で自分の意見や疑問、失敗を素直に伝えても恥ずかしい思いや罰せられないという安心感がある状態のことです。この本には、チームとの関係性の作り方について書かれています。チームメンバーとの関係は働きやすさに直結するので、この本を通して、チームメンバーとの心理的安全性の作り方を意識するようになりました。

いちばんやさしいGit&GitHubの教本

GitとGitHubの違いもよく分かっていなかったころに、勉強のために読んだ本です。GitやGitHubの操作方法や活用方法が書かれており、実際に業務でGitとGitHubを利用する時の参考になりました。

1冊ですべて身につくHTML & CSSとWebデザイン入門講座

教育期間中に読んだ本です。HTMLとCSSの基本やWebサイトの基本、フルスクリーンや2カラムなどのレイアウトについて学べました。Webデザインの実例がたくさん出てきて、見やすいデザイン、配色の話、モダンなデザインは何かといったことを知ることができました。利用者が使いやすくて見やすいデザインをつくるときの参考になります。

入社2年目に読んだ本

アジャイルサムライ −達人開発者への道−

アジャイル開発の理解を深めるために読んだ本です。アジャイル開発とは計画を完全に固めて進めるのではなく、短い期間で小さな開発と検証を繰り返し、状況の変化やユーザーの意見に素早く適応しながら開発を進める手法です。このアジャイルサムライでは、アジャイルの実践的な内容をユーモアや手書きイラストを活用して説明しており、アジャイルの理解が深まる本でした。

最短突破 Ruby技術者認定試験

Ruby Silverの資格勉強のために今読んでいる参考書です。先ほど紹介したプロを目指す人のためのRuby入門との違いは、資格を取るために特化した解説や説明がしっかりとしていて、コードと解説の流れで効率的に勉強できています。

バックエンドエンジニアのためのインフラ・クラウド大全

バックエンドエンジニアに求められるインフラ・クラウドの基礎知識をまとめた本です。自身の成長のために読みました。内容が少し難しいと感じる部分もありましたが、エンジニアとしての実務的な内容が多く、インフラ・クラウドの理解が深まる本でした。インフラの全体像やパフォーマンス改善、運用・監視の知識がつきました。

1週間で勝手に自律神経が整っていく体になるすごい方法

ITエンジニアは業務中ずっと座りっぱなしなことが多く、だんだんとメンタルや体調が悪くなっていくことが他の職種より多いと感じました。そのようなことを事前に防止するために読んだ本です。書いていることは基本的なことが多く、朝食にバナナやヨーグルト、はちみつを食べることや、朝に太陽の光を浴びること、早めに起きることなど、一般的によく聞く健康法の話が多いですが、具体的になにをやればいいかが書かれており非常に参考になりました。実際に実践して自律神経の改善を感じました。

終わりに

改めて本を紹介してみると、インプットの大切さと、それをまとめて発信するアウトプットの大切さを実感しました。今後も知識を得て実際に活用しながら、ITエンジニアとして成長していきたいと思います。

Railsのファイルキャッシュ起因で遅延が起きた話

株式会社リゾーム 業務ソリューション事業部 企画開発課の梶谷です。
商業施設・専門店向けのコミュニケーションウェア BOND GATE(ボンドゲート)の運用、保守を担当しています。

本番環境で「画面操作時に表示が遅いことがある」という状況があり、原因切り分けのためにNew Relic(APM)を使って調査していたところ特定操作時のレスポンス遅延が発生していることに気付きました。
調査の結果、原因はアクション本体の処理ではなく、同一リクエスト内で実行されていたキャッシュ削除処理に時間がかかっていることが分かりました。

今回は発生していた事象、調査結果、対応内容と対応結果をご紹介します。
同様の事象を調査する際の参考になれば幸いです。

発生していた事象

ある本番環境で、特定操作時のレスポンス遅延が発生していました。
New Relicでの計測上で、キャッシュの削除処理であるActiveSupport::Cache::FileStore#delete_matched の処理時間が長くなっている事を確認しました。

調査結果

今回の事象が起こっていた弊社製品BOND GATE(ボンドゲート)は、商業施設・専門店向けのコミュニケーションウェアです。 複数施設を担当するユーザーは施設を切り替えながら情報を確認します。
今回の遅延はこの「施設切替」操作に関連するリクエストで発生していました。
New Relic上では別の機能のトランザクションが遅いように見えていたため、最初は施設切替処理が原因だとは気付けていませんでした。
しかし、New Relicで遅いトランザクションを確認してその内訳を追ったことで施設切替に伴う処理がボトルネックになっていることを特定できました。

実装を追うと、施設切替に伴う処理の中でキャッシュ削除が実行されており、正規表現を使って削除対象を指定しキャッシュ全体の中から一致するものを探索して削除する処理が施設切替が行われるたびに走っていたということが分かりました。
その際に delete_matched に正規表現を渡して削除を行っていたため、New Relic上でも delete_matched の処理時間が長く計測されていました。
また、この方式で削除を行っていたためキャッシュの全体量が増えるほど探索対象が増えて処理時間が伸びやすくなってしまっていました。

一般的には、キャッシュには Redis や Memcached のようなオンメモリストアがよく使われます。
これらは高速で、設定した有効期限やメモリ上限に応じて古いデータが整理される仕組みを持っているためキャッシュの管理に向いています。
しかしRedis 、Memcached などを利用する際にはRails アプリとは別にミドルウェアを運用する必要があります。
BOND GATE はお客様のご都合に合わせて、パブリッククラウド・プライベートクラウド・オンプレミスなど環境ごとに違う運用方式で提供されています。
そのため、ミドルウェアを全環境で同一条件で導入・運用することは保守面での負荷が高く採用していませんでした。
結果として運用しやすさを優先し、サーバを用意する必要がないFileStoreを利用していました。

FileStore は、期限切れキャッシュを自動的に物理削除し続ける仕組みはなく、cleanup などの明示的な削除処理を実行しない限り、キャッシュファイルがディスク上に残り続けます。
運用状況を確認したところ、キャッシュ全体の削除は実質的にデプロイ時にしか行われておらず、長期間にわたってキャッシュが蓄積していました。
確認時点でのキャッシュファイル数は約78万件でした。
BOND GATE では施設切替のたびに該当のキャッシュ削除処理が実行されるため、キャッシュ蓄積によって削除処理自体が重くなり施設切替操作のたびにレスポンス遅延を引き起こしていました。

実施した対応

対象環境でキャッシュ削除用の Rake タスクを実行してキャッシュ全体を削除しました。
その後、ActiveSupport::Cache::FileStore#delete_matchedの実行時間を計測しました。

対応結果

削除をする前とした後の計測結果は以下の通りで、処理時間の大幅な短縮が見られました。

  • 削除前24時間平均 : 約1.33s

  • 削除後直近24時間の平均 :約3ms

削除前と比較すると約443倍の高速化が実現できています。

今回は暫定的な対応として手動で削除を行いました。
長期的な再発防止策として、キャッシュ削除を定期実行する運用の導入を進めています。

おわりに

今回の処理速度の遅延は長期間のキャッシュ蓄積が原因で、施設切替時に実行されるキャッシュ削除処理で発生していました。
また、今回の調査で画面上の印象だけで判断せず、New Relicでトランザクション単位に処理を確認することの大切さに気付くことができました。
キャッシュ整理後はdelete_matched の処理時間が短期計測で改善しており、今後はキャッシュ削除の定期実行の運用環境の整備によって継続的な性能維持を図っていこうと考えています。
最後までお読みいただきありがとうございました。
今後も運用面を含めて継続的に改善し、安定した性能維持に取り組んでいきます。

BOND GATEからBOND WORKSへの移行を支えるAWSインフラ改修

株式会社リゾーム 業務ソリューション事業グループのあめぎです。

2025年にBOND WORKSがリリースされもう1年以上経過しました。 有り難いことに、前身となるBOND GATEからBOND WORKSへ乗り換えてくださるお客様がどんどん増えている状況です。

しかし、そうなってくると問題となるのはデータ移行です。 既存のBOND GATEの情報を、BOND WORKSへ移行し過去の情報資産をそのままご活用いただけないといけません。

リゾームでは、その対応のためにデータ移行作業を行うわけですが、リリース当初の手法では時間もかかり無駄が多い状況でした。 そのため、2つの視点からデータ移行作業の見直しを行いました。

  • AWSインフラ改修
  • データ移行ツール開発・改修

今回は、AWSインフラ改修のお話です。 結果として、移行前のバックアップ作成・転送・復元作業が不要となり、移行開始までに発生していた約2時間の待機時間を削減することができました。

従来のデータ移行

BOND WORKSリリース当初は、乗り換え予定のお客様がまだ少なく以下の手順を行っていました。

  1. BOND GATEの本番DBからバックアップを作成
  2. バックアップをS3へ保存
  3. BOND WORKSがバックアップをS3から取得
  4. バックアップを一時格納DBへ復元
  5. データ移行ツールが一時格納DBを参照
    従来のデータ移行

問題点

実際に作業者がデータ移行ツールを実行できる状態になるまで、つまり手順5に到達するまで約2時間の待機時間が発生していました。 移行作業を開始する前の準備時間が長すぎたのです。 もちろん待ち時間で全く別の作業を行えばよいのですが、作業者の負担を考えると望ましい状況ではありませんでした。

解決案

BOND WORKSのデータ移行ツールがBOND GATEの本番DBへ直接接続できればよい、という結論に至りました。 これであれば、問題点であった待ち時間が解消されます。

解決策

課題

  • BOND WORKS環境とBOND GATE環境をどうやって結ぶか?
     ⇒ 今回のお話
  • 直接本番DBへ接続すると、お客様のBOND GATEに影響があるのではないか?
     ⇒ 次回:移行ツール開発・改修のお話

BOND WORKS環境とBOND GATE環境をどうやって結ぶか

今回は複数のサービスを組み合わせて解決することにしました。

  1. PrivateLink
  2. 中継用VPC
  3. VPCピアリング
  4. リバースプロキシnginxで本番DBへ転送
    複数のサービスを組み合わせて解決

PrivateLinkについて

  • ネットワークアドレスの重複あり
  • 双方向のTCPコネクションは不要

これらを要件を満たすときの強い味方がPrivateLinkです。 今回はBOND WORKSとBOND GATEという異なる管理者が運用する環境同士を接続する必要がありました。そのため、一部でネットワークアドレスの重複が確認されており、その回避策としてPrivateLinkを採用しました。この辺りがネットワーク管理の難しいところですね。

中継用VPCについて

PrivateLinkを直接本番DBのあるVPCに繋げられるのでは?と考えた方もいるのではないでしょうか、その通りです。 BOND GATEはお客様ごとにVPCや本番DBなど環境が分離されていたため、当初その案も存在しました。 一方で、ランニングコストを考慮し中継用VPCを用意することにしました。

PrivateLinkにはエンドポイントが必要になるのですが、意外とこのエンドポイントに費用がかかります。 1AZに抑えたとしても1つ当たり約10ドル/月です。(当時の試算・東京リージョン想定) BOND GATEのユーザーは何十社とおられますので、それぞれの環境へ全てを繋げると大変費用がかかります。

なので中継用VPCから目的の本番DBに繋げられるようにすることで、その費用を削減することにしました。

VPCピアリングについて

中継用VPCから目的の本番DBに繋げられるようにするために用意したのがVPCピアリングです。 これであれば中継用VPCと目的の本番DBがあるVPCは相互通信が可能となります。

そしてVPCピアリングはエンドポイントと違って月額の固定費がかかりません。データ転送量のみの課金です。 今回の目的ととても相性が良いサービスでした。

ネットワークアドレスの重複があると使えないサービスなのですが、幸いBOND GATEは環境毎にアドレス帯を分けて運用していました。 個人的にここが大きな分岐点だったと感じています。

リバースプロキシnginxで本番DBへ転送について

本来であればNLBから本番DBを直接指定して転送したいところなのですが、残念ながらそのような構成を組むことができません。 そこでリバースプロキシnginxを用意して、指定ポート毎に目的の本番DBへ転送できるようにしました。 ついでに動的IPである本番DBの名前解決もできるようにしました。

リバースプロキシnginxが単一障害点になっていると感じた方、その通りです。お客様用途であれば望ましい構成ではありません。 本構成はデータ移行ツール実行時にしか使わないので、こちらを採用することにしました。

まとめ

今回はデータ移行ツール用途の特殊な環境構築ということで、主にどうすれば安く実現できるかを考えて構築を進めました。 特に、BOND GATE側でアドレス帯の重複を避けて運用されていたおかげで、VPCピアリングを利用できたことには感慨深いものがありました。

ネットワーク担当者は、自身の管理するアドレス帯は意識的に重複を避ける傾向があります。 アドレス帯の重複は基本的に双方向の通信を許可せず、容易に起こりやすい割に解消のためのハードルがとても高いからです。 よって、通信の予定はなかったとしても「万が一」に備えアドレス重複は避けることが多いのです。

そして私は、BOND GATEがリリースされた2011年当初のネットワーク担当者ではありません。 当時のネットワーク担当者たちは、今回のような「万が一」に備えて構築を行ってくれていたのですね。

歴代のネットワーク担当者たちの仕事に感謝して、今回は終わりにしようと思います。 次回は開発メンバーによるデータ移行ツール開発・改修の記事です。お楽しみに。

【BOND WORKS】Next.js14から15への移行対応

株式会社リゾーム 業務ソリューション事業グループの土井です。 BOND WORKSでフロントエンドエンジニアを担当しています。

BOND WORKS | 製品・サービス情報 | 株式会社リゾーム

今回は以前行ったNext.js14から15への移行について紹介します。

対応の流れ

大まかに以下のような流れで実施しました。

  1. アップグレードの手順調査
  2. ローカルでのアップグレード対応
    1. 公式で配布されているcodemodによる自動修正
    2. 自動修正で直らなかった箇所の手動修正
  3. ローカルでの動作チェック
  4. mainブランチへの反映
  5. 動作確認用の環境での動作チェック
  6. 移行手順書の作成

今回はアップグレード対応と対応後の移行手順書の作成に焦点を当てていきます。

アップグレード対応

アップグレード対応については公式ドキュメント及び、公式ドキュメントを有志の方が翻訳してくださったものを参考に実施しました。

実際に行った対応を紹介します。

codemod実行

アップグレードで必要な既存のコードに対する変更は、公式が提供している自動更新用ツールのcodemodを使用することで自動的に行うことが出来ます。 npx @next/codemod@canary upgrade latestを使用してcodemodの実行を行います。 canary部分が使用するcodemodのバージョン、latest部分がアップグレードするNext.jsのバージョンです。 このまま実行すると最新バージョン(※記事作成時点で16系)へアップグレードされてしまうため、npx @next/codemod@15.5.19 upgrade 15.5.19のようにバージョンを指定して実行を行いました。(※記事作成時点での15系の最新版が15.5.19のため例ではこのバージョンを指定しています)

コマンドを実行すると対話形式で更新が実行されていきます。 codemodのインストールやreactの更新、codemodを使用した書き換え、turbopackの使用、vercelへのデプロイといった項目の選択が順番に求められます。必要に応じて承諾、拒否を行います。 対応の際には、turbopack化は別途行う予定だったことと、vercelは使用していなかったのでこの2つは拒否しました。

各エラー発生箇所への対応

codemod実行後に型エラーやビルドでのエラーが出ている箇所の対応を行います。 codemodが自動で直しきれない箇所は@next-codemod-errorというプレフィックス付きのコメント共に型のエラーとして検出されます。エラー発生箇所をそれぞれアップグレードによる変更を確認しつつ修正を行いました。

未反映箇所の修正対応

一部のコードでcodemodの実行だけでは反映されない箇所が見つかったため、手動での修正を行いました。 具体的には、params、searchParamsが非同期になったため、型へPromiseのラップが必要だったのですが反映されていない箇所がありました。これについては文字検索で一件ずつ確認を行っていきました。

手動修正箇所の例

type Props = {
  params: {
    id: string;
  };
};

↓

type Props = {
  params: Promise<{
    id: string;
  }>;
};

移行手順書の作成

移行対応などの作業は属人化しがちなので、そうならないために手順書を作成し他のメンバーでも対応が行えるようにしました。 BOND WORKSのフロントエンドは複数リポジトリに分かれているため、それぞれのリポジトリで対応が必要になります。今回は自分がまず一つのリポジトリに対応を実施、その後他のリポジトリについては他のメンバーに対応してもらいました。

実際に対応してみて

今回の移行対応で苦労したと感じたのは、codemodだけでは修正が完結しなかったことです。 途中少し触れましたが、一部の型定義が変換されておらず手動での確認が必要でした。 文字検索は使用しましたが、あくまで目視でのチェックとなり神経をすり減らす作業でした。

移行対応中もチームの開発が進んでいたため、作業ブランチに最新のmainブランチを継続的に取り込む必要がありました。mainブランチを取り込むと、新たに追加・変更されたコードにも移行対応が必要になるため、Next.js 15へ移行するためのCodemodやReact 19へ移行するためのCodemodを適宜実行する必要がありました。移行作業の期間が長くなるほどmainブランチの取り込みとCodemodの再適用が増えるため、できるだけ短期間で対応を進めることの重要性を感じました。

おわりに

今回のようなアップグレード対応についてはこれまでほぼ経験がなかったため自身の中でのハードルが高かったです。しかし今回の経験を得て「アップグレード対応=難しい作業」という印象が払拭できたと思います。

16への移行についても計画があるので、対応の際には今回同様アップグレードへの対応に加えて手順書を作成しチーム内で共有しようと思います。

BOND WORKSにおけるnpmサプライチェーン攻撃対策

株式会社リゾーム 業務ソリューション事業グループの岡部です。BOND WORKSのフロントエンドエンジニアです。

今回は、昨今相次ぐnpmへのサプライチェーン攻撃に対し、BOND WORKSチームが実施している対策を紹介します。

はじめに:サプライチェーン攻撃とは

npmに対するサプライチェーン攻撃は次のような流れで行われます。

  1. 攻撃者は、フィッシングによるメンテナーアカウントの乗っ取りやCI/CD環境の脆弱性の悪用などを通じて、パッケージの公開権限やリポジトリへのコミット・マージ権限、npm publish で使用するトークンなどの認証情報を取得する
  2. 攻撃者は対象のライブラリに不正なコードを混入させ、それを含むバージョンを正規のリリースとして公開する
  3. 利用者が npm install によって当該バージョンをインストールしたり、ライブラリのAPIを呼び出したりすると、悪意のあるコードが実行される。その結果、認証情報の窃取やデータ漏洩など、さまざまな被害が発生する

今回は上記の3番、パッケージ利用者の目線で、どこにリスクがありどんな対策が有効なのか見ていきます。

1. 悪意のあるパッケージを入れない

第一の防御層は、悪意のあるパッケージをダウンロード・インストールしないことです。そのための方法を2つ紹介します。

1-1. 悪意のあるパッケージを検出・ブロックする

1つ目は、悪意のあるパッケージを検出し、インストール前にブロックすることです。

脅威インテリジェンスによる検出・ブロックを行うツール・サービスとして、Aikido社のSafe Chain、GMO Flatt Security社のTakumi Guardが有名です。

Aikido Safe Chainはインストールして使うツールです。npm などパッケージマネージャのコマンドをラップし、ローカルに立てた軽量な検閲用プロキシサーバーを経由してパッケージをインストールさせるという仕組みです。一度導入すれば複数のパッケージマネージャに対して有効になるのが強みの1つです。

Takumi Guardも検閲のためのプロキシサーバーではありますがこちらはSaaSとして提供されます。パッケージマネージャの接続先をTakumi Guardに変えるだけで利用でき、非常に手軽です。

いずれも無料で利用でき、これらを導入するだけでも安心感はグッと上がります。

1-2. 公開から一定期間経過していないパッケージをブロックする

2つ目は、公開から一定期間が経過していないパッケージをブロックすることです。

悪意のあるパッケージは、攻撃に気付いた開発者によって数時間から1日ほどで取り下げられることが多いです。したがって、公開から数日経っているパッケージはそれだけでリスクが下がっているということです。

もちろん発見が遅れる場合もあるでしょうから、時間が経っていれば安全とは限りません。先ほど紹介した1つ目の方法と組み合わせて、多角的に防御することが重要です。

npmでは min-release-age を設定すると、公開後一定期間が経過していないパッケージをブロックできます。自作パッケージなど信頼できる場合は min-release-age-exclude を設定すれば制限をバイパスできます。このように、デフォルトでブロックしつつ一部のみ許可する、いわゆる許可リスト(Allowlist)方式で運用するのが安全です。

詳細は割愛しますが、Yarnやpnpmにも同様の設定があります。また、先ほど紹介したAikido Safe Chain側でも設定が可能です。

注意点としては、緊急で当てないといけないパッチまでブロックされる可能性があるということです。その場合は安全を確認した上で npm install package-name@1.2.3 --min-release-age=0 のように対象パッケージのみ一時的に許可するのが良いでしょう。

2. npm install 実行時のスクリプトを防ぐ

npmに対するサプライチェーン攻撃における最も恐ろしい点の1つは、インストールしただけで感染する可能性があることです。

依存パッケージに preinstall、install、postinstall、prepare などのライフサイクルスクリプトが設定されていると、npm install / npm ci の過程で自動実行されることがあります。コード内でimportしていないから安全、とは言えません。

  • preinstall: インストール処理より前に実行される
  • install: インストール処理の最中に実行される
  • postinstall: install の後に実行される
  • prepare: ローカルプロジェクトでの npm install や、Git URLからのインストールなどで実行される
  • node-gyp: (ライフサイクルスクリプトではないがCやC++で書かれた処理を扱う仕組みで、こちらも攻撃に使われることがある)

したがって、このようなスクリプトの自動実行を抑止する設定が効果的です。npmでは ignore-scripts=true を設定することで、上記スクリプトの実行を禁止できます。こうしておけば、仮に悪意のあるパッケージをダウンロードしてしまった場合でも、被害発生の可能性を下げられます。

また、npmにはGitリポジトリをパッケージソースとして扱う機能があり、こちらも攻撃に使われることがあります。この機能が必要ない場合、--allow-git=none も併用して無効化するのがおすすめです。

npmはデフォルトでこれらを許可するようになっており、任意コード実行が非常にやりやすい(攻撃者にとっては嬉しい)環境といえます。この点に関してはnpmサイドも対策を進めており、npm v12からはデフォルトでスクリプトの実行やGit連携がブロックされ、許可リスト方式での運用が推奨されるようになります1。

このようなスクリプトが必要なパッケージもあります。v11ではブロック対象を警告表示してくれるので、今から許可が必要なパッケージを洗い出して準備しておくのがいいでしょう。

3. CI/CD環境を守る

ローカルだけでなくCI/CD環境を守ることも大切です。ローカルで気を付けていても、CIが別のポリシーで動いていると、そこが抜け穴になることがあります。できるだけ設定は揃えた上で、CI/CD環境では以下のような追加の対策を行うと効果的です。

  • Dependabotでも、公開から一定期間待つクールダウンを設定できる2。自動PRを利用する場合はパッケージマネージャ側の設定と統一しておく
  • CIスクリプトでは、意図しないパッケージのアップデートが混入しないように npm ci や yarn install --immutable、pnpm install --frozen-lockfile を使う

おわりに

有名どころのパッケージだと、何万、何十万ものユーザーが使っています。自分のプロジェクトが直接依存していなくても、利用しているパッケージが間接的に依存しているパターンも多いです。ゆえに影響範囲が広く、またShai-Huludのように自己増殖能力を備えている場合は爆発的に被害が広がる傾向にあります。

最近ではAxiosやTanstackといった定番といえる人気ライブラリへの攻撃が話題になりました。もはやNode.js開発者全員にとって無関係ではいられない話題といえるでしょう。リスクは身近に迫っています。

BOND WORKSチームでも機能開発と並行して対策を進めました。定期的なパッケージ更新に安心して取り組めるようになりました!参考になれば幸いです🙏

Next.jsでReact Iconsを使うと開発サーバー(next dev)が重いので対策を入れた

株式会社リゾーム 業務ソリューション事業グループの岡部です。BOND WORKSのフロントエンドエンジニアをやっています。

BOND WORKSのフロントエンドはNext.jsで開発しており、アイコンライブラリとしてReact Icons(react-icons)を採用しています。

React IconsにはFont Awesome、Material Design Icons、Bootstrap Iconsなどをはじめとするメジャーなアイコンパックが多く含まれているため、個々のアイコンライブラリを別々にインストールする必要がありません。またReactコンポーネントとして提供されているため簡単に使えて便利です。

しかしNext.jsでReact Iconsを使用すると、開発サーバー(next dev)の実行時にパフォーマンスが著しく悪化する問題が発生しました。今回はこの問題について調査を行い、また暫定的ではありますが対策を入れたのでご紹介します。

TL;DR

  • Next.jsでReact Iconsを使うと next dev が重い
  • インポートの最適化を行う optimizePackageImports が効かず、アイコンパック内の全コンポーネントがバンドルされるのが原因
  • アイコンコンポーネントを @react-icons/all-files から個別にインポートすることで対策が可能

環境

  • Next.js 15.5.12 (App Router, Webpack)
  • React Icons 5.5.0

Next.jsとReact Iconsを組み合わせたときの問題点

前提知識:optimizePackageImports とは

Next.jsにはインポートを最適化する optimizePackageImports という機能があります。Vercelの解説記事によると、以下のようにバレルファイルからのインポートを検出し、モジュールを直接インポートする文に置換するそうです。コンパイル時に行われるので、バンドラにバレルファイルを解析させてTree shakingするよりも確実かつ低コストとのこと。

/* optimizePackageImports による最適化のイメージ */
// Before
import { module2 } from 'my-lib'
// After
import module2 from 'my-lib/module2'

React Iconsが最適化されない!

しかしReact Iconsでは optimizePackageImports が効かないみたいです(2026年2月現在)。公式ドキュメントには react-icons/* に対応していると書かれているのですが…🤔

下記Issueにあるように、アイコンを1つインポートしただけでアイコンパック全体がバンドルに含まれてしまいます。

最適化が効かない原因ですが、React Iconsは各アイコンコンポーネントを個別ファイルで提供していないため、import文を置換できないのだと思われます。

// これを最適化したいが…
import { IconName } from 'react-icons/md'
// ❌単体ファイルが存在しないのでこのように書けない!
import IconName from 'react-icons/md/IconName'

では、インポート元のファイルはどのような構造になっているのでしょうか?react-icons/md/index.mjs を見てみると、関数コンポーネントが直接定義・エクスポートされていることが分かります。1コンポーネント=1アイコンなので、Material Design Iconsパックに対応するこのファイルでは実に約4300個もの関数がエクスポートされ、ファイルサイズも2 MBを超えます。

// node_modules/react-icons/md/index.mjs より抜粋
export function Md123 (props) {
  return GenIcon({ /* 省略 */ })(props);
};
export function Md3dRotation (props) {
  return GenIcon({ /* 省略 */ })(props);
};
export function MdAbc (props) {
  return GenIcon({ /* 省略 */ })(props);
};
// ... (exportが4000個以上ある)

つまり、React Iconsにおけるアイコンパックごとのindexファイルは、厳密にはバレルファイルではないということですね。

開発体験の悪化

大量のexportを含むファイルは、ビルド時や実行時にパフォーマンスの問題を引き起こします。

本番環境ではビルド時の負荷こそ高いものの、Tree shakingが行われるため、実行時のパフォーマンスに問題はありませんでした。

しかし開発サーバー(next dev)ではTree shakingが行われないため、巨大なJavaScriptファイルをブラウザが読み込む必要があり、CPUに高い負荷がかかっていました。メモリの消費も激しかったです。特に複数のアイコンパックを参照している場合は顕著で、ページの初回表示に30秒~1分近くかかるなど、快適に開発できる状態ではありませんでした。

Issueに目立った動きがないのでしばらく修正は来なさそうですし、他のアイコンライブラリへの切り換えにも時間がかかります。そこで、Issue内のコメントを参考にして、@react-icons/all-files パッケージからアイコンを個別にインポートするという暫定対策を入れてみることにしました。

暫定対策:@react-icons/all-files からアイコンを個別にインポートする

React Iconsには、アイコンを個別のファイルに分けて提供する @react-icons/all-files パッケージが存在します。移行の際には以下のようにimport文を置き換える必要があります。

/* 置換例 */
// Before
import { MdMoreVert, MdPrint } from 'react-icons/md';

// After
import { MdMoreVert } from '@react-icons/all-files/md/MdMoreVert';
import { MdPrint } from '@react-icons/all-files/md/MdPrint';

1. @react-icons/all-files を導入する

肝心の @react-icons/all-files パッケージですが、npmの公式レジストリで公開されていません。下記Issueで言及されていますが、大量のアイコンを含む関係でファイル数が多くなりすぎて公開できないそうです。

そこで、当該Issue内のコメントにあるように、GitHubリポジトリのReleasesで配布されているものを使います。

# 5.5.0 を npm でインストールする例
npm install @react-icons/all-files@https://github.com/react-icons/react-icons/releases/download/v5.5.0/react-icons-all-files-5.5.0.tgz

2. import文を置換する

次にimportの置換を行います。

/* 置換例 (再掲) */
// Before
import { MdMoreVert, MdPrint } from 'react-icons/md';

// After
import { MdMoreVert } from '@react-icons/all-files/md/MdMoreVert';
import { MdPrint } from '@react-icons/all-files/md/MdPrint';

import文は以下のように複数行で記述される場合もあり、単純な文字列置換では対応が難しいです。

// 1行の場合
import { MdMoreVert, MdPrint } from 'react-icons/md';

// 複数行の場合
import {
  MdKeyboardArrowLeft,
  MdKeyboardArrowRight,
  MdKeyboardDoubleArrowLeft,
  MdKeyboardDoubleArrowRight,
} from 'react-icons/md';

そこで、AST(抽象構文木)を扱うことができる jscodeshift を使ってCodemodを作成することにしました。

Codemodの処理の流れは以下のようになります。

  1. react-icons/* からのimport文を探す

    例だと import { MdMoreVert, MdPrint } from 'react-icons/md' の部分

  2. import文からアイコンパック名を取得

    例だと 'react-icons/md' の md の部分

  3. importされている識別子を取得

    例だと MdMoreVert と MdPrint の2つ

  4. 2, 3で得た情報を元に個別のimport文を作成

    例だと以下の2つのimport文を表すオブジェクトを作成

    • import { MdMoreVert } from '@react-icons/all-files/md/MdMoreVert'
    • import { MdPrint } from '@react-icons/all-files/md/MdPrint'
  5. 実際のソースファイルを、元のimport文を置き換える形で書き換える

また、optimizePackageImports の問題が修正されたときに戻せるよう、逆の置換をするCodemodも作成しました。

改善を確認🚀

対策を入れた上で next dev を動かしてみると、改善前は表示に30秒以上かかっていたページが10~15秒ほどで表示されました!PCの負荷も体感できるレベルで軽減され、開発体験が向上しました🎉

念のためバンドルアナライザを使い、余計なファイルや巨大なIndexファイルの読み込みが無いことも確認しました。

おわりに

React Iconsはとても便利なライブラリですが、利用時は注意が必要です。

問題を調査し対策する中で、たくさんの学びがありました。

  • バレルファイルの問題点
  • Next.jsがどのようにインポートを最適化するか
  • jscodeshiftの使い方
  • などなど…

似たような状況で困っている方の参考になれば幸いです。