All skills
Skillintermediate

Browser Rendering Gotchas

| 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 |

Claude Code Knowledge Pack7/10/2026

Overview

Browser Rendering Gotchas

Tier Limits

LimitFreePaid
Daily browser time10 minUnlimited*
Concurrent sessions330
Requests/minute6180
Session keep-alive10 min max10 min max

*Subject to fair-use policy.

Check quota:

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

Always Close Browsers

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

// ❌ 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

ErrorCauseFix
Session limit exceededToo many concurrentClose unused browsers, use pages not browsers
Page navigation timeoutSlow page or networkidle on busy pageIncrease timeout, use waitUntil: "load"
Session not foundExpired sessionCatch error, launch new session
Evaluation failedDOM element missingUse ?. optional chaining
Protocol error: Target closedPage closed during operationAwait all ops before closing

page.evaluate() Gotchas

// ❌ 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:

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.