機密情報を守る:React + Go で作る「完全クライアント完結型」QRコード生成器
社内の Wi-Fi 情報や限定公開 URL を共有する際、既存の SaaS 型 QR 生成サービスを利用すると「入力値が第三者の環境に送信される」という構造的なセキュリティリスクが伴います。
この課題を解決するため、「ブラウザ内で生成が完結し、サーバー側には一切データを送らない」構成の QR コード生成ツールを開発しました。本記事では、そのアーキテクチャと実装のポイントを紹介します。
GitHub リポジトリ: ttokunaga-jp/QR_Code_Generator
なぜこのリポジトリを作ったのか
もっとも大きな動機は 情報漏洩リスクの排除 です。
- プライバシーの担保: Wi-Fi のパスワードや機密性の高い URL を外部サーバーに送信したくない。
- 静的アセットの配布: 実行時にサーバーサイドのロジックを必要とせず、ビルド済みの静的ファイルのみを配信する構成にしたい。
- セルフホストの容易さ: Docker 一つでどこでも安全に動かせるようにしたい。
最初に決めた開発方針
開発にあたり、以下の 4 つのポリシーを策定しました。
-
ミニマル構成:
frontend/とbackend/のシンプルなディレクトリ構成を維持。 - 静的配信のみ: 実行時にサーバーへ入力を送らない。Go サーバーは単なる配信基盤として機能させる。
-
再現性の保証:
Makefileと Docker マルチステージビルドを活用し、環境に依存せず同じ成果物を得られるようにする。 -
運用の簡素化:
docker-compose.ymlでプロキシ設定などを切り替え可能にし、CI/CD への組み込みを容易にする。
アーキテクチャの概要
システム全体の流れは以下の通りです。
┌────────────┐ ビルド ┌───────────────┐
│ React + Vite │─────────▶│ /frontend/build │
└────────────┘ └───────────────┘
▲ │
│ i18next, Tailwind │ STATIC_DIR=/app/build
│ ▼
┌────────────┐ go build ┌───────────────┐
│ Go server │───────────▶│ /server (bin) │
└────────────┘ └───────────────┘
│
Docker multi-stage
│
alpine runtime
フロントエンド
React + TypeScript + Vite を採用。
QRCode.toCanvas を用いて、ブラウザの DOM 内で直接 QR コードを描画します。i18next による日英切り替えにも対応しています。
バックエンド
Go 1.22 による軽量な静的ファイルサーバーです。
/healthz エンドポイントを備え、コンテナの死活監視に対応。入力値を受け取る API は一切持ちません。
インフラ
Node → Go → Alpine の 3 段マルチステージ構成の Dockerfile を作成。最終的なイメージサイズを最小化しています。
フロントエンド実装の工夫
ブラウザ内での QR 生成
入力イベントをトリガーに、ライブラリを用いて即時描画を行っています。
const wifiString = `WIFI:T:${encryption};S:${ssid};P:${password};H:false;;`;
await QRCode.toCanvas(canvasRef.current, wifiString, {
width: qrSize,
margin: 2,
color: { dark: '#000000', light: '#FFFFFF' }
});
-
バリデーション: Wi-Fi モードでは SSID 必須チェック、URL モードでは
new URL()による形式検証を実施し、誤った QR 生成を抑制。 -
ダウンロード機能: Canvas の
toDataURLを活用し、PNG 画像として保存可能。追加ライブラリなしでブラウザ標準 API のみで完結させました。
バックエンド実装の工夫
Go で書かれたサーバーは、配信時の安定性を高めるための実装を施しています。
-
SPA ルーティング: フロントエンド側でのルーティング拡張に備え、存在しないパスへのリクエストは
index.htmlを返すようにハンドリング。 -
キャッシュ制御:
index.htmlの更新が即座に反映されるよう、Cache-Control: no-storeを明示。 -
環境変数による柔軟性: 配信ディレクトリ (
STATIC_DIR) やポート番号を環境変数で上書き可能にしています。
ビルドとデプロイの流れ
Makefile によるローカル再現
開発者が迷わないよう、コマンドを共通化しています。
make build # npm ci → npm run build → go build
make frontend-build
make backend-build
Docker マルチステージビルド
Dockerfile 内で以下のプロセスを完結させています。
- frontend-builder: Node 環境で Vite ビルドを実行。
- server-builder: Go 環境で配信サーバーのバイナリを作成。
- runtime: 最小限の Alpine イメージに上記 2 つの成果物のみをコピー。
これにより、攻撃面(Attack Surface)を最小限に抑えた実行環境が構築されます。
セキュリティ観点でのまとめ
- データの局所性: 入力値はブラウザから外に出ません。
- 透明性: 「サーバーへ送信しない」ことを UI 上で明示。
- 堅牢なイメージ: ランタイム層にビルドツールを含めないことで、脆弱性リスクを低減。
これからの拡張アイデア
今後、さらに利便性を高めるために以下の機能を検討しています。
- PWA 化: Service Worker を導入し、完全オフライン環境での QR 生成を実現。
-
テンプレート保存: よく使う設定を
LocalStorageに保存。 - カスタマイズ性: ロゴの合成やカラー変更機能の追加。
「入力値を預からない」というシンプルな設計ですが、社内ツールとしては非常に強力な安心感を提供できます。似たような課題感をお持ちの方は、ぜひリポジトリを参考にしてみてください。
実装の検証状況(2025-12-01)
ローカル環境および Docker コンテナ上にて、Wi-Fi モード・URL モード双方の生成・ダウンロード機能が正常に動作することを確認済みです。
Discussion