ReoGrid ReoGrid Web

React で作るオンライン XLSX ビューア — ファイルはブラウザの外に出ない

· unvell team
React で作るオンライン XLSX ビューア — ファイルはブラウザの外に出ない

「xlsx viewer online」で検索すると、同じ仕組みのサイトがずらりと並びます — スプレッドシートをアップロードすると、相手のサーバーがそれをパースし、画像にして返してくる。公開用のサンプルファイルならそれで十分です。しかし給与計算表や顧客データのエクスポート、NDA 下のファイルとなると、あなたは自分のデータを第三者に渡したことになります — そしてたいてい、どこかに「保管する権利」を与えるチェックボックスがあります。

そうしたことを一切しないビューアを作れます。ファイル選択 → パース → 描画というパイプライン全体が、ブラウザ内で完結する。バイト列がネットワークに触れることはありません。本記事は、社内ツール・顧客ポータル・ドキュメントサイトのどこにでも組み込める、読み取り専用の XLSX ビューアを React で丸ごと示します。


ビューアの完成形

これがすべてです — ドラッグ&ドロップのドロップゾーン、ファイルピッカー、ファイル情報バー、そして読み取り専用のグリッド。xlsx の インポート は無料の Lite エディションで動くため、ライセンスなしで動作します。

import { Reogrid, type ReogridInstance } from '@reogrid/lite/react';
import { useRef, useState, type DragEvent } from 'react';

export default function XlsxViewer() {
  const gridRef = useRef<ReogridInstance>(null);
  const [file, setFile] = useState<{ name: string; size: number } | null>(null);
  const [dragging, setDragging] = useState(false);

  async function open(f: File) {
    if (!f.name.toLowerCase().endsWith('.xlsx')) return;
    const ws = gridRef.current!.worksheet;
    await ws.loadFromFile(f, { chunked: true });   // パース+描画をブラウザ内で
    ws.protected = true;                            // 閲覧専用: 全セルをロック
    setFile({ name: f.name, size: f.size });
  }

  function onDrop(e: DragEvent) {
    e.preventDefault();
    setDragging(false);
    const f = e.dataTransfer.files[0];
    if (f) open(f);
  }

  return (
    <div
      onDragEnter={(e) => { e.preventDefault(); setDragging(true); }}
      onDragLeave={(e) => { e.preventDefault(); setDragging(false); }}
      onDragOver={(e) => e.preventDefault()}
      onDrop={onDrop}
      style={{ position: 'relative', border: '1px solid #e2e8f0', borderRadius: 12, overflow: 'hidden' }}
    >
      <div style={{
        display: 'flex', alignItems: 'center', gap: 12,
        padding: '10px 14px', borderBottom: '1px solid #e2e8f0', background: '#f8fafc',
      }}>
        <label style={{ cursor: 'pointer', fontWeight: 600, color: '#1d4ed8' }}>
          .xlsx を開く
          <input
            type="file"
            accept=".xlsx"
            hidden
            onChange={(e) => e.target.files?.[0] && open(e.target.files[0])}
          />
        </label>
        <span style={{ fontSize: 13, color: '#64748b' }}>
          {file
            ? `${file.name} · ${(file.size / 1024).toFixed(0)} KB · 読み取り専用`
            : 'ファイルなし — ドラッグするか「.xlsx を開く」をクリック'}
        </span>
      </div>

      <Reogrid ref={gridRef} style={{ width: '100%', height: 600 }} />

      {dragging && (
        <div style={{
          position: 'absolute', inset: 0, background: 'rgba(59,130,246,0.08)',
          border: '2px dashed #3b82f6',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontSize: 16, fontWeight: 600, color: '#1d4ed8', pointerEvents: 'none',
        }}>
          ドロップして表示
        </div>
      )}
    </div>
  );
}

loadFromFile は、input やドロップイベントから受け取った File オブジェクトをそのまま渡せます。FileReader でバイト列を読み、OOXML を展開し、セル・スタイル・結合・罫線・数式を構築して Canvas に描画します。fetch もアップロードも SheetJS のグルーコードもありません。


「読み取り専用」こそがビューアの本質

ビューアはエディタではありません。ユーザーがセルをクリックして入力できてしまうと、「どこに保存するのか?」に答える必要が出てきます — そして設計上、バックエンドは存在しません。だからロックします:

worksheet.protected = true;

これで全セルが保護モードになります。ユーザーはセルの選択・スクロール・コピーといった、ビューアで してほしい 操作は引き続きできますが、セルへの入力は何も起きません。ドキュメントビューアとドキュメントエディタの違いが、たった 1 行です。

新しいファイルを開くとワークシートがリセットされるため、ロードの に設定してください。編集しようとしたときにフィードバックを出したい場合(「このファイルは読み取り専用です」というさりげないトーストなど)は、その試行を購読します:

worksheet.onProtectedCellEdit(({ row, column }) => {
  showToast('このファイルは読み取り専用です');
});

シートの大部分はロックしたまま、特定の範囲だけを解除することもできます — 「ビューア」に 1 列だけコメント入力欄を持たせたい、といった場合に便利です:

