INTEGRITY Dokumentace

Předběžné vykreslování stránek pro prohledávače

Předběžné vykreslování vygeneruje finální HTML stránky ještě předtím, než se vrátí klientovi. U aplikací náročných na JavaScript to znamená načtení stránky v prohlížeči, počkání na spuštění JavaScriptu na straně klienta a vrácení vykresleného HTML místo počátečního app shellu.

Předběžné vykreslování je užitečné, když prohledávače, boti pro náhledy na sociálních sítích, indexovací úlohy AI nebo partnerské integrace potřebují obsah HTML, který vaše aplikace běžně vytváří v prohlížeči. S Cloudflare Browser Run a Cloudflare Workers, můžete vykreslit veřejnou adresu URL ve spravovaném headless Chrome a vrátit vykreslené HTML.

V tomto tutoriálu:

Předpoklady

Abyste mohli tento návod dokončit, potřebujete:

Stránka, kterou předrenderováváte, může běžet kdekoli. Worker v tomto návodu funguje pouze jako služba pro předrenderování, která volá Browser Run.

1. Konfigurace Browser Run

Přidejte vazbu Browser Run do konfigurace Wrangler:

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "name": "my-prerender-worker",
  "main": "src/index.ts",
  // Set this to today's date
  "compatibility_date": "2026-08-28",
  "browser": {
    "binding": "BROWSER"
  }
}
name = "my-prerender-worker"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-08-28"

[browser]
binding = "BROWSER"

2. Přidání Workeru pro předvykreslování

Nahraďte obsah src/index.ts s následujícím Workerem. Aktualizujte ALLOWED_HOSTNAMES k zahrnutí názvů hostitelů, které váš Worker může předvykreslovat.

// Only render pages you control. This prevents the Worker from becoming
// an open browser-rendering proxy for arbitrary websites.
const ALLOWED_HOSTNAMES = new Set(["example.com", "www.example.com"]);

const getTargetUrl = (request) => {
	const requestUrl = new URL(request.url);
	const target = requestUrl.searchParams.get("url");

	if (!target) {
		throw new Error("Missing url query parameter");
	}

	const targetUrl = new URL(target);

	// Only render HTTP(S) pages. Other protocols are not valid web pages.
	if (!["http:", "https:"].includes(targetUrl.protocol)) {
		throw new Error("Only HTTP and HTTPS URLs are allowed");
	}

	if (!ALLOWED_HOSTNAMES.has(targetUrl.hostname)) {
		throw new Error("This hostname is not allowed");
	}

	return targetUrl;
};

const renderHtml = async (env, targetUrl) => {
	// The /content Quick Actions endpoint loads the page in Browser Run and returns
	// a JSON envelope containing the rendered HTML in the result field.
	const response = await env.BROWSER.quickAction("content", {
		url: targetUrl.toString(),
		gotoOptions: {
			waitUntil: "networkidle2",
			timeout: 30000,
		},
		// If your page has a specific readiness signal, use waitForSelector
		// instead of relying only on network activity.
		// waitForSelector: { selector: "[data-prerender-ready='true']", timeout: 30000 },
	});

	if (!response.ok) {
		const detail = (await response.text()).slice(0, 500);
		throw new Error(`Browser Run failed with ${response.status}: ${detail}`);
	}

	const data = await response.json();

	if (!data.success || typeof data.result !== "string") {
		throw new Error("Browser Run returned an unsuccessful response");
	}

	return data.result;
};

export default {
	async fetch(request, env) {
		try {
			// Read and validate the URL before sending it to Browser Run.
			const targetUrl = getTargetUrl(request);
			const html = await renderHtml(env, targetUrl);

			// Return the rendered HTML to the crawler or integration.
			return new Response(html, {
				headers: {
					"content-type": "text/html; charset=utf-8",
				},
			});
		} catch (error) {
			return Response.json(
				{ error: error instanceof Error ? error.message : "Unknown error" },
				{ status: 400 },
			);
		}
	},
};
interface Env {
	BROWSER: BrowserRun;
}

// Only render pages you control. This prevents the Worker from becoming
// an open browser-rendering proxy for arbitrary websites.
const ALLOWED_HOSTNAMES = new Set(["example.com", "www.example.com"]);

const getTargetUrl = (request: Request) => {
	const requestUrl = new URL(request.url);
	const target = requestUrl.searchParams.get("url");

	if (!target) {
		throw new Error("Missing url query parameter");
	}

	const targetUrl = new URL(target);

	// Only render HTTP(S) pages. Other protocols are not valid web pages.
	if (!["http:", "https:"].includes(targetUrl.protocol)) {
		throw new Error("Only HTTP and HTTPS URLs are allowed");
	}

	if (!ALLOWED_HOSTNAMES.has(targetUrl.hostname)) {
		throw new Error("This hostname is not allowed");
	}

	return targetUrl;
};

const renderHtml = async (env: Env, targetUrl: URL) => {
	// The /content Quick Actions endpoint loads the page in Browser Run and returns
	// a JSON envelope containing the rendered HTML in the result field.
	const response = await env.BROWSER.quickAction("content", {
		url: targetUrl.toString(),
		gotoOptions: {
			waitUntil: "networkidle2",
			timeout: 30000,
		},
		// If your page has a specific readiness signal, use waitForSelector
		// instead of relying only on network activity.
		// waitForSelector: { selector: "[data-prerender-ready='true']", timeout: 30000 },
	});

	if (!response.ok) {
		const detail = (await response.text()).slice(0, 500);
		throw new Error(`Browser Run failed with ${response.status}: ${detail}`);
	}

	const data = (await response.json()) as {
		success: boolean;
		result?: string;
	};

	if (!data.success || typeof data.result !== "string") {
		throw new Error("Browser Run returned an unsuccessful response");
	}

	return data.result;
};

export default {
	async fetch(request, env): Promise<Response> {
		try {
			// Read and validate the URL before sending it to Browser Run.
			const targetUrl = getTargetUrl(request);
			const html = await renderHtml(env, targetUrl);

			// Return the rendered HTML to the crawler or integration.
			return new Response(html, {
				headers: {
					"content-type": "text/html; charset=utf-8",
				},
			});
		} catch (error) {
			return Response.json(
				{ error: error instanceof Error ? error.message : "Unknown error" },
				{ status: 400 },
			);
		}
	},
} satisfies ExportedHandler<Env>;

Worker přijímá url parametr dotazu, ověří název hostitele, požádá Browser Run o vykreslení dané URL adresy a vrátí vykreslené HTML.

waitUntil: "networkidle2" čeká, dokud stránka nemá po dobu alespoň 500 ms více než dvě síťová připojení. Stránkám vykreslovaným na straně klienta to často stačí. Pokud vaše stránka potřebuje přesnější signál připravenosti, předejte waitForSelector do stejného payloadu Quick Actions a počkejte na prvek, který se zobrazí až po načtení obsahu. Další informace najdete v Časové limity Quick Actions v Browser Run.

3. Test předvykreslování

  1. Spusťte Worker ve vzdáleném režimu:

    npx wrangler dev --remote

    .quickAction() metoda zatím není v místním vývojovém režimu podporována. Použijte wrangler dev --remote při lokálním testování Browser Run Quick Actions.

  2. V jiném terminálu si vyžádejte vykreslenou stránku:

    curl "http://localhost:8787/?url=https://example.com/"

    Odpověď by měla obsahovat vykreslené HTML cílové stránky.

4. Nasaďte

  1. Po místním ověření nasaďte Worker:

    npx wrangler deploy
  2. Po nasazení si vyžádejte vykreslenou stránku z URL adresy vašeho Workeru:

    curl "https://<YOUR_WORKER_HOSTNAME>/?url=https://example.com/"

Úvahy o produkčním nasazení