MENU

【DataTables】大量データの表示を高速化する方法

DataTablesは小さな一覧ならほとんど意識せずに使えますが、数千件、数万件のデータを一度に読み込むと話が変わります。
初期表示、検索、並び替え、DOM生成のどこかで急に重さが出ます。

この記事では、大量データを扱うときに見直したいDataTablesの設定と、サーバー側へ寄せる判断ポイントを整理します。

目次

公式ページ

遅くなる原因

DataTablesが遅くなる主な原因は、ブラウザ側で処理する量が多くなりすぎることです。

たとえば、以下のような処理がすべてブラウザ側で発生します。

  • HTMLのtrtdを大量に作成する
  • 全データを対象に検索する
  • 全データを対象に並び替える
  • columns.renderで全行分の表示を加工する
  • 1ページに表示する行数が多くDOMが重くなる

数百件程度なら問題にならなくても、数千件を超えると端末やブラウザによって体感差が出やすくなります。
数万件以上を扱う場合は、最初からサーバー側での処理を検討した方が安全です。

まずはページングを使う

大量データを1画面にすべて表示すると、DOMが重くなります。
DataTablesのページングを使い、1ページあたりの表示件数を必要な範囲に抑えます。

new DataTable('#usersTable', {
    pageLength: 25
});

pageLengthは、1ページに表示する行数を指定するオプションです。
初期値は10ですが、一覧の用途に合わせて2550などに調整します。

「全部見たいから1000件表示」にしてしまうと、DataTablesを使っていても画面は重くなります。
表示件数は、ユーザーが実際に見比べられる範囲に抑えるのがおすすめです。

DataTables 2系ではdeferRenderが標準で有効

DataTablesにはdeferRenderというオプションがあります。
これは、AjaxやJavaScript配列からデータを読み込むとき、必要になった行のDOMだけを作成するための設定です。

new DataTable('#usersTable', {
    data: users,
    deferRender: true,
    pageLength: 25,
    columns: [
        { data: 'id', title: 'ID' },
        { data: 'name', title: '名前' },
        { data: 'email', title: 'メール' }
    ]
});

DataTables 2系では、deferRenderの初期値はtrueです。
そのため、DataTables 2系を使っている場合は明示しなくても有効になっています。

ただし、以下の点には注意が必要です。

  • ajaxdataで読み込むデータに対して効果が出やすい
  • ページングが有効なときに効果が出やすい
  • まだ表示されていない行のDOMは存在しない

たとえば、初期表示直後に全行のtrへ直接イベントを付けるような書き方は避けます。

const table = new DataTable('#usersTable', {
    data: users
});

table.on('click', 'tbody tr', function () {
    const rowData = table.row(this).data();
    console.log(rowData);
});

イベントは、DataTablesのイベントや委譲イベントとして扱うと、後から作られる行にも対応しやすくなります。

HTMLに大量のtbodyを書かない

大量データを最初からHTMLのtbodyに書いておくと、ブラウザがHTMLを解析する時点で重くなります。

<table id="usersTable">
    <thead>
        <tr>
            <th>ID</th>
            <th>名前</th>
            <th>メール</th>
        </tr>
    </thead>
    <tbody>
        <!-- ここに数千件、数万件のtrを書くと重くなりやすい -->
    </tbody>
</table>

大量データを扱う場合は、HTMLにはテーブルの枠だけを用意し、データはAjaxやJavaScript配列で渡す方が扱いやすくなります。

<table id="usersTable" class="display">
    <thead>
        <tr>
            <th>ID</th>
            <th>名前</th>
            <th>メール</th>
        </tr>
    </thead>
</table>
new DataTable('#usersTable', {
    ajax: '/api/users',
    columns: [
        { data: 'id', title: 'ID' },
        { data: 'name', title: '名前' },
        { data: 'email', title: 'メール' }
    ]
});

この形にしておくと、deferRenderやサーバーサイド処理へ移行しやすくなります。

数万件以上はserverSideを検討する

大量データの高速化で一番重要なのは、ブラウザに全件を渡さないことです。

DataTablesにはserverSideオプションがあります。
これを有効にすると、ページング、検索、並び替えのたびにDataTablesがサーバーへAjaxリクエストを送り、必要なページ分だけを取得します。

new DataTable('#usersTable', {
    processing: true,
    serverSide: true,
    ajax: {
        url: '/api/users',
        type: 'POST'
    },
    columns: [
        { data: 'id', title: 'ID' },
        { data: 'name', title: '名前' },
        { data: 'email', title: 'メール' },
        { data: 'createdAt', title: '登録日' }
    ]
});

processing: trueを付けておくと、処理中であることをユーザーに伝えやすくなります。

サーバーサイド処理では、DataTablesから以下のような情報が送られます。

  • start: 何件目から取得するか
  • length: 何件取得するか
  • search[value]: 検索文字列
  • order: 並び替え対象の列
  • columns: 列の定義情報

