Rendered Text
Extract JavaScript-rendered visible text with Cloudflare Browser Rendering - SPA text scrape for search and AI ingestion
Load any URL in a headless browser and extract the visible page text after JavaScript has executed.
API Reference
GET /text
Returns the document title and normalized visible text from document.body.innerText.
Prop
Type
Example Request
curl "https://your-worker.workers.dev/text?url=https://www.cloudflare.com"Success Response
url string
The normalized URL that was loaded
title string
The page <title> after rendering
text string
Visible text with whitespace collapsed
textLength number
Length of the returned text in characters
truncated boolean
Whether the text was truncated at 50,000 characters
{
"url": "https://www.cloudflare.com/",
"title": "Connect, protect, and build everywhere",
"text": "Connect, protect, and build everywhere ...",
"textLength": 1234,
"truncated": false
}Error Response
{
"error": "Missing or invalid query parameter: url",
"code": "INVALID_URL"
}Error Codes
400- Missing or invalidurlparameter502- Navigation or extraction failed (RENDER_ERROR)
Use Cases
- Scrape single-page apps and client-rendered content
- Feed rendered page text to LLM pipelines
- Compare static fetch vs browser-rendered output
- Build search indexes for JavaScript-heavy sites
Limitations
- Requires Browser Rendering enabled on your Cloudflare account
- Local development needs
npx wrangler dev --remote - Extracted text is truncated at a maximum length
- Navigation timeout on slow or script-heavy pages
Use in your project
Copy these files into an existing Worker. Prefer Deployment to try the full experiment first. Source: apps/experiments/rendered-text.
import puppeteer from "@cloudflare/puppeteer";import type { Page } from "@cloudflare/puppeteer";import { DEFAULT_VIEWPORT, NAVIGATION_TIMEOUT_MS } from "../constants/defaults";import { normalizeTitle, truncateText } from "./text";import type { RenderedTextResponse } from "../types/text";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 extractRenderedText( browserBinding: Fetcher, url: string): Promise<RenderedTextResponse> { const raw = await withBrowserPage(browserBinding, url, async (page) => { const title = await page.title(); const text = await page.$eval("body", (body) => body.innerText ?? ""); return { title, text }; }); const title = normalizeTitle(raw.title); const { text, truncated } = truncateText(raw.text); return { url, title, text, textLength: text.length, truncated, };}Deployment
Enable Browser Rendering
Browser Rendering must be enabled on your account.
Test your deployment
curl "https://your-worker.workers.dev/text?url=https://www.cloudflare.com"Local Development
cd apps/experiments/rendered-text
npm install
npm run devTest locally:
curl "http://localhost:8787/text?url=https://www.cloudflare.com"Cloudflare Features Used
- Workers - Edge compute runtime
- Browser Rendering - Headless Chromium via
@cloudflare/puppeteer - Puppeteer DOM evaluation - Extract rendered text after
networkidle0