Speeding up Pluck's full-page Chrome extension capture loop

Notes from optimizing Pluck's real Chrome extension full-page capture path from slow scroll-settle loops to sub-four-second captures on recent sites.

June 15, 2026 · 7 min read · Tooling

BW / SP

Pluck captures web pages into a local design library: screenshot segments, DOM, CSS, computed styles, sections, inspectable nodes, and enough metadata to make the capture useful later. That fidelity is the point. A fast screenshot alone is not enough if the viewer loses the structure of the page.

The capture path had become too slow. The original baseline for a full-page capture of Devin was 36,970ms in the extension, while the local server work was only 260ms. That made the extension loop the real problem.

I used a /goal process to keep the optimization honest: one change at a time, real unpacked Chrome extension runs through the toolbar popup, fresh manifests after every experiment, and no credit for server-only timing or API-only tests.

First target: the Devin baseline

The first pass found the obvious fixed-wait problem. The old scroll-stitch path paid repeated settle delays for preload, scroll, freeze, and post-freeze work. Reducing the safe fallback settle delay helped, but the real win was moving successful full-page captures to Chrome's debugger screenshot path with Page.captureScreenshot and captureBeyondViewport.

RunCapture idChangeExtension totalSpeedupSegmentsSectionsNodesTotal bytesVerdict
Baselinecap_d94670a9-c285-4f7d-8f09-12d04462601eOriginal fixed 850ms settle tail36,970ms1.00x13241516,989,423Known-good reference
Experiment 1cap_14b79991-9b74-4a8a-8dd8-b507d2c27143Full-page settle tail reduced to 200ms; same scroll-stitch structure10,930ms3.38x13241516,908,103Kept as fallback improvement
Experiment 4cap_63a906cd-d96f-423b-ae8a-b8b255d72675Debugger segmented capture first; scroll-stitch fallback; 50ms debugger settle3,236ms11.42x13241506,815,180Fast, needed fidelity follow-up
Experiment 5cap_2589ffe3-5e3a-4d06-a3ab-293327a8aae8Debugger capture with bottom/top paint primer3,167ms11.67x13241466,465,569Footer coverage verified, scroll restore still needed
Experiment 6cap_c7029dbc-75b2-4d50-87e5-1ce4cff5e109Debugger-first capture with paint primer, original-scroll restoration, 50ms debugger settle, and 200ms fallback settle3,154ms11.72x13241466,436,672Kept

The important part was not just getting a 10x number. The accepted run still had screenshot segments, DOM snapshot, style snapshot, CSS snapshot, sections, inspectable nodes, and a complete lower-page capture. The debugger path also kept the scroll-stitch implementation as a fallback.

Fresh batch: 10 newer sites

After that first win, I ran a fresh batch of recent SaaS and developer-tool sites. The goal changed from "prove 10x on one capture" to "make the slowest real captures fast enough to use every day."

The newest 10 captures made the next bottleneck clear:

RankCapture idURLExtension totalSegmentsSectionsNodesTotal bytesHealth
1cap_b89b2a5a-e8ff-402f-9a72-8f87e83147e4https://railway.com/19,761ms101111513,451,789none
2cap_496d966c-c72d-40d9-aa77-0555cfdecf59https://neon.com/19,750ms101311216,344,048none
3cap_9c0a6abc-489b-4644-b19d-8db77356a7efhttps://firebase.google.com/18,679ms10211626,299,068none
4cap_82e6c1d9-b8b9-45fb-8acb-f1d6b2f7a89ehttps://render.com/17,469ms1031585,892,569none
5cap_c0104a8a-e9f3-4562-aaee-4dbe2d6829aahttps://workos.com/17,414ms10181377,736,947none
6cap_e03430d0-5af6-43a3-a20e-0cb89582d475https://www.svix.com/17,325ms1022407,287,661none
7cap_ab873492-ab53-4dc2-a9b0-cd1e80b10492https://www.inngest.com/8,842ms101614328,911,244none
8cap_c651f91d-4eab-439d-a846-4313332f46achttps://fly.io/4,287ms101410011,411,887none
9cap_106ece03-cc09-416d-aa5b-8a704d0063behttps://trigger.dev/3,688ms10202328,056,285none
10cap_22f337d9-68b9-4e63-8487-e65b495a605chttps://planetscale.com/1,688ms94952,515,031none