サーバーは、これらを使ってDBから必要な範囲だけ取得します。

返却するJSONは、基本的に以下の形です。

{
    "draw": 1,
    "recordsTotal": 100000,
    "recordsFiltered": 1250,
    "data": [
        {
            "id": 1,
            "name": "山田 太郎",
            "email": "yamada@example.com",
            "createdAt": "2026-06-28"
        }
    ]
}

recordsTotalは全体件数、recordsFilteredは検索条件に一致した件数です。
dataには、現在のページに表示する分だけを入れます。

serverSideで注意すること

serverSide: trueにすると、検索や並び替えはサーバー側の実装に依存します。
フロント側の設定だけでは速くなりません。

サーバー側では、以下を意識します。

  • DB側でLIMITOFFSETを使い、必要な件数だけ取得する
  • 検索対象のカラムにインデックスを検討する
  • 並び替えできるカラムを制限する
  • 正規表現検索など重い検索を安易に有効にしない
  • 返却する列を必要最小限にする
  • drawは整数として扱う

特に、DataTablesから送られてきた列名や並び替え条件をそのままSQLに埋め込むのは危険です。
サーバー側では、並び替え可能なカラムをホワイトリストで定義しておきます。

const ORDER_COLUMNS = {
    0: 'id',
    1: 'name',
    2: 'email',
    3: 'created_at'
};

const orderColumnIndex = Number(request.order?.[0]?.column ?? 0);
const orderColumn = ORDER_COLUMNS[orderColumnIndex] ?? 'id';
const orderDirection = request.order?.[0]?.dir === 'desc' ? 'DESC' : 'ASC';

上記は考え方の例です。
実際には利用しているバックエンドやDBライブラリに合わせて、安全にパラメータを扱ってください。

検索のたびに重い場合はsearchDelayを使う

検索欄に文字を入力するたびに再描画やAjaxが走ると、データ量が多い画面では重く感じることがあります。

その場合は、searchDelayを指定します。

new DataTable('#usersTable', {
    serverSide: true,
    processing: true,
    ajax: '/api/users',
    searchDelay: 500,
    columns: [
        { data: 'id', title: 'ID' },
        { data: 'name', title: '名前' },
        { data: 'email', title: 'メール' }
    ]
});

searchDelayは、検索欄への入力から実際に検索を実行するまでの待ち時間をミリ秒で指定します。
DataTables 2系では、指定した時間だけ入力が止まってから検索が実行されます。

サーバーサイド処理では、検索のたびにAPIが呼ばれます。
入力ごとにリクエストが飛びすぎる場合は、searchDelayで調整するとサーバー負荷を下げられます。

検索やソートが不要な列は無効化する

操作ボタンや詳細リンクなど、検索や並び替えの対象にする必要がない列は、searchableorderablefalseにします。

new DataTable('#usersTable', {
    ajax: '/api/users',
    columns: [
        { data: 'id', title: 'ID' },
        { data: 'name', title: '名前' },
        { data: 'email', title: 'メール' },
        {
            data: null,
            title: '操作',
            searchable: false,
            orderable: false,
            render: function () {
                return '<button type="button">詳細</button>';
            }
        }
    ]
});

クライアント側処理では、不要な列を検索対象から外すことで検索時の処理を減らせます。
サーバーサイド処理でも、DataTablesからcolumns[i][searchable]columns[i][orderable]が送られるため、サーバー側の検索・並び替え対象を絞る判断に使えます。

renderでは重い処理をしない

columns.renderは便利ですが、表示、検索、ソートなどのタイミングで何度も呼び出されます。
ここで重い処理をすると、表示や検索が遅くなります。

避けたい例です。

{
    data: 'description',
    title: '説明',
    render: function (data) {
        return marked.parse(data);
    }
}

Markdown変換や複雑な日付整形などを毎回renderで行うと、行数が増えたときに重くなります。

できるだけ、API側で表示用の値を作っておくか、軽い変換に留めます。

{
    data: 'statusLabel',
    title: 'ステータス'
}

物理値から表示用ラベルへ変換する程度であれば問題になりにくいですが、複雑な加工はサーバー側や事前処理に寄せると安定します。

Scrollerで仮想スクロールにする

ページングではなく、スクロールしながら大量データを見せたい場合は、Scroller拡張を使う方法があります。

Scrollerは、画面に見えている範囲の行を中心に描画する仮想レンダリングの拡張です。
大量行をスクロール表示したいときに向いています。

new DataTable('#usersTable', {
    ajax: '/api/users',
    deferRender: true,
    scrollY: 400,
    scroller: true,
    columns: [
        { data: 'id', title: 'ID' },
        { data: 'name', title: '名前' },
        { data: 'email', title: 'メール' }
    ]
});

