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