Back to Articles

go-stare: Fast Web Screenshots Using Chrome DevTools Protocol Without the Headless Browser Overhead

[ View on GitHub ]

go-stare: Fast Web Screenshots Using Chrome DevTools Protocol Without the Headless Browser Overhead

Hook

What if you could take web screenshots without actually running a headless browser? go-stare does exactly that by speaking Chrome’s native protocol, cutting resource usage by up to 80% while processing hundreds of URLs per minute.

Context

Taking automated screenshots of web pages has traditionally meant firing up heavyweight solutions like Puppeteer, Playwright, or Selenium. These tools launch entire browser instances, each consuming 100-300MB of memory and requiring Node.js runtimes or complex dependency chains. For security researchers scanning thousands of subdomains, DevOps teams monitoring web services, or QA engineers capturing UI states, this overhead becomes a serious bottleneck.

The Chrome DevTools Protocol (CDP) offers a more elegant path. CDP is the same JSON-RPC interface that Chrome DevTools uses to communicate with the browser. Instead of launching new browser processes, you can connect to an existing Chrome instance and control it programmatically. go-stare exploits this architecture, delivering a lightweight Go binary that captures screenshots with minimal overhead—no npm packages, no heavyweight frameworks, just direct protocol communication.

Technical Insight

Workers

URLs

Create tab context

Create tab context

Create tab context

Navigate & Load

Screenshot data

URL Input

stdin/file/args

Worker Pool

5 goroutines

Chrome Instance

:9222 CDP

Worker 1

Worker 2

Worker N

CDP Commands

Page.captureScreenshot

Output Directory

PNG files

System architecture — auto-generated

go-stare’s architecture revolves around three core components: CDP connection management, concurrent URL processing, and screenshot capture orchestration. Unlike tools that bundle an entire browser automation framework, go-stare uses the chromedp library selectively, leveraging only its CDP communication layer while avoiding the heavy browser lifecycle management.

The tool expects you to launch Chrome or Chromium with remote debugging enabled, typically on port 9222. This is a one-time setup that keeps a single browser instance running:

# Start Chrome with remote debugging
chrome --headless --remote-debugging-port=9222

# Then use go-stare to capture screenshots
cat urls.txt | go-stare -o screenshots/

Internally, go-stare implements a worker pool pattern using goroutines. The default concurrency of 5 workers strikes a balance between throughput and resource consumption. Each worker establishes its own CDP session (a browser tab context), navigates to a URL, waits for the load event, and captures a screenshot using the Page.captureScreenshot CDP command. Here’s the conceptual flow:

// Simplified conceptual code showing go-stare's approach
func captureScreenshot(ctx context.Context, url string, output string) error {
    // Create a new tab context via CDP
    tabCtx, cancel := chromedp.NewContext(ctx)
    defer cancel()
    
    var buf []byte
    
    // Execute CDP commands: navigate and capture
    err := chromedp.Run(tabCtx,
        chromedp.Navigate(url),
        chromedp.FullScreenshot(&buf, 90), // quality: 90
    )
    
    if err != nil {
        return fmt.Errorf("screenshot failed: %w", err)
    }
    
    // Write to disk
    filename := generateFilename(url)
    return os.WriteFile(filepath.Join(output, filename), buf, 0644)
}

The real performance advantage comes from reusing the browser process. Traditional headless tools spawn a new Chromium instance for each batch job, incurring 2-3 seconds of startup overhead every time. go-stare connects to your already-running Chrome instance in milliseconds. When processing 500 URLs, this architectural difference translates to saving over 15 minutes of cumulative browser startup time.

go-stare also shines in Unix pipeline compositions, a design philosophy deeply rooted in Go’s stdlib patterns. The tool reads from stdin by default, making it trivial to chain with other reconnaissance tools:

# Discover subdomains, probe for live HTTP services, screenshot them
subfinder -d example.com -silent | \
  httpx -silent -status-code -mc 200 | \
  go-stare -o example-screenshots/ -c 10

This composability makes go-stare particularly valuable in security workflows where you’re discovering and documenting thousands of web assets. The -c flag controls concurrency—bump it to 10 or 15 when your Chrome instance has sufficient resources, or throttle it down to 2-3 when targeting rate-limited services.

One often-overlooked advantage of the CDP approach is debugging transparency. Since you’re connecting to a real Chrome instance, you can open chrome://inspect in another browser tab and watch your screenshots being captured in real-time. This visibility is invaluable when troubleshooting authentication flows or JavaScript-heavy sites that don’t render immediately.

Gotcha

The biggest limitation is also go-stare’s core design choice: you must manage the Chrome instance yourself. This isn’t a double-click-and-run tool. You need to launch Chrome with --remote-debugging-port, ensure it stays running, and handle scenarios where it crashes or hangs. For containerized environments, this means building Docker images with Chrome installed and managing process supervision—not insurmountable, but extra operational complexity compared to self-contained tools like wkhtmltoimage.

Customization options are minimal. There’s no way to set viewport dimensions, inject custom headers, handle authentication prompts, or wait for specific DOM elements. The tool captures whatever Chrome renders at the moment the page load event fires. JavaScript-heavy SPAs that render content after initial load may produce blank or incomplete screenshots. If you need to wait 5 seconds for animations to complete, execute custom JavaScript before capture, or emulate mobile viewports, you’ll need to fork the code or choose a more feature-rich alternative. go-stare prioritizes speed and simplicity, deliberately trading configurability for performance. At version 0.0.2, expect rough edges—error handling could be more graceful, and there’s no retry logic for network failures or timeout configuration beyond what chromedp provides by default.

Verdict

Use if: You’re doing security reconnaissance or batch web monitoring where speed matters more than pixel-perfect accuracy, you’re comfortable managing a Chrome process, you need a lightweight Go binary that integrates seamlessly with Unix pipelines, or you’re processing hundreds to thousands of URLs and traditional headless tools are too slow. Skip if: You need advanced browser automation features like custom viewports, JavaScript injection, authentication handling, or precise rendering control, you want a zero-configuration tool that bundles everything, you’re taking screenshots of complex SPAs that require wait conditions, or you need production-grade stability with comprehensive error handling and retries. For those cases, reach for chromedp directly (more control), Playwright (more features), or gowitness (security-focused with built-in reporting).

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/automation/dwisiswant0-go-stare.svg)](https://starlog.is/api/badge-click/automation/dwisiswant0-go-stare)