TrayPage Docs

Cloudflareでセルフホストする

自分のCloudflareアカウントにTrayPageを立てる手順。Deploy to Cloudflareボタン、Postgres、カスタムドメイン、メールまで。

自分のCloudflareアカウントでTrayPageを動かします。サーバーの管理は不要です。構成の比較は セルフホスト を参照してください。

ブラウザ / AIエージェント
  → カスタムドメイン(アプリ用・閲覧用の2つ)
  → Worker(DBの近くで実行)
      ├─ Hyperdrive → Postgres(PlanetScale または Neon)
      ├─ R2(ページ本文)
      └─ Email Service(ログインリンク・招待)

前提

必要なもの理由
Workers Paid プラン($5/月)無料プランのCPU時間の上限(1リクエストあたり10ms)では、ログインやページ表示の処理が収まりません。必須です
PostgresPlanetScale(Cloudflareに請求。$5/月〜)またはNeon(無料枠あり)
GitHubまたはGitLabのアカウントボタンはリポジトリをあなたのアカウントにコピーし、そこへのpushで再デプロイします
Cloudflareで管理しているドメインアプリ用と閲覧用の2つのカスタムドメイン、およびメールの送信元に使います

費用の目安は、Workers PaidとDBで月$5〜10程度からです。

1. Postgresを作る

PlanetScale

  1. Cloudflareのダッシュボードで Storage & databases → Postgres & MySQL (Hyperdrive) を開き、Create PlanetScale database を選びます。
  2. エンジンは Postgres を選びます。リージョンは利用者の多い地域に近い場所にします(例: 日本なら aws-ap-northeast-1)。このリージョンを控えておいてください(手順5で使います)。
  3. 作成が終わるまで待ちます。

