Zip Share は、複数ファイルをアップロード・自動でZIP圧縮し、期限付きダウンロードリンクを生成するファイル共有アプリです。
クライアント側で ZIP 化 → Cloudflare R2 に保存し、メタ情報と有効期限を D1 に記録します。
Next.js(App Router)+ Hono を Cloudflare Workers 上で動かすサーバーレス構成で、高速かつシンプルな共有体験を実現しています。
会員登録不要で利用できます。
URL:https://zip-share-app.mark-reo.workers.dev/
| トップ画面 | ファイル追加画面 |
|---|---|
![]() |
![]() |
| URL発行画面 | ダウンロードページ |
|---|---|
![]() |
![]() |
フロントエンドからエッジ実行環境まで一気通貫で設計・実装できる力を示すため、日常でよく使う「一時的なファイル共有」を題材にしました。
狙いは、Cloudflare スタック(Workers / R2 / D1)を実務レベルで使いこなすこと。
Wrangler を用いたローカル〜デプロイの流れ、R2 / D1 のバインディング、Hono での API 設計、そして R2 にオブジェクト/D1 にメタ情報という責務分離をまとめて検証しています。
採用事例が増えており、低レイテンシ・無料枠の手軽さ・セキュリティ面の利点が大きいことも選定理由です。
成果物は 「Next.js × Hono × Cloudflare(Workers / R2 / D1)」の最小テンプレートとして、今後の個人/チーム開発でも流用できるよう整理しています。
- フロントエンド:TypeScript / React 19 / Next.js 15(App Router)/ Tailwind CSS v4 / Heroicons
- データベース:Cloudflare D1 + Drizzle ORM
- ストレージ:Cloudflare R2(オブジェクトストレージ)
- API:Hono(Cloudflare Workers)
- アップロード、圧縮:react-dropzone(選択・ドラッグ&ドロップ)、JSZip(複数ファイルの ZIP 化)
- アクセシビリティ:セマンティック HTML / WAI-ARIA
- デザイン:Figma
-
ファイルアップロード機能
- ドラッグ&ドロップ / クリック・タップでファイル選択可能
- react-dropzone を使用し、UXを意識した実装
-
複数ファイルの自動 ZIP 圧縮
- 2 件以上の選択時はクライアント側で JSZip により ZIP 化
- ワーカーのメモリ負荷を軽減し、アップロード回数を最小化
-
ファイルサイズ上限&超過ガード(単一 / 合計)
- 上限は
MAX_UPLOAD_BYTES(/lib/constants.ts)で一元管理(※現在はテスト用 5MB、本番想定 1GB) react-dropzoneのmaxSizeにより、上限を超える単一ファイルは追加されず、エラー通知(ファイル名を表示)- ファイル合計サイズも事前チェックし、超過した場合は追加せず
ErrorAlertで通知 - 上限超過時や送信中は選択・送信を自動で無効化(
canAddFiles/canUploadFiles) - 容量表記は
formatSize()(/lib/formatBytes.ts)で B/KB/MB/GB に統一
- 上限は
-
有効期限付き共有リンク生成
- 1日 / 3日 / 5日 / 7日から選択可能
- DB に有効期限を記録し、期限切れアクセスは自動でブロック
-
Cloudflare R2 へのファイル保存
- バイナリデータをR2にアップロードし、メタデータはD1へ保存
- ZIP のときは
application/zip、それ以外は元のContent-Typeを付与
-
ファイルダウンロード処理
- 固有の
/files/[id]ページからファイルをダウンロード可能 - 有効期限チェックや
Content-Typeの制御を行い、安全なレスポンスを提供
- 固有の
-
API 構築(Hono + Cloudflare Workers)
POST /api/upload:アップロード+メタ保存+URL 返却GET /api/files/:id:メタ情報取得GET /api/download/:id:期限チェック後にファイル配信
-
Cloudflare D1 + Drizzle ORM によるDB構築
- 型安全な schema 定義と migration に対応
-
フロント側のファイル追加のリスト描画
- React の list key は配列 index ではなく UUID を採用(並べ替え・削除時の再利用バグ回避)
-
アクセシビリティ対応
- セマンティック HTML、ラベルの関連付け、WAI-ARIA 配慮などを実装
├── app/
│ ├── global.css
│ ├── layout.tsx
│ ├── page.tsx # アップロード画面(メインのフロント画面)
│ ├── components/
│ │ ├── DropArea.tsx # ドラッグアンドドロップ( またはクリック )領域
│ │ ├── ErrorAlert.tsx # エラー表示UI
│ │ ├── FileList.tsx # 選択ファイル一覧・合計表示
│ │ ├── UploadActions.tsx # 期限選択・アップロード等の操作
│ │ └── UploadResultCard.tsx # アップロード成功時の共有URL発行
│ ├── api/
│ │ └── [[...route]]/
│ │ └── route.ts # Hono + Drizzle + Cloudflare Workers による API 定義
│ └── files/[id]/
│ ├── page.tsx # ダウンロード画面
│ └── client.tsx # クライアント側のダウンロード処理
├── db/
│ └── schema.ts # Drizzle ORM のスキーマ定義(filesテーブル)
├── public/
│ └── ... # 静的アセット(faviconなど)
├── types/
│ └── upload.ts # 型定義( UploadResult / ExpirationOption / FileRow )
├── lib/
│ ├── constants.ts # 上限などの定数(例: MAX_UPLOAD_BYTES)
│ └── formatBytes.ts # 容量表記のフォーマッタ(B/KB/MB/GB)
├── drizzle/
│ └── migrations/ # Drizzleによるマイグレーションファイル
│ ├── meta/
│ └── [ランダムな識別子].sql # 自動生成(例: 0000_xxx.sql)
├── .env # 環境変数(ローカル用・コミット対象外)
├── .env.example # 環境変数テンプレート(コミット対象)
├── .gitignore
├── cloudflare-env.d.ts # Cloudflare 用の型定義(env, DB, R2)
├── drizzle.config.ts # Drizzle設定ファイル
├── next.config.ts # Next.jsの設定ファイル
├── open-next.config.ts # OpenNextの設定ファイル
├── package.json
├── postcss.config.mjs
├── tailwind.config.ts
├── tsconfig.json
└── wrangler.jsonc # Cloudflare Workers 専用設定(D1, R2 を含む)※Pages未使用
Next.js の App Router 構成に加え、Cloudflare Workers(D1 / R2)によるサーバーレス API 処理と、Drizzle ORM による型安全なデータ操作を組み合わせています。
前提
- Node.js v20 以上
- Cloudflare アカウントの作成(R2ではクレカ登録が必要)
- Wrangler のインストール(
npm install -g wranglerまたはnpm install -D wrangler)
git clone <このリポジトリURL>
cd zip-share-app
npm installnpx wrangler loginA. このリポジトリの既定名で作る(推奨)
wrangler.jsonc の既定と揃うので、そのまま動きます。
# D1 データベース
npx wrangler d1 create zip-share-app
# R2 バケット
npx wrangler r2 bucket create zip-share-app↑ で出力された database_id を、wrangler.jsonc の該当箇所へ貼り付けます。
bucket_name は「作成した名前」と一致していれば OK(ID の貼り付けは不要)
B. 別名で作る場合
任意のプロジェクト名で作ってもOKですが、wrangler.jsonc の database_name / bucket_name を同じ名前に揃えてください。
npx wrangler d1 create your-project
npx wrangler r2 bucket create your-bucket// wrangler.jsonc(抜粋・別名に揃える)
{
"d1_databases": [
{
"binding": "DB",
"database_name": "your-project",
"database_id": "<create の出力 database_id>"
}
],
"r2_buckets": [
{
"binding": "R2",
"bucket_name": "your-bucket"
}
]
}
以下を参考にするか、プロジェクト直下の .env.example をコピーして .env を作成してください。
# Cloudflare Account
CLOUDFLARE_ACCOUNT_ID=xxxxxxxxxxxxxxxxx
# Cloudflare D1
CLOUDFLARE_DATABASE_ID=xxxxxxxxxxxxxxxxx
CLOUDFLARE_D1_TOKEN=xxxxxxxxxxxxxxxxx
# アプリのベースURL(必ず preview の出力と同じもの)
# 例: Windows → http://127.0.0.1:8787 / macOS → http://localhost:8787
BASE_URL=http://127.0.0.1:8787※.env.example はテンプレートとしてリポジトリに含めています。
.gitignore により .env と .env*.local はコミット対象外です。
npm run db:pushnpm run previewメモ:UI の動作確認だけなら npm run dev でもOK。R2/D1 を伴うアップロードは preview 推奨です。
npm run deploy
このリポジトリを使わず最小構成で試す(オプション)
このリポジトリをクローンせずに、「OpenNext × Cloudflare」の構成で、ゼロから動作・構築をしたい方向けです。
- Wrangler ログイン
npx wrangler login- Next.js を Cloudflare テンプレートで作成
npm create cloudflare@latest -- my-next-app --framework=next
cd my-next-app
npm install- OpenNext を採用(本リポと同様のビルド/プレビュー)
npm run previewWSL2(Ubuntu)上での実行を推奨します。ネイティブ Windows だと、OpenNext + Cloudflare Workers のローカル検証でパス解決やファイル監視差異が原因のエラーに遭遇する場合があります。
既知の不具合回避としてツールのバージョンを下げる対応は基本非推奨です。まずは WSL2 を利用する構成をご検討ください。
macOS 環境がある場合は、macOS での構築・検証を推奨します。
- トップページでファイルをドロップまたは選択
- 有効期限を選択
- 「アップロード」ボタンを押すと、ZIP圧縮 → アップロード完了後に URL が表示される
- 表示された URL をコピーして共有したい相手に共有
- URL にアクセスすることでファイルをダウンロード可能(期限付き)
期限切れファイルの自動削除(Cron Triggers)を実装しようと考えています。
D1 の expiresAt を基準に Cloudflare Workers の Cron を定期実行し、R2 のオブジェクト削除 → 対応する D1 レコードのクリーンアップを自動化します。
R2 の Lifecycle ルールは「アップロードからの経過日数」基準のため、 可変 TTL(1/3/5/7日) に正確に対応できる本方式を採用予定です。
このプロジェクトは MIT License のもとで公開されています。



