This site is not affiliated with or endorsed by Cloudflare, Inc. It showcases deployable experiments across most Cloudflare products.
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 invalid url parameter
  • 502 - 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

Click the deploy button

Deploy to Cloudflare Workers

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 dev

Test locally:

curl "http://localhost:8787/metrics?url=https://www.cloudflare.com"

Cloudflare Features Used

On this page