Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

プレビュー URL

最終更新 Markdown で表示Agent セットアップ

クイックデプロイ

すばやくプレビューを出すときは、Cloudflare Tunnel で Web サービス向けのプレビュー URL を作る方法をおすすめします。ローカル開発、workers.dev、本番のいずれでも使えます。

await sandbox.startProcess("python -m http.server 8000");
const tunnel = await sandbox.tunnels.get(8000);
console.log(tunnel.url);
// https://acute-llama-dancing-roundly.trycloudflare.com

// Request will be routed directly to the webserver running on the sandbox.
const req = await fetch(`${tunnel.url}/api/users`); // => GET http://localhost:8000/api/users

Cloudflare Tunnel のサポートには、現時点で次の制限があります。

  • 生成される URL は指定できません。
  • ランダム生成の URL 以外に、認証の仕組みはありません。
  • URL ごとに、サンドボックス上で追加の cloudflared プロセスが動きます。

API と機能の全体は tunnels API リファレンス を参照してください。

本番利用、安定 URL、カスタムドメイン

本番では、exposePort() API を使い、トラフィックを Worker 経由でルーティングする方法をおすすめします。

プレビュー URL は、サンドボックス内で動くサービスへ公開 HTTPS でアクセスできます。ポートを公開すると、そのサービスへリクエストをプロキシする一意の URL が得られます。

// Extract hostname from request
const { hostname } = new URL(request.url);

await sandbox.startProcess("python -m http.server 8000");
const exposed = await sandbox.exposePort(8000, { hostname });

console.log(exposed.url);
// Production: https://8000-sandbox-id-abc123random4567.yourdomain.com
// Local dev: http://8000-sandbox-id-abc123random4567.localhost:{port}/

URL の形式

本番: https://{port}-{sandbox-id}-{token}.yourdomain.com

  • 自動生成トークン: https://8080-abc123-random16chars12.yourdomain.com
  • カスタムトークン: https://8080-abc123-my_api_v1.yourdomain.com

ローカル開発: http://{port}-{sandbox-id}-{token}.localhost:{dev-server-port}

トークンの種類

自動生成トークン(デフォルト)

カスタムトークンを指定しない場合、16 文字のランダムなトークンが生成されます。

const exposed = await sandbox.exposePort(8000, { hostname });
// https://8000-sandbox-id-abc123random4567.yourdomain.com

自動生成トークンの URL は、ポートの公開を解除して再公開すると変わります。

安定 URL 向けのカスタムトークン

本番デプロイや共有 URL では、コンテナ再起動後も同じ URL を保つためにカスタムトークンを指定します。

const stable = await sandbox.exposePort(8000, {
	hostname,
	token: "api_v1",
});
// https://8000-sandbox-id-api_v1.yourdomain.com
// Same URL every time ✓

トークンの要件:

  • 長さは 1〜16 文字
  • 使えるのは小文字(a-z)、数字(0-9)、アンダースコア(_)のみ
  • 各サンドボックス内で一意であること

カスタムトークンの用途:

  • エンドポイントが変わらない本番 API
  • 外部ユーザーへのデモ URL 共有
  • 例が一貫したドキュメント
  • URL を予測できる統合テスト

ID の大文字と小文字

プレビュー URL は、ホスト名からサンドボックス ID を取り出してリクエストをルーティングします。ホスト名は大文字小文字を区別しないため(RFC 3986)、常に小文字になります。8080-MyProject-123.yourdomain.com8080-myproject-123.yourdomain.com になります。

問題: "MyProject-123" でサンドボックスを作ると、その ID のまま Durable Object として存在します。一方、プレビュー URL はホスト名を小文字化した "myproject-123" へルーティングします。これらは別の Durable Object なので、プレビュー URL からはサンドボックスに届きません。

// Problem scenario
const sandbox = getSandbox(env.Sandbox, "MyProject-123");
// Durable Object ID: "MyProject-123"
await sandbox.exposePort(8080, { hostname });
// Preview URL: 8080-myproject-123-token123.yourdomain.com
// Routes to: "myproject-123" (different DO - doesn't exist!)

解決策: サンドボックス作成時に normalizeId: true を使い、ID を小文字にします。

const sandbox = getSandbox(env.Sandbox, "MyProject-123", {
	normalizeId: true,
});
// Durable Object ID: "myproject-123" (lowercased)
// Preview URL: 8080-myproject-123-token123.yourdomain.com
// Routes to: "myproject-123" (same DO - works!)

normalizeId: true がないと、ID に大文字が含まれる場合に exposePort() はエラーを投げます。

