Curated Skills
by lstudlo

cloudflare

references/browser-rendering/gotchas.md

.md 89 lines
Content
# Browser Rendering Gotchas

## Tier Limits

| Limit | Free | Paid |
|-------|------|------|
| Daily browser time | 10 min | Unlimited* |
| Concurrent sessions | 3 | 30 |
| Requests/minute | 6 | 180 |
| Session keep-alive | 10 min max | 10 min max |

*Subject to fair-use policy.

**Check quota:**
```typescript
const limits = await puppeteer.limits(env.MYBROWSER);
// { remaining: 540000, total: 600000, concurrent: 2 }
```

## Always Close Browsers

```typescript
const browser = await puppeteer.launch(env.MYBROWSER);
try {
  const page = await browser.newPage();
  await page.goto("https://example.com");
  return new Response(await page.content());
} finally {
  await browser.close(); // ALWAYS in finally
}
```

**Workers vs REST:** REST auto-closes after timeout. Workers must call `close()` or session stays open until `keep_alive` expires.

## Optimize Concurrency

```typescript
// ❌ 3 sessions (hits free tier limit)
const browser1 = await puppeteer.launch(env.MYBROWSER);
const browser2 = await puppeteer.launch(env.MYBROWSER);

// ✅ 1 session, multiple pages
const browser = await puppeteer.launch(env.MYBROWSER);
const page1 = await browser.newPage();
const page2 = await browser.newPage();
```

## Common Errors

| Error | Cause | Fix |
|-------|-------|-----|
| Session limit exceeded | Too many concurrent | Close unused browsers, use pages not browsers |
| Page navigation timeout | Slow page or `networkidle` on busy page | Increase timeout, use `waitUntil: "load"` |
| Session not found | Expired session | Catch error, launch new session |
| Evaluation failed | DOM element missing | Use `?.` optional chaining |
| Protocol error: Target closed | Page closed during operation | Await all ops before closing |

## page.evaluate() Gotchas

```typescript
// ❌ Outer scope not available
const selector = "h1";
await page.evaluate(() => document.querySelector(selector));

// ✅ Pass as argument
await page.evaluate((sel) => document.querySelector(sel)?.textContent, selector);
```

## Performance

**waitUntil options (fastest to slowest):**
1. `domcontentloaded` - DOM ready
2. `load` - load event (default)
3. `networkidle0` - no network for 500ms

**Block unnecessary resources:**
```typescript
await page.setRequestInterception(true);
page.on("request", (req) => {
  if (["image", "stylesheet", "font"].includes(req.resourceType())) {
    req.abort();
  } else {
    req.continue();
  }
});
```

**Session reuse:** Cold start ~1-2s, warm connect ~100-200ms. Store sessionId in KV for reuse.