Neon

  1. Neon でprojectを作り、リージョンを控えます(例: aws-ap-southeast-1)。
  2. 接続文字列(postgresql://...)を控えます。Hyperdriveが接続プールを持つので、ホスト名に -pooler が付くものではなく、直接の接続文字列を使います。

2. Hyperdriveを作ってDBにつなぐ

DBを作っただけでは、Hyperdriveの設定は作られません。ボタンでは作成済みのHyperdriveの設定を選ぶので、先に作っておきます。

  • PlanetScale: 同じ画面で Create Hyperdrive Connection を選び、手順1のDBを選びます。

  • Neon: Storage & databases → Hyperdrive → Create configuration で、手順1の接続文字列を入力します。CLIでは次のとおりです。

    npx wrangler hyperdrive create traypage --connection-string="postgresql://USER:PASSWORD@HOST/DB?sslmode=require"

3. ボタンでデプロイする

  1. TrayPageのリポジトリのREADMEにある Deploy to Cloudflare ボタンを押します。

  2. 画面で次を設定します。

    項目設定
    Gitアカウント・リポジトリ名コピー先。以後の設定変更はこのリポジトリで行います
    Worker名既定は traypage。workers.devのURLになります
    HYPERDRIVE手順2で作ったHyperdriveの設定
    PAGE_BUCKETページ本文のR2バケット。自動で作られます
    BETTER_AUTH_SECRETopenssl rand -hex 32 の出力。あとで変えると全員がログアウトします
    VIEWER_TICKET_SECRET別に openssl rand -hex 32 を実行した出力。BETTER_AUTH_SECRET と同じ値にしないでください
    Build command / Deploy command既定のまま
  3. デプロイが終わったら、https://<Worker名>.<アカウントのサブドメイン>.workers.dev/health が {"ok":true,...} を返すことを確認します。

  4. DBのマイグレーションは、初回のアクセスでアプリが自動で適用します。/health 以外のURL(例: /api/organizations が401を返す)を1回開いてください。

この時点では、ページ本文がアプリと同じホストから配信され、メールも送れません。次の手順4から6を済ませてから使い始めてください。

4. カスタムドメインを2つ設定する

コピーしたリポジトリの wrangler.jsonc を自分のドメインに書き換えます。

"workers_dev": false,
"routes": [
  { "pattern": "traypage.example.com", "custom_domain": true },
  { "pattern": "view.traypage.example.com", "custom_domain": true }
],
"vars": {
  "ENVIRONMENT": "production",
  "TRAYPAGE_EDITION": "self_hosted",
  "APP_ORIGIN": "https://traypage.example.com",
  "VIEWER_ORIGIN": "https://view.traypage.example.com",
  "VIEWER_HOST": "view.traypage.example.com"
},
  • 2つのホスト名は、同じアカウントで管理しているゾーンのものにします。DNSレコードと証明書は自動で作られます。
  • workers_dev は false にします。workers.devのURLが残っていると、閲覧用ホストの分離を迂回できてしまいます。
  • vars はダッシュボードではなく wrangler.jsonc に書きます。ダッシュボードで変えた値は、次のデプロイで wrangler.jsonc の内容に戻ります(secretは残ります)。

pushするとWorkers Buildsが再デプロイします。https://traypage.example.com/health が200、https://view.traypage.example.com/ が404(閲覧用ホストは共有ページの本文しか返しません)になることを確認してください。

5. placementをDBのリージョンに合わせる

指定しないと、Workerは利用者に近い場所で動き、クエリのたびにDBとの間を往復して遅くなります(検証では、バンクーバーと東京のDBの組み合わせで1操作あたり0.6〜1.6秒かかりました)。wrangler.jsonc で、DBのリージョンを指定します。

"placement": { "mode": "targeted", "region": "aws:ap-northeast-1" },
DBのリージョンregion に書く値
PlanetScale aws-ap-northeast-1(東京)aws:ap-northeast-1
PlanetScale aws-us-east-1aws:us-east-1
PlanetScale gcp-us-central1gcp:us-central1
Neon aws-ap-southeast-1(シンガポール)aws:ap-southeast-1
Neon aws-us-east-2aws:us-east-2
Neon azure-eastus2azure:eastus2

規則は「プロバイダ名:リージョン名」です。再デプロイ後、応答ヘッダの cf-placement が remote-NRT(東京の場合)のようになっていれば、DBの近くで動いています。

curl -sI https://traypage.example.com/api/organizations | grep -i cf-placement

6. メールを設定する(Cloudflare Email Service)

ログインはメールのリンクで行います。メールはCloudflare Email Serviceで送り、APIキーは要りません。

  1. ダッシュボードの Email Service → Domains で、送信元にするドメインを登録します(SPFやDKIMのDNSレコードが追加されます)。

  2. wrangler.jsonc の vars に送信元を追加してpushします。

    "EMAIL_FROM": "TrayPage <noreply@traypage.example.com>"
  3. アプリ用ドメインの /login から、自分のメールアドレスにログインリンクが届くことを確認します。

注意: Email Serviceは公開ベータです。送信元ドメインの登録が終わるまで、ログインリンクは届きません。wrangler.jsonc の send_email に allowed_sender_addresses(送信元アドレスの許可リスト)を足すと、EMAIL_FROM 以外のアドレスからは送れなくなります。

以前のバージョンで案内していた RESEND_API_KEY は、設定されていればResendを優先して使います(Email Serviceに切り替えるには、このsecretを削除します)。

任意の設定

設定方法
Googleログインsecret GOOGLE_CLIENT_ID・GOOGLE_CLIENT_SECRET を追加し、vars に "NEXT_PUBLIC_GOOGLE_AUTH": "1" を追加。Google Cloud ConsoleのリダイレクトURIは https://<アプリ用ドメイン>/api/auth/callback/google
Enterpriseのライセンスsecret TRAYPAGE_LICENSE_KEY(ライセンスとEnterprise機能)
PDF出力BROWSER binding(Browser Run)。テンプレートに含まれています。なくても「印刷・PDFとして保存」は使えます
リアルタイム通知REVIEW_SESSIONS(Durable Objects)。テンプレートに含まれています。なくても定期的な確認で動きます

secretは、ダッシュボードの Workers & Pages → (Worker) → Settings → Variables and Secrets、または npx wrangler secret put <名前> で追加します。すべての設定項目は 設定項目のリファレンス にあります。

更新

コピーしたリポジトリに本家の変更を取り込んでpushすると、Workers Buildsが再デプロイします。DBのマイグレーションは、新しいバージョンの初回アクセスで自動で適用されます。

git remote add upstream <TrayPageのリポジトリのURL>
git fetch upstream && git merge upstream/main   # wrangler.jsonc で自分の設定と競合したら、自分の値を残す
git push

うまく動かないとき

症状確認すること
応答が遅い(1操作で1秒前後)手順5のplacement。cf-placement ヘッダ
Exceeded CPU time limit / 1102エラーWorkers Paidになっているか
/health は200だが、ほかが500Hyperdriveの設定(DBの接続文字列とパスワード)。npx wrangler tail でログを見る
ログインリンクが届かない手順6(送信元ドメインの登録と EMAIL_FROM)と、手順6の注意(古い RESEND_API_KEY が残っていないか)
閲覧用ドメインでアプリの画面が出るVIEWER_HOST と routes(手順4)