推奨: 最初から小文字の ID を使います('my-project-123')。詳細は Sandbox オプション - normalizeId を参照してください。

リクエストのルーティング

プレビュー URL のリクエストをルーティングするには、Worker の fetch ハンドラーの先頭で proxyToSandbox() を呼び出します。

import { proxyToSandbox, getSandbox } from "@cloudflare/sandbox";

export { Sandbox } from "@cloudflare/sandbox";

export default {
	async fetch(request, env) {
		// Handle preview URL routing first
		const proxyResponse = await proxyToSandbox(request, env);
		if (proxyResponse) return proxyResponse;

		// Your application routes
		// ...
	},
};

リクエストの流れは、ブラウザ → 自分の Worker → Durable Object(サンドボックス)→ 自分のサービスです。

複数のポート

複数のサービスを同時に公開できます。

// Extract hostname from request
const { hostname } = new URL(request.url);

await sandbox.startProcess("node api.js"); // Port 3000
await sandbox.startProcess("node admin.js"); // Port 3001

const api = await sandbox.exposePort(3000, { hostname, name: "api" });
const admin = await sandbox.exposePort(3001, { hostname, name: "admin" });

// Each gets its own URL with unique tokens:
// https://3000-abc123-random16chars01.yourdomain.com
// https://3001-abc123-random16chars02.yourdomain.com

使えるもの

  • HTTP/HTTPS リクエスト
  • WebSocket 接続
  • Server-Sent Events
  • すべての HTTP メソッド(GET、POST、PUT、DELETE など)
  • リクエストヘッダーとレスポンスヘッダー

使えないもの

  • 生の TCP/UDP 接続
  • 独自プロトコル(HTTP で包む必要があります)
  • 1024〜65535 の範囲外のポート
  • ポート 3000(SDK が内部で使用)

WebSocket の対応

プレビュー URL は WebSocket 接続に対応します。公開済みポートへ WebSocket のアップグレードリクエストが届くと、ルーティング層が接続ハンドシェイクを自動で処理します。

// Extract hostname from request
const { hostname } = new URL(request.url);

// Start a WebSocket server
await sandbox.startProcess("bun run ws-server.ts 8080");
const { url } = await sandbox.exposePort(8080, { hostname });

// Clients connect using WebSocket protocol
// Browser: new WebSocket('wss://8080-abc123-token123.yourdomain.com')

// Your Worker routes automatically
export default {
	async fetch(request, env) {
		const proxyResponse = await proxyToSandbox(request, env);
		if (proxyResponse) return proxyResponse;
	},
};

リクエストの属性に応じて、接続先のサンドボックスやポートを Worker 側で決めたい場合は、Ports APIwsConnect() を参照してください。

セキュリティ

組み込みのセキュリティ:

  • トークンベースのアクセス - 公開した各ポートに、URL 内の一意のトークンが付きます(例: https://8080-sandbox-abc123token456.yourdomain.com
  • 本番の HTTPS - すべてのトラフィックは TLS で暗号化されます。証明書は第 1 レベルのワイルドカード(*.yourdomain.com)向けに自動発行されます。Worker がサブドメイン上で動く場合は、カスタムドメインの TLS に関する注意 を参照してください。
  • 推測しにくい URL - 自動生成トークンはランダムで、推測しにくくなっています
  • トークンの衝突防止 - カスタムトークンは、各サンドボックス内で一意になるよう検証されます

アプリケーション層の認証を追加する:

さらにセキュリティを高めるには、アプリケーション内で認証を実装します。

from flask import Flask, request, abort

app = Flask(__name__)

@app.route('/data')
def get_data():
    # Check for your own authentication token
    auth_token = request.headers.get('Authorization')
    if auth_token != 'Bearer your-secret-token':
        abort(401)
    return {'data': 'protected'}

これで、URL トークンの上に 2 層目のセキュリティが加わります。

トラブルシューティング

URL にアクセスできない

サービスが起動し、待ち受けしているかを確認します。

// 1. Is service running?
const processes = await sandbox.listProcesses();

// 2. Is port exposed?
const ports = await sandbox.getExposedPorts();

// 3. Is service binding to 0.0.0.0 (not 127.0.0.1)?
// Good:
app.run((host = "0.0.0.0"), (port = 3000));

// Bad (localhost only):
app.run((host = "127.0.0.1"), (port = 3000));

本番環境のエラー

カスタムドメインの問題は、プレビュー URL のカスタムドメインのトラブルシューティング を参照してください。

ローカル開発

関連リソース

役に立ちましたか?