worksheet.protected = true;
worksheet.setRangeLock(1, 5, 200, 5, 'unlocked'); // F 列だけ編集可能にする

ドロップだけでなく URL からも表示する

ドラッグ&ドロップが目玉のインタラクションですが、「このスプレッドシートを表示」というリンクの多くは、すでに自分のオリジン上にあるファイル — レポート、テンプレート、添付ファイル — を指しています。ファイルピッカーを省いて直接ロードしましょう:

// 自分のオリジン上のファイル(または CORS 対応の任意の URL)
await worksheet.loadFromUrl('/reports/2026-q2.xlsx', { chunked: true });
worksheet.protected = true;

バイト列がすでにメモリ上にある場合 — API から取得した、IndexedDB から取り出した、base64 の blob からデコードした — そのまま渡してディスク経由のラウンドトリップを省けます:

const buf = await fetch('/api/export/4821').then(r => r.arrayBuffer());
await worksheet.loadFromBuffer(buf);
worksheet.protected = true;

これで viewer?src=/reports/2026-q2.xlsx のような共有可能なルートも数行です — クエリパラメータを読み、loadFromUrl を呼ぶだけ。ファイルはあくまでクライアントで描画され、URL はバイト列の出どころにすぎません。


何が正しく描画されるか

実世界の .xlsx ファイルは見た目以上に雑です — テーマカラー、共有文字列、リッチテキストラン、条件付きの数値書式セクション。インポータは現実的な範囲をカバーします: セルの値と数式、フォントと塗りつぶし、辺ごとの罫線、結合セル、通貨/日付/カスタム数値書式、ウィンドウ枠の固定、行/列のグループ化、条件付き書式、画像。既知の非対応はピボットテーブルとグラフです(ピボットはフラットな値として読み込まれ、グラフはスキップされます)。

完全な対応表と巨大ファイルのパフォーマンス — 44 万セルのファイルがチャンク式ロードで最初のフレームを約 40 ms で描画する話 — は、関連記事 ブラウザで本物の Excel ファイルをインポートする で扱っています。上のビューアにある { chunked: true } フラグが、大きなファイルでもタブをフリーズさせない仕組みです。深掘り記事でそのトレードオフを説明しています。


「印刷 / PDF 保存」を追加する

ブラウザの印刷ダイアログは、表示中の内容を紙や PDF で取得させる最もシンプルな方法です。Pro エディションはシートをクリーンな HTML テーブルとして描画し、ダイアログを開きます:

import { printWorksheet } from '@reogrid/pro';

<button onClick={() => printWorksheet(gridRef.current!.worksheet, {
  title: file?.name,
  orientation: 'landscape',
})}>
  印刷 / PDF 保存
</button>

ダイアログからユーザーは実際のプリンタか「PDF として保存」を選びます。サーバー側レンダリングもヘッドレス Chrome も不要 — いま見ているそのページが対象です。


ビューア用途での Lite と Pro

xlsx の インポートと読み取り専用モードは、どちらも無料の Lite エディションで動くため、上のコアビューアはコストゼロです。知っておくべき点が 1 つ: Lite は 100 行 × 26 列を超えるデータを切り詰めます。既知で範囲の決まったファイルなら問題ありません。しかし汎用ビューアは 任意の ユーザーファイルを受け付けます — 誰かが 5,000 行のエクスポートをドロップした瞬間、Lite は黙ってそれを切り落とします。

自分の管理外のファイルを扱うビューアを作るなら、Pro エディション がサイズ上限を解除し、printWorksheet、109 関数の数式ライブラリ、xlsx の エクスポート を追加します。ビューアのコードは同一で、@reogrid/lite/react@reogrid/pro/react に差し替えるだけです。

Lite(無料)Pro
xlsx インポート+描画
読み取り専用 / 保護モード
グリッドサイズ100 × 26無制限
印刷 / PDF 保存
xlsx エクスポート

クライアントサイドが正しいデフォルトである理由

サーバーサイドのビューアは、数 MB のアップロード、自社マシンでのパース CPU、一時ファイルのライフサイクル、そして自分で書いて守らねばならないプライバシーの説明を意味します。ユーザーに自分のファイルを見せるだけのために、誰も頼んでいない仕事ばかりです。

クライアントサイドなら、ファイルは端末から出ず、ロード時間はユーザーの CPU だけに制限され、保持・記録・漏洩すべきものが何もありません。「.xlsx を開く」はローカルな操作のまま — それこそ人々がビューアに当然期待することであり、そして「online xlsx viewer」サイトが実際にはほぼ決してやらないことです。

試してみてください: ライブの XLSX ビューアデモ にご自身の .xlsx をドロップし、ネットワークタブが空のままなのを確認してください。次は 編集可能な ツールを作りますか? もう半分の物語は 編集可能な数式付き React 製インボイスを構築する をご覧ください。

ReoGrid Web を試してみる

React/Vue 向けの Canvas ベース Excel 互換スプレッドシートコンポーネント。 Lite は無料 — npm install 一発で始められます。

関連記事

ニュースレター

開発の最新情報をお届けします

新しいリリース・機能追加・お知らせをいち早く受け取るには、
メーリングリストにご登録ください。