Scrollerを使う場合は、以下の組み合わせを意識します。

  • ajaxまたはdataでデータを渡す
  • deferRenderを有効にする
  • scrollYで縦スクロール領域を指定する
  • scroller: trueを指定する

HTMLに大量のtrを最初から書いている場合、Scrollerの効果は出にくくなります。
Scrollerを使うなら、AjaxやJavaScript配列からデータを渡す形にしましょう。

一度に返すデータを減らす

Ajaxでデータを取得している場合、必要以上に大きなJSONを返していないかも確認します。

たとえば、一覧に表示しない詳細本文や備考を全件分返していると、通信量とブラウザ側のメモリ使用量が増えます。

一覧では必要な項目だけ返します。

{
    "id": 1,
    "name": "山田 太郎",
    "email": "yamada@example.com",
    "statusLabel": "有効"
}

詳細画面でしか使わない項目は、詳細画面を開くときに別APIで取得する方が軽くなります。

初期表示で不要な機能を減らす

DataTablesは多機能ですが、すべての機能を使う必要はありません。
不要な機能を無効化すると、処理やUIを少し軽くできます。

new DataTable('#usersTable', {
    ajax: '/api/users',
    paging: true,
    searching: true,
    ordering: true,
    info: true,
    columns: [
        { data: 'id', title: 'ID' },
        { data: 'name', title: '名前' },
        { data: 'email', title: 'メール' }
    ]
});

たとえば、検索が不要ならsearching: false、並び替えが不要ならordering: falseにします。

new DataTable('#usersTable', {
    ajax: '/api/users',
    searching: false,
    ordering: false,
    columns: [
        { data: 'id', title: 'ID' },
        { data: 'name', title: '名前' },
        { data: 'email', title: 'メール' }
    ]
});

ただし、速度のために必要な機能まで削ると使いにくい画面になります。
実際に必要な操作を確認し、不要なものだけを外すのがよいです。

データ量ごとの目安

厳密な境界は、列数、端末性能、renderの重さ、検索条件などで変わります。
ただ、考え方の目安は以下です。

データ量 方針
数百件 通常のDataTablesで十分なことが多い
数千件 deferRender、ページング、不要な検索・ソートの無効化を検討
数万件以上 serverSide: trueを検討
大量行をスクロール表示したい Scrollerを検討
検索やソートが重い サーバー側処理、DBインデックス、searchDelayを検討

「何件から必ずサーバーサイドにする」というより、ユーザーが操作したときの体感速度で判断します。
初期表示、検索、並び替え、ページ移動を実際に確認して、重い箇所に合わせて対策を選びます。

実装例

数千件程度で、ブラウザ側で処理する場合の例です。

new DataTable('#usersTable', {
    ajax: '/api/users',
    pageLength: 25,
    deferRender: true,
    searchDelay: 300,
    columns: [
        { data: 'id', title: 'ID' },
        { data: 'name', title: '名前' },
        { data: 'email', title: 'メール' },
        {
            data: 'memo',
            title: 'メモ',
            orderable: false
        },
        {
            data: null,
            title: '操作',
            searchable: false,
            orderable: false,
            render: function () {
                return '<button type="button">詳細</button>';
            }
        }
    ]
});

数万件以上で、サーバー側に処理を寄せる場合の例です。

new DataTable('#usersTable', {
    processing: true,
    serverSide: true,
    ajax: {
        url: '/api/users/search',
        type: 'POST'
    },
    searchDelay: 500,
    pageLength: 25,
    columns: [
        { data: 'id', title: 'ID' },
        { data: 'name', title: '名前' },
        { data: 'email', title: 'メール' },
        { data: 'statusLabel', title: 'ステータス' },
        {
            data: null,
            title: '操作',
            searchable: false,
            orderable: false,
            render: function () {
                return '<button type="button">詳細</button>';
            }
        }
    ]
});

大量データでは、フロントエンドだけで頑張るより、DBが得意な検索や並び替えをサーバー側に任せる方が安定します。

まとめ

大量データの一覧では、ブラウザ側で抱えるデータ量とDOM量を減らすのが基本です。

  • 1ページの表示件数を増やしすぎない
  • DataTables 2系ではdeferRenderが標準で有効
  • HTMLに大量のtbodyを書かず、Ajaxやdataで渡す
  • 数万件以上はserverSide: trueを検討する
  • 検索リクエストが多すぎる場合はsearchDelayを使う
  • 検索やソートが不要な列はsearchable: falseorderable: falseにする
  • columns.renderでは重い処理を避ける
  • スクロール表示が必要ならScrollerを検討する
  • 一覧に不要なデータはAPIで返さない

少量データならDataTablesの設定だけで十分なことも多いですが、大量データではサーバーサイド処理が本命になります。
初期表示、検索、並び替え、ページ移動のどこが遅いのかを確認し、原因に合った対策を選びましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次