サーバー側 vs クライアント側 フィルタリング — 判断チェックリスト
データ量、レイテンシ、権限、キャッシュを基準に、UIの情報漏えいや遅延を防ぐためのサーバー側/クライアント側フィルタリングの選択チェックリスト。

本質的な問題:漏えい、遅延、一貫性の欠如
UIでのフィルタリングは単なる検索ボックス以上のものです。通常、テキスト検索(名前、メール、注文ID)、ファセット(ステータス、担当者、日付範囲、タグ)、ソート(新着、高額、最終活動)など、関連するいくつかの操作が組み合わさって表示内容を変えます。
重要な問いは「どちらの手法が良いか」ではなく、フルデータセットがどこにあり、誰がそれにアクセスできるかです。ブラウザがユーザーに見せてはいけないレコードを受け取ると、見えなくしていてもUIから機密データが露出する可能性があります。
サーバー側とクライアント側のフィルタリングに関する議論の多くは、実際にはユーザーがすぐに気づく2つの失敗への反応です:
- 漏えい: ネットワークペイロード、キャッシュされたレスポンス、または予期せぬフィルタによって隠された行が明らかになること。
- 遅延: あまりに多くのデータを送ってデバイス側に重い処理をさせ、画面が遅く感じられること。
さらに厄介なのは結果の不一致です。あるフィルタはクライアントで、別はサーバーで動くと、ユーザーはカウントやページ、合計が合わないのを見ます。特にページネーションがある場合、信頼が急速に壊れます。
実践的なデフォルトは単純です:ユーザーがフルデータセットにアクセスできないならサーバー側でフィルタする。アクセスが許され、かつデータセットが短時間で読み込めるほど小さいならクライアント側フィルタでも問題ありません。
平易な定義
フィルタリングは「条件に合う項目を見せて」というだけです。重要なのは「一致の処理がどこで行われるか」:ユーザーのブラウザ(クライアント)か、バックエンド(サーバー)か。
クライアント側フィルタリングはブラウザで動きます。アプリは一連のレコード(多くはJSON)をダウンロードしてからローカルでフィルタを適用します。データが読み込まれた後は瞬時に感じられることがありますが、データ量が小さく安全に公開できる場合に限ります。
サーバー側フィルタリングはバックエンドで動きます。ブラウザがフィルタ入力(例えば status=open, owner=me, createdAfter=Jan 1)を送り、サーバーは一致する結果だけを返します。実務では、フィルタを受け取りデータベースクエリを組み立て、ページネーションされたリストと合計を返すAPIエンドポイントになることが多いです。
シンプルな頭の中のモデル:
- クライアント側: 多くをダウンロードして、ここでフィルタする。
- サーバー側: 必要なものだけを要求する。
ハイブリッドな構成は一般的です。良いパターンは、サーバーで「大きな」フィルタ(権限、所有権、日付範囲、検索)を強制し、UI上の小さなトグル(アーカイブを隠す、簡易タグ、列の表示/非表示)をローカルで処理して追加リクエストを避けることです。
ソート、ページネーション、検索も同じ判断に含めるべきです。ペイロードサイズ、ユーザー体感、公開するデータに影響します。
判断要因1:データ量とペイロードコスト
まず実用的な問い:クライアントでフィルタする場合にブラウザにどれだけのデータを送ることになるか?正直な答えが「数画面分より多い」なら、ダウンロード時間、メモリ使用、操作の遅さで代償を払います。
完璧な見積もりは要りません。桁感覚を掴んでください:ユーザーが見る可能性のある行数と、行ごとの平均サイズはどれくらいか。短いフィールド数個の500件と、長いノートやリッチテキスト、ネストされたオブジェクトを含む50,000件は全く異なります。
ワイドなレコードは静かなるペイロードキラーです。行数は少なく見えても、各行に多くのフィールド、大きな文字列、結合データ(担当者+企業+最終活動+住所全文+タグ)が含まれると重くなります。表示は3列でも、チームが「念のため全部送ろう」とするとペイロードが膨らみます。
また成長にも注意してください。今日問題ないデータセットが数ヶ月で耐え難くなることがあります。データが急速に増えるなら、クライアント側フィルタを短期的な抜け道と考え、デフォルトにしないでください。
経験則:
- 典型的なモバイル回線でフルデータセットを快適に送れないなら、サーバー側でフィルタする。
- ユーザーが一度に触れるのが小さなスライスだけなら、そのスライスを取得してサーバー側でフィルタする。
- データセットが小さく、安定していて公開しても安全なら、クライアント側フィルタは快適に感じられる。
最後のポイントはパフォーマンス以上に重要です。「ブラウザにフルデータセットを送れるか?」はセキュリティの問いでもあります。自信を持って「はい」と言えないなら送らないでください。
判断要因2:レイテンシと体感
フィルタリングの選択は正確さで失敗するより体感で失敗することが多いです。ユーザーはミリ秒を測りません。入力時の停止、フリッカー、結果が打鍵中に飛んだりするのを感じます。
時間は異なる場所で失われます:
- ネットワーク: リクエスト/レスポンス時間とペイロードサイズ
- サーバー: DBクエリ、結合、ソート、権限チェック
- ブラウザ: JSONの解析、行のレンダリング、大きな配列のフィルタ
この画面で「十分に速い」とは何かを定義してください。リストビューは入力時の応答性とスムーズなスクロールが必要なことが多く、レポートページは最初の結果が素早く出れば短い待ち時間を許容できます。
オフィスのWi‑Fiだけで判断しないでください。遅い接続では、クライアント側フィルタは初回ロード後は快適に感じますが、その初回ロード自体が遅い可能性があります。サーバー側はペイロードを小さく保ちますが、各キー入力でリクエストを発行すると遅延を感じさせることがあります。
人間の入力を設計に組み込みましょう。タイピング中はリクエストをデバウンスしてください。大きな結果セットでは段階的読み込みを使い、ページが何かを素早く表示してスクロール中もスムーズに保つようにします。
判断要因3:権限とデータ露出
権限は速度よりもフィルタリングアプローチを決めるべきです。ブラウザがユーザーに見せてはいけないデータを受け取った時点で問題です。ボタンを無効にする、列を折りたたむなどの見せ方はアクセス制御にはなりません。
この画面で何が機密かをまず名付けてください。明らかなフィールド(メール、電話、住所)もあれば、見落としやすいものもあります:内部メモ、コストやマージン、特別価格ルール、リスクスコア、モデレーションフラグなど。
最大の落とし穴は「クライアントでフィルタするが、許可された行だけ表示する」と考えることです。フルデータセットをダウンロードしてしまっていれば、誰でもネットワークレスポンスを調べたり、データを保存できます。UIで列を隠すだけではアクセス制御になりません。
サーバー側フィルタが安全なデフォルトとなる場合
ユーザーごとに権限が異なるとき(特に異なるユーザーが異なる行やフィールドを見られる場合)は、サーバー側フィルタが安全なデフォルトです。
簡単なチェック:
- 役割によって異なる行が見えるか(チームのみ、地域のみ、自分に割り当てられたもの)?
- 役割によって異なるフィールドが見えるか(メモ、価格、PII)?
- 行レベルのルールがあるか(アカウント所有者、案件ステージ、「非公開」フラグ)?
- エクスポート、ソート、合計が制限情報を明らかにしないか?
- 漏えいがコンプライアンス問題を引き起こすか?
いずれかが「はい」なら、フィルタとフィールド選択はサーバー側で適用してください。検索、ソート、ページネーション、エクスポートにも同じルールを適用すること。
例:CRMの連絡先リストで、営業担当は自分のアカウントのみを見られ、マネージャーはチーム全体を見られるとします。ブラウザが全連絡先をダウンロードしてローカルでフィルタすると、営業担当でもレスポンスから隠されたアカウントを復元できてしまいます。サーバー側フィルタはそのような行をそもそも送らないことで防げます。
判断要因4:キャッシュと鮮度
キャッシュは画面を瞬時に感じさせますが、間違った真実を見せることもあります。重要なのは何をどれくらい再利用できるか、どのイベントで無効化するかを決めることです。
まずキャッシュ単位を選んでください。フルリストをキャッシュするのは簡単ですが帯域の無駄で陳腐化しやすいです。無限スクロールにはページ単位のキャッシュがよく合います。クエリ結果(フィルタ+ソート+検索)をキャッシュすると正確ですが、ユーザーが多くの組み合わせを試すと項目数が急増します。
鮮度はドメインによって重要度が変わります。データが頻繁に変わる(在庫、残高、配送状況)なら30秒のキャッシュでもユーザーを混乱させます。遅く変わるデータ(アーカイブ済みレコード、参照データ)なら長めのキャッシュで問題ないことが多いです。
コードを書く前に無効化計画を立ててください。時間経過以外に、どの操作で必ずリフレッシュするかを決めます:作成/編集/削除、権限変更、一括インポートやマージ、ステータス遷移、アンドゥ/ロールバック、ユーザーがフィルタするフィールドを更新するバックグラウンドジョブなど。
またキャッシュの場所も決めてください。ブラウザメモリは戻る/進むを速くしますが、ユーザーや組織をキー化していないとアカウント間でデータを漏らします。バックエンドキャッシュは権限と一貫性面で安全ですが、フィルタ署名と呼び出し元の識別を含めないと結果が混ざるリスクがあります。
手順:新しい画面でどう選ぶか
目標は譲れません:画面は速く感じ、データを漏らさないこと。
実用的な意思決定フロー
- 最も強い制約から始める。 役割やチーム、地域、サブスクリプションによってアクセスが変わるなら権限を優先。データセットが大きいか急速に成長するならサイズを優先。
- データが大きいか機密ならサーバー側をデフォルトに。 ブラウザコンソールにフルデータをログするのが快適でないなら送らないでください。
- クライアント側フィルタは小さく安全な再利用リストに限定。 ステータスのドロップダウン、小さなタグリスト、承認済み結果の単一ページなど。
- UIの前にAPI形状を定義する。 フィルタ、ソート、ページネーション、デフォルトを文書化してサーバーとUIが食い違わないようにする。
- ガードレールを追加する。 最大ページサイズを強制し、タイムアウトを設定し、フィルタが遅いときUIがどう振る舞うかを決める(スピナーを表示、古い結果を保持、再試行を促すなど)。
漏えいや遅延を招く一般的なミス
多くのチームは同じパターンで躓きます:デモでは綺麗に見えたUIが、実データ、実権限、実ネットワーク速度で欠点を露呈します。
データを漏らすミス
最も深刻なのはフィルタリングを単なる表示処理と扱うことです。ブラウザが見てはいけないレコードを受け取った時点で負けです。
よくある原因:
- 速度のためにフルデータセットをブラウザに送り、ローカルでフィルタする。誰でもレスポンスを調べたりメモリ内から取り出せます。
- キャッシュされたフィルタ結果をユーザー識別子や組織、ロール、ポリシーバージョンでキー化せずに保存してしまう。権限変更で古いデータが露出する可能性があります。
例:インターンは自分の地域のリードのみ見るべきなのに、APIが全地域を返し、Reactでドロップダウンでフィルタしていると、インターンはフルリストを抽出できます。
画面を遅くするミス
遅延は多くの場合仮定から生まれます:
- クライアント側フィルタが常に速いと考えてしまう。50,000行ならダウンロードと解析の方が集中クエリより時間がかかることがあります。
- ページネーションと読み込み状態を忘れる。巨大なリストのレンダリングでブロックする画面は、クエリが正しくても壊れたように感じます。
もう一つ微妙で痛い問題はルールの不一致です。サーバーが「前方一致」、UIが「部分一致」を使うなどの差で、カウントが合わない、リフレッシュ後にアイテムが消えるといった現象が起きます。
出荷前の簡単チェックリスト
好奇心旺盛なユーザーと、回線の悪い日の2つの視点で最終確認をしてください。
- ペイロードの健全性: レスポンスにユーザーが見てはいけない行やフィールドが含まれていないか確認する。合計、グループカウント、IDで制限情報が推測されないかも見る。
- 安全なログ出力: フィルタにはメールや名前、IDが含まれることが多い。多くの人が見られる場所に敏感なフィルタ値やフルSQL、リクエストボディを記録しない。
- デバイス間の一貫性: デスクトップとモバイルで同じフィルタを適用する。差はクライアント側ソート、ロケールルール(大文字小文字、アクセント)、デフォルト値の違いから生じることが多い。
- 明確な状態表示: ローディング、空、エラーの状態をはっきりさせる。タイプやトグルを素早く切り替えたときにUIがフリッカーしたり古い結果を表示し続けないようにテストする。
- 最悪ケースのガードレール: 最大ページサイズ、最大日付範囲、タイムアウトを設ける。ワイルドカード、過剰なOR条件、インデックスされていないフィールドによる高コストクエリを防ぐ。
簡単なテスト:制限付きのレコードを作り、広くフィルタしたりフィルタをクリアしても、そのレコードがペイロード、合計、キャッシュに一度も現れないことを確認してください。
例:CRMの連絡先リスト
200,000件の連絡先があるCRMを想像してください。営業は自分のアカウントのみ、マネージャーはチーム全体、管理者は全てを見ることができます。画面には検索、フィルタ(ステータス、担当者、最終活動)、ソートがあります。
ここではクライアント側フィルタはすぐに破綻します。ペイロードが重くなり初回ロードが遅くなり、データ漏えいのリスクが高まります。UIが行を隠してもブラウザはデータを受け取っています。さらにデバイスに負担をかけます:大きな配列、重いソート、繰り返しのフィルタ実行、高メモリ使用、古い端末でのクラッシュ。
安全なアプローチはページネーション付きのサーバー側フィルタです。クライアントはフィルタ選択と検索テキストを送り、サーバーはユーザーが許可された行だけをフィルタ・ソートして返します。
実践パターン:
- まず権限を適用し、その後フィルタとソートを実行する。
- 1ページずつ(例:50行)と合計数を返す。
- キャッシュは慎重に(ユーザー別、またはロール別)行い、権限を跨いで結果が混ざらないようにする。
小さな例外として、微小で静的なデータはクライアント側で問題ありません。8個の値しかない「連絡先ステータス」ドロップダウンは一度読み込めばローカルでフィルタしてもほとんどリスクもコストもありません。
次のステップ:決定を文書化して書き直しを減らす
チームが一度の誤採用で燃えるわけではありません。同じ画面ごとに別の選択をして、漏えいや遅いページを差し迫った中で直そうとすることで痛手を被ります。
画面ごとに短い決定メモを書いてください:フィルタ一覧、データサイズ、ブラウザに送るコスト、「十分な速さ」の基準、機密フィールド、キャッシュ方針(するかしないか)など。サーバーとUIを揃えて、フィルタで「二つの真実」が生まれないようにします。
Koder.ai (koder.ai)で画面を素早く作っているなら、どのフィルタをバックエンドで強制し(権限や行レベルアクセス)、どの小さなUIトグルをReact側に残すかを事前に決める価値があります。その1つの選択が後で最も高価な書き直しを防ぎます。
よくある質問
いつサーバー側フィルタリングを使い、いつクライアント側を使うべきですか?
デフォルトでは、ユーザーごとに表示できるデータが異なる場合、大きなデータセットがある場合、またはページネーションや合計が一貫している必要がある場合はサーバー側を採用してください。クライアント側は、全データセットが小さく、公開しても安全で、ダウンロードが速い場合のみ使用します。
UIで行を隠していても、なぜクライアント側フィルタリングはセキュリティリスクになるのですか?
ブラウザが受け取ったものは誰でも確認できます。UI上で行や列を隠していても、ネットワークレスポンス、キャッシュ、メモリ内オブジェクトからデータを取得される可能性があります。
フィルタリングが遅く感じる原因は何ですか?
主に、過剰なデータを送って大きな配列を毎キーストロークでフィルタ・ソートするか、入力ごとにサーバーリクエストを送ってデバウンスしない場合に発生します。ペイロードを小さく保ち、各入力変更で重い処理を行わないでください。
クライアントとサーバーのフィルタを混在させても結果の不一致を避けるには?
「真の」フィルタを一元化してください:権限、検索、ソート、ページネーションはサーバーでまとめて適用します。クライアント側のロジックは、基礎データセットを変えない小さなUI用の切り替えに限定します。
フィルタ済みリストを安全にキャッシュする方法は?
クライアント側キャッシュは古いデータを表示したり、アカウント間でデータを漏らす可能性があります。サーバー側キャッシュは権限に安全ですが、フルのフィルタ署名と呼び出し元の識別(ユーザー/組織/ロール)を含めてキー化し、結果が混ざらないようにしてください。
データセットが「小さい」と判断するにはどう見積もればいいですか?
現実的にユーザーが持ちうる行数と、各行のバイト数を考えてください。一般的なモバイル接続や古い端末で快適に読み込めないなら、クライアント側は避け、サーバーでページネーションしてください。
異なるロールが異なる行やフィールドを見られる場合はどうする?
サーバー側です。役割やチーム、リージョン、所有権ルールで表示される行やフィールドが変わる場合、サーバーが行とフィールドのアクセスを強制すべきです。クライアントはユーザーが見るべきレコードとフィールドだけを受け取ります。
サーバー側フィルタ用のAPIはどう設計するべきですか?
先にAPIの契約を定義してください:受け付けるフィルタ項目、デフォルトのソート、ページネーションルール、検索の一致ルール(大文字小文字、アクセント、部分一致など)。そのロジックをバックエンドで一貫して実装し、合計やページが一致するかテストします。
サーバー側フィルタをUIで高速に感じさせるには?
タイピングのデバウンスで無駄なリクエストを防ぎ、古い結果を新しい結果が来るまで表示してフリッカーを抑えます。ページネーションや段階的読み込みで、巨大なレスポンスを待たずにユーザーに何かを早く見せてください。
データ件数が多く権限が厳しいCRMリストではどうするのが良いですか?
権限を先に適用し、その後フィルタとソートを行って1ページ分だけ(例:50行)と合計数を返すのが安全です。「念のため」に追加フィールドを送らないでください。キャッシュキーにはユーザー/組織/ロールを含め、営業担当がマネージャー向けのデータを受け取らないようにします。