The three slowest captures all spent about 12.7s in debuggerSegmentSettle. That was repeated content-script settling across 10 debugger segments, including visible-image waiting, even though full DOM, CSS, style, and font capture still happened after screenshots.

So the second pass narrowed the segment loop:

  • Per-segment debugger settle uses a short frame delay and skips font/image waits.
  • Debugger screenshots capture 4 viewports per segment instead of one viewport per segment.
  • The bottom paint primer still waits for visible images, but only for 150ms.
  • The top reset uses frame-only settle.
  • Scroll-stitch remains as the fallback path with the safer 200ms settle.

Getting the three slowest under four seconds

I tested the changes through the real extension, not by importing capture functions directly. Railway was the tuning target because it was the slowest of the fresh batch.

RunCapture idURLChangeExtension totalSpeedup vs batch captureSegmentsSectionsNodesTotal bytesVerdict
Railway E1cap_f23635bc-0ad4-42c6-8f4c-fe15ce425c65https://railway.com/Debugger segment settle skips font/image waits, still one viewport per segment; manual run was uncapped full page11,119msn/a171111517,759,139Informational only; proved segment settle dropped from 12,698ms to 1,163ms
Railway E2cap_3c553e1b-9e1b-40da-a5da-9092c33f4ffehttps://railway.com/2-viewport debugger chunks; capped to 10 viewports4,768ms4.14x51111813,450,484Improved but still above target
Railway E3cap_0cb639d0-888d-4db0-a6e5-d20e574bf7c5https://railway.com/3-viewport debugger chunks; capped to 10 viewports4,373ms4.52x41111714,156,539Improved; viewer top and inspectable metadata intact
Railway E4cap_ea6af70d-55d2-4ee8-80f8-bacdcd636d1ehttps://railway.com/4-viewport debugger chunks; capped to 10 viewports4,091ms4.83x31111713,808,178Nearly target
Railway keptcap_d0d14ff9-8ec4-4a6f-8ca3-04e56beb5c8ahttps://railway.com/4-viewport chunks plus 150ms primer image wait3,854ms5.13x31111713,898,321Kept; below target, no health warnings
Neon keptcap_71b30498-566d-4bbb-83b2-01431e5ea734https://neon.com/Same kept settings3,444ms5.73x31311216,543,882Kept; below target, no health warnings
Firebase keptcap_654bd6b8-34f4-4ca6-a4a1-12dc026d32c0https://firebase.google.com/Same kept settings2,556ms7.31x3211626,930,094Kept; below target, no health warnings

That put the three slowest fresh captures below four seconds while preserving the fidelity gates that mattered:

SiteBatchFinalSpeedupSegmentsFidelity gate
Railway19,761ms3,854ms5.13x10 -> 3no health warnings; sections and nodes preserved
Neon19,750ms3,444ms5.73x10 -> 3no health warnings; sections and nodes preserved
Firebase18,679ms2,556ms7.31x10 -> 3no health warnings; sections and nodes preserved

What made the process work

The useful discipline was treating speed as a measured product behavior, not a code-path assumption. Every accepted change needed a saved manifest from the real Chrome extension. The manifest had to show captureTimings.extension.totalMs, screenshot segments, capture stats, and no capture-health warnings. The viewer also had to open the expected capture rather than a mismatched page or server-only artifact.

The second useful constraint was keeping fidelity explicit. For Pluck, a page capture is not just pixels. It is the screenshot plus DOM, CSS, styles, sections, inspectable nodes, and enough health metadata to know whether the capture is trustworthy.

The result was not a generic "make waits shorter" patch. The final change moved waiting out of the repeated debugger segment loop while keeping the one-time collection and fallback paths that preserve capture fidelity.