Browser Rendering
Page Metrics
Collect Puppeteer page-load metrics with Cloudflare Browser Rendering - Core Web Vitals-style timing from the edge
Navigate to any URL in a headless browser and return Puppeteer page load metrics as JSON.
API Reference
GET /metrics
Loads the page and returns metrics from page.metrics().
Prop
Type
Example Request
curl "https://your-worker.workers.dev/metrics?url=https://www.cloudflare.com"Success Response
url string
The normalized URL that was measured
metrics object
Puppeteer metrics such as Nodes, LayoutCount, ScriptDuration, and JSHeapUsedSize
{
"url": "https://www.cloudflare.com/",
"metrics": {
"Timestamp": 12345.678,
"Documents": 1,
"Nodes": 842,
"LayoutCount": 12,
"ScriptDuration": 0.234,
"JSHeapUsedSize": 1048576
}
}Error Response
{
"error": "Missing or invalid query parameter: url",
"code": "INVALID_URL"
}Error Codes
400- Missing or invalidurlparameter502- Navigation or metrics collection failed (METRICS_ERROR)
Use Cases
- Measure DOM complexity and script cost after full page load
- Compare client-side rendering overhead across sites
- Debug performance issues that only appear in a real browser
- Learn Puppeteer metrics collection on Workers
Limitations
- Requires Browser Rendering enabled on your Cloudflare account
- Local development needs
npx wrangler dev --remote - Metrics reflect one page load at a fixed 1280×800 viewport
- Navigation timeout may fail on heavy single-page applications
Use in your project
Copy these files into an existing Worker. Prefer Deployment to try the full experiment first. Source: apps/experiments/page-metrics.
DependenciesNoneBindingsBrowser RenderingPlatformWorkers runtime
import puppeteer from "@cloudflare/puppeteer";import type { Page } from "@cloudflare/puppeteer";import { DEFAULT_VIEWPORT, NAVIGATION_TIMEOUT_MS } from "../constants/defaults";import type { PageMetricsResponse } from "../types/metrics";export async function withBrowserPage<T>( browserBinding: Fetcher, url: string, fn: (page: Page) => Promise<T>): Promise<T> { let browser: Awaited<ReturnType<typeof puppeteer.launch>> | null = null; try { browser = await puppeteer.launch(browserBinding); const page = await browser.newPage(); await page.setViewport(DEFAULT_VIEWPORT); await page.goto(url, { waitUntil: "networkidle0", timeout: NAVIGATION_TIMEOUT_MS, }); return await fn(page); } finally { if (browser) { try { await browser.close(); } catch { /* ignore close errors */ } } }}export function normalizeMetrics(raw: Record<string, number>): Record<string, number> { const normalized: Record<string, number> = {}; for (const [key, value] of Object.entries(raw)) { if (typeof value === "number" && Number.isFinite(value)) { normalized[key] = Math.round(value * 1000) / 1000; } } return normalized;}export async function collectPageMetrics( browserBinding: Fetcher, url: string): Promise<PageMetricsResponse> { const metrics = await withBrowserPage(browserBinding, url, async (page) => page.metrics()); return { url, metrics: normalizeMetrics(metrics as Record<string, number>), };}Deployment
Enable Browser Rendering
Browser Rendering must be enabled on your account.
Test your deployment
curl "https://your-worker.workers.dev/metrics?url=https://www.cloudflare.com"Local Development
cd apps/experiments/page-metrics
npm install
npm run devTest locally:
curl "http://localhost:8787/metrics?url=https://www.cloudflare.com"Cloudflare Features Used
- Workers - Edge compute runtime
- Browser Rendering - Headless Chromium via
@cloudflare/puppeteer - Puppeteer
page.metrics()- Page load and memory metrics