INTEGRITY Документация

Предварительный рендеринг страниц для краулеров

Предварительный рендеринг создаёт итоговый HTML страницы до того, как она отправляется клиенту. Для приложений с большим количеством JavaScript это означает загрузку страницы в браузере, ожидание выполнения клиентского JavaScript и возврат отрендеренного HTML вместо исходного каркаса приложения.

Предварительный рендеринг полезен, когда поисковым краулерам, ботам социальных превью, заданиям ИИ-индексации или партнёрским интеграциям нужен HTML-контент, который ваше приложение обычно создаёт в браузере. С помощью Cloudflare Browser Run и Cloudflare Workers, вы можете отрендерить публичный URL-адрес в управляемом headless-браузере Chrome и вернуть отрендеренный HTML.

В этом руководстве вы:

Предварительные требования

Чтобы следовать этому руководству, вам понадобится:

Страница, которую вы предварительно рендерите, может размещаться где угодно. Worker в этом руководстве выступает лишь сервисом предварительного рендеринга, который вызывает Browser Run.

1. Настройка Browser Run

Добавьте привязку Browser Run в конфигурацию 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. Добавление Worker для предварительного рендеринга

Замените содержимое src/index.ts со следующим Worker. Обновите ALLOWED_HOSTNAMES чтобы указать имена хостов, для которых ваш Worker может выполнять предварительный рендеринг.

// 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 принимает url параметр запроса, проверяет имя хоста, просит Browser Run отрисовать этот URL и возвращает отрисованный HTML.

waitUntil: "networkidle2" параметр ожидает, пока на странице не останется не более двух сетевых подключений в течение как минимум 500 мс. Обычно этого достаточно для страниц, отрисовываемых на стороне клиента. Если вашей странице требуется более точный сигнал готовности, передайте waitForSelector в тот же payload Quick Actions, чтобы дождаться появления элемента, который появляется только после загрузки контента. Дополнительную информацию см. в Тайм-ауты Quick Actions в Browser Run.

3. Тестирование предварительного рендеринга

  1. Запустите Worker в удалённом режиме:

    npx wrangler dev --remote

    .quickAction() метод пока не поддерживается в режиме локальной разработки. Используйте wrangler dev --remote при локальном тестировании Quick Actions Browser Run.

  2. В другом терминале запросите отрендеренную страницу:

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

    Ответ должен содержать отрендеренный HTML целевой страницы.

4. Разверните

  1. Разверните Worker после локальной проверки:

    npx wrangler deploy
  2. После развёртывания запросите отрендеренную страницу по URL-адресу вашего Worker:

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

Особенности работы в продакшене