This site is not affiliated with or endorsed by Cloudflare, Inc. It showcases deployable experiments across most Cloudflare products.
Browser Rendering

Browser Links

Extract all links from JavaScript-rendered pages using Cloudflare Browser Rendering - SPA-aware link discovery for crawlers

Load a page in a headless browser and return all unique anchor links after JavaScript rendering.

API Reference

Prop

Type

Example Request

curl "https://your-worker.workers.dev/links?url=https://www.cloudflare.com"

Success Response

url string

The normalized URL that was loaded

linkCount number

Number of unique links returned

truncated boolean

Whether results were capped at 500 links

links array

Each item has href and text

{
  "url": "https://www.cloudflare.com/",
  "linkCount": 42,
  "truncated": false,
  "links": [{ "href": "https://www.cloudflare.com/plans/", "text": "Plans" }]
}

Error Codes

  • 400 - Missing or invalid url (INVALID_URL)
  • 502 - Navigation or extraction failed (LINKS_ERROR)

Use Cases

  • Crawl SPAs and client-rendered navigation
  • Build sitemaps from fully rendered pages
  • Compare static HTML links vs browser-rendered links

Limitations

  • Requires Browser Rendering enabled on your Cloudflare account
  • Local development needs npx wrangler dev --remote to reach a real browser
  • Fixed 1280×800 viewport and navigation timeout
  • Single URL per request; no multi-page crawling

Use in your project

Copy these files into an existing Worker. Prefer Deployment to try the full experiment first. Source: apps/experiments/browser-links.

DependenciesNoneBindingsBrowser RenderingPlatformWorkers runtime
import puppeteer from "@cloudflare/puppeteer";import type { Page } from "@cloudflare/puppeteer";import { DEFAULT_VIEWPORT, NAVIGATION_TIMEOUT_MS } from "../constants/defaults";import { normalizeLinks } from "./links";import type { LinksResponse, PageLink } from "../types/links";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 async function extractPageLinks(  browserBinding: Fetcher,  url: string): Promise<LinksResponse> {  const rawLinks = await withBrowserPage(browserBinding, url, async (page) => {    return page.$$eval("a[href]", (anchors) =>      anchors.map((anchor) => ({        href: anchor.href,        text: anchor.textContent ?? "",      }))    );  });  const { links, truncated } = normalizeLinks(rawLinks as PageLink[]);  return {    url,    linkCount: links.length,    truncated,    links,  };}

Deployment

Click the deploy button

Deploy to Cloudflare Workers

Enable Browser Rendering

Browser Rendering must be enabled on your account.

Local Development

cd apps/experiments/browser-links
npm install
npm run dev

npm run dev uses wrangler dev --remote so a real browser instance is available.

Cloudflare Features Used

On this page