← Browser Run / browser-run / how-to
Разверните Worker для Browser Run с Durable Objects
Следуя этому руководству, вы создадите Worker, который использует API Browser Run вместе с Durable Objects чтобы делать скриншоты веб-страниц и сохранять их в R2.
Сохранение сеансов браузера с помощью Durable Objects повышает производительность, так как исключает время, необходимое на запуск нового сеанса браузера. Поскольку Durable Objects переиспользует сеансы, это снижает количество одновременно необходимых сеансов.
- Зарегистрируйтесь для получения Аккаунт Cloudflare ↗.
- Установка
Node.js↗.
менеджер версий Node.js
Используйте менеджер версий Node, например Volta ↗ или nvm ↗ чтобы избежать проблем с правами доступа и переключать версии Node.js. Wrangler, о котором пойдёт речь далее в этом руководстве, требует версию Node 16.17.0 или более поздней версии.
1. Создайте проект Worker
Cloudflare Workers предоставляет бессерверную среду выполнения, которая позволяет создавать новые приложения или дополнять существующие без настройки и обслуживания инфраструктуры. Ваше Worker-приложение служит контейнером для взаимодействия с headless-браузером и выполнения таких действий, как создание скриншотов.
Создайте новый проект Worker с именем browser-worker выполнив:
npm create cloudflare@latest -- browser-worker2. Установка Puppeteer
В вашем browser-worker директории установите пакет Cloudflare форк Puppeteer:
npm i -D @cloudflare/puppeteer3. Создание бакета R2
Создайте два бакета R2: один для продакшена и один для разработки.
Обратите внимание, что имена бакетов должны состоять из строчных букв и могут содержать только дефисы.
wrangler r2 bucket create screenshots
wrangler r2 bucket create screenshots-testЧтобы убедиться, что бакеты созданы, выполните:
wrangler r2 bucket listПосле выполнения list команду, вы увидите все имена бакетов, включая только что созданные.
4. Настройка конфигурационного файла Wrangler
Настройте свой browser-worker проекта конфигурационный файл Wrangler добавив привязку browser привязка и Флаг совместимости с Node.js. Привязки браузера обеспечивают взаимодействие между Worker и headless-браузером, что позволяет выполнять такие действия, как создание снимка экрана, генерация PDF и другое.
Обновите файл конфигурации Wrangler, добавив привязку API Browser Run, созданный вами бакет R2 и Durable Object:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "rendering-api-demo",
"main": "src/index.js",
// Set this to today's date
"compatibility_date": "2026-08-28",
"compatibility_flags": ["nodejs_compat"],
"account_id": "<ACCOUNT_ID>",
// Browser Run API binding
"browser": {
"binding": "MYBROWSER",
},
// Bind an R2 Bucket
"r2_buckets": [
{
"binding": "BUCKET",
"bucket_name": "screenshots",
"preview_bucket_name": "screenshots-test",
},
],
// Binding to a Durable Object
"durable_objects": {
"bindings": [
{
"name": "BROWSER",
"class_name": "Browser",
},
],
},
"migrations": [
{
"tag": "v1", // Should be unique for each entry
"new_sqlite_classes": [
// Array of new classes
"Browser",
],
},
],
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "rendering-api-demo"
main = "src/index.js"
# Set this to today's date
compatibility_date = "2026-08-28"
compatibility_flags = [ "nodejs_compat" ]
account_id = "<ACCOUNT_ID>"
[browser]
binding = "MYBROWSER"
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "screenshots"
preview_bucket_name = "screenshots-test"
[[durable_objects.bindings]]
name = "BROWSER"
class_name = "Browser"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "Browser" ]5. Код
Приведённый ниже код использует Durable Object для создания экземпляра браузера с помощью Puppeteer. Затем он открывает несколько веб-страниц с разными разрешениями, делает скриншот каждой и загружает его в R2.
Durable Object держит сессию браузера открытой в течение 60 секунд после последнего использования. Пока сессия браузера открыта, любые запросы будут использовать её повторно, а не создавать новую. Обновите код своего Worker, скопировав и вставив следующее:
import { DurableObject } from "cloudflare:workers";
import * as puppeteer from "@cloudflare/puppeteer";
export default {
async fetch(request, env) {
const obj = env.BROWSER.getByName("browser");
// Send a request to the Durable Object, then await its response
const resp = await obj.fetch(request);
return resp;
},
};
const KEEP_BROWSER_ALIVE_IN_SECONDS = 60;
export class Browser extends DurableObject {
browser;
keptAliveInSeconds = 0;
storage;
constructor(state, env) {
super(state, env);
this.storage = state.storage;
}
async fetch(request) {
// Screen resolutions to test out
const width = [1920, 1366, 1536, 360, 414];
const height = [1080, 768, 864, 640, 896];
// Use the current date and time to create a folder structure for R2
const nowDate = new Date();
const coeff = 1000 * 60 * 5;
const roundedDate = new Date(
Math.round(nowDate.getTime() / coeff) * coeff,
).toString();
const folder = roundedDate.split(" GMT")[0];
// If there is a browser session open, re-use it
if (!this.browser || !this.browser.isConnected()) {
console.log(`Browser DO: Starting new instance`);
try {
this.browser = await puppeteer.launch(this.env.MYBROWSER);
} catch (e) {
console.log(
`Browser DO: Could not start browser instance. Error: ${e}`,
);
}
}
// Reset keptAlive after each call to the DO
this.keptAliveInSeconds = 0;
// Check if browser exists before opening page
if (!this.browser)
return new Response("Browser launch failed", { status: 500 });
const page = await this.browser.newPage();
// Take screenshots of each screen size
for (let i = 0; i < width.length; i++) {
await page.setViewport({ width: width[i], height: height[i] });
await page.goto("https://workers.cloudflare.com/");
const fileName = `screenshot_${width[i]}x${height[i]}`;
const sc = await page.screenshot();
await this.env.BUCKET.put(`${folder}/${fileName}.jpg`, sc);
}
// Close tab when there is no more work to be done on the page
await page.close();
// Reset keptAlive after performing tasks to the DO
this.keptAliveInSeconds = 0;
// Set the first alarm to keep DO alive
const currentAlarm = await this.storage.getAlarm();
if (currentAlarm == null) {
console.log(`Browser DO: setting alarm`);
const TEN_SECONDS = 10 * 1000;
await this.storage.setAlarm(Date.now() + TEN_SECONDS);
}
return new Response("success");
}
async alarm() {
this.keptAliveInSeconds += 10;
// Extend browser DO life
if (this.keptAliveInSeconds < KEEP_BROWSER_ALIVE_IN_SECONDS) {
console.log(
`Browser DO: has been kept alive for ${this.keptAliveInSeconds} seconds. Extending lifespan.`,
);
await this.storage.setAlarm(Date.now() + 10 * 1000);
// You can ensure the ws connection is kept alive by requesting something
// or just let it close automatically when there is no work to be done
// for example, `await this.browser.version()`
} else {
console.log(
`Browser DO: exceeded life of ${KEEP_BROWSER_ALIVE_IN_SECONDS}s.`,
);
if (this.browser) {
console.log(`Closing browser.`);
await this.browser.close();
}
}
}
}import { DurableObject } from "cloudflare:workers";
import * as puppeteer from "@cloudflare/puppeteer";
interface Env {
MYBROWSER: Fetcher;
BUCKET: R2Bucket;
BROWSER: DurableObjectNamespace;
}
export default {
async fetch(request, env): Promise<Response> {
const obj = env.BROWSER.getByName("browser");
// Send a request to the Durable Object, then await its response
const resp = await obj.fetch(request);
return resp;
},
} satisfies ExportedHandler<Env>;
const KEEP_BROWSER_ALIVE_IN_SECONDS = 60;
export class Browser extends DurableObject<Env> {
private browser?: puppeteer.Browser;
private keptAliveInSeconds: number = 0;
private storage: DurableObjectStorage;
constructor(state: DurableObjectState, env: Env) {
super(state, env);
this.storage = state.storage;
}
async fetch(request: Request): Promise<Response> {
// Screen resolutions to test out
const width: number[] = [1920, 1366, 1536, 360, 414];
const height: number[] = [1080, 768, 864, 640, 896];
// Use the current date and time to create a folder structure for R2
const nowDate = new Date();
const coeff = 1000 * 60 * 5;
const roundedDate = new Date(
Math.round(nowDate.getTime() / coeff) * coeff,
).toString();
const folder = roundedDate.split(" GMT")[0];
// If there is a browser session open, re-use it
if (!this.browser || !this.browser.isConnected()) {
console.log(`Browser DO: Starting new instance`);
try {
this.browser = await puppeteer.launch(this.env.MYBROWSER);
} catch (e) {
console.log(
`Browser DO: Could not start browser instance. Error: ${e}`,
);
}
}
// Reset keptAlive after each call to the DO
this.keptAliveInSeconds = 0;
// Check if browser exists before opening page
if (!this.browser)
return new Response("Browser launch failed", { status: 500 });
const page = await this.browser.newPage();
// Take screenshots of each screen size
for (let i = 0; i < width.length; i++) {
await page.setViewport({ width: width[i], height: height[i] });
await page.goto("https://workers.cloudflare.com/");
const fileName = `screenshot_${width[i]}x${height[i]}`;
const sc = await page.screenshot();
await this.env.BUCKET.put(`${folder}/${fileName}.jpg`, sc);
}
// Close tab when there is no more work to be done on the page
await page.close();
// Reset keptAlive after performing tasks to the DO
this.keptAliveInSeconds = 0;
// Set the first alarm to keep DO alive
const currentAlarm = await this.storage.getAlarm();
if (currentAlarm == null) {
console.log(`Browser DO: setting alarm`);
const TEN_SECONDS = 10 * 1000;
await this.storage.setAlarm(Date.now() + TEN_SECONDS);
}
return new Response("success");
}
async alarm(): Promise<void> {
this.keptAliveInSeconds += 10;
// Extend browser DO life
if (this.keptAliveInSeconds < KEEP_BROWSER_ALIVE_IN_SECONDS) {
console.log(
`Browser DO: has been kept alive for ${this.keptAliveInSeconds} seconds. Extending lifespan.`,
);
await this.storage.setAlarm(Date.now() + 10 * 1000);
// You can ensure the ws connection is kept alive by requesting something
// or just let it close automatically when there is no work to be done
// for example, `await this.browser.version()`
} else {
console.log(
`Browser DO: exceeded life of ${KEEP_BROWSER_ALIVE_IN_SECONDS}s.`,
);
if (this.browser) {
console.log(`Closing browser.`);
await this.browser.close();
}
}
}
}6. Тестирование
Запустите npx wrangler dev чтобы протестировать Worker локально.
7. Разверните
Запустите npx wrangler deploy чтобы развернуть Worker в глобальной сети Cloudflare.
Дополнительные материалы
- Другие Примеры Puppeteer ↗
- Начало работы с Durable Objects
- Использование R2 из Workers