ScreenshotNeo

BlogHow-to

Capture a Full-Page Webpage Screenshot in Go Using Rod

Use Rod’s full-page screenshot API in Go, choose between viewport capture and scroll-and-stitch, and handle readiness, output, and common failures.

By the ScreenshotNeo team4 October 20269 min read

To capture a full-page webpage screenshot in Go with Rod, wait for the page to load, call page.Screenshot(true, nil), then write the returned bytes to a file. The true argument enables full-page capture. For pages where resizing the viewport is unsuitable, Rod also provides ScrollScreenshot, which scrolls through the page and stitches segments.

Capture a full page with Rod

The following example shows the capture and file-writing steps. It assumes you already have a Rod *rod.Page named page and have navigated it to the URL you want.

imageBytes, err := page.Screenshot(true, nil)
if err != nil {
    return err
}
if err := os.WriteFile("full-page.png", imageBytes, 0o644); err != nil {
    return err
}

Import os for the file write. The screenshot call returns encoded image bytes; check its error before saving. Rod’s full-page implementation reads the page’s CSS content dimensions, temporarily sets the viewport to those dimensions, captures the image, then attempts to restore the previous viewport. If it cannot determine the previous viewport, its cleanup path clears the device-metrics override. See the [Rod page screenshot API](https://github.com/go-rod/rod/blob/main/page.go) and [official screenshot example](https://github.com/go-rod/rod/tree/main/examples).

Minimal runnable program

This complete program launches a browser, opens a page, waits for the load event, captures the page, saves it, and closes the browser. Install Rod in your Go module first with go get github.com/go-rod/rod. Rod needs a Chromium-compatible browser available to launch or connect to.

package main

import (
    "log"
    "os"

    "github.com/go-rod/rod"
)

func main() {
    browser := rod.New().MustConnect()
    defer browser.MustClose()

    page := browser.MustPage("https://example.com")
    page.MustWaitLoad()

    imageBytes, err := page.Screenshot(true, nil)
    if err != nil {
        log.Fatal(err)
    }
    if err := os.WriteFile("full-page.png", imageBytes, 0o644); err != nil {
        log.Fatal(err)
    }
}

This uses Rod’s Must* methods for connection, navigation, and waiting; these panic on errors. The screenshot and file operations use ordinary error returns. In a long-running service or command that must report errors cleanly, prefer the corresponding non-Must methods for setup and navigation and propagate errors rather than allowing a panic. Rod’s official example also demonstrates waiting for load before capture.

Convenience wrapper

MustScreenshotFullPage is Rod’s file-saving convenience wrapper for full-page screenshots. It is useful when panic-on-error behavior fits your program. If no path is supplied, the wrapper saves to Rod’s default screenshots folder. Use Screenshot(true, nil) when you want to handle the returned bytes yourself, control the output path with os.WriteFile, or use ordinary error handling.

Choose full-page capture or scroll-and-stitch

Method How it works Consider it when Trade-off
Screenshot(true, req) Measures the page’s CSS content size, temporarily resizes the viewport, and captures the full page. You want the direct full-page API and the page renders correctly at the expanded viewport size. Some responsive layouts may change when the viewport is expanded. Very large pages can require substantial image memory.
ScrollScreenshot Captures viewport-sized segments while scrolling, waits between segments, then stitches them vertically. The page needs scrolling behavior or the resized viewport capture is unsuitable. Fixed-position elements such as headers can appear more than once. Segment stitching creates a large final image too.

Rod documents a default 300 ms wait between scroll segments. The scroll method leaves viewport dimensions alone, but it is not universally faster or more reliable; the project documentation does not provide comparative speed or maximum page-size benchmarks. The best choice depends on the page’s responsive behavior, lazy content, and fixed elements.

Scroll-and-stitch example

Use Rod’s ScrollScreenshot when you explicitly want the browser to scroll through the page. Wait for DOM stability before starting, then save the returned bytes. The API’s options include FixedTop and FixedBottom to skip repeated fixed regions when tuning the stitched result.

page.MustWaitStable()
imageBytes, err := page.ScrollScreenshot(rod.ScrollScreenshotOptions{})
if err != nil {
    return err
}
if err := os.WriteFile("full-page-scroll.png", imageBytes, 0o644); err != nil {
    return err
}

Check the exact option fields available in the Rod release used by your project. Rod’s official example waits for stability before calling the scrolling method and demonstrates JPEG quality configuration. Its documentation notes the default per-scroll wait and the repeated-fixed-element caveat. See the [scroll screenshot API and comments](https://github.com/go-rod/rod/blob/main/page.go) and [Rod examples](https://github.com/go-rod/rod/tree/main/examples).

Wait for the content you need

MustWaitLoad() waits for the page load event. It is a useful baseline for conventional pages, but it does not guarantee that every client-rendered component, lazy image, animation, advertisement, or later network request has finished. MustWaitStable() is shown in Rod’s scroll screenshot example, but stability is not a universal guarantee that all application-specific content is ready.

For pages with content that appears after load, decide what readiness means for that page. You can wait for an application-specific selector using Rod’s wait APIs, or scroll through the page before capturing if offscreen content is lazy-loaded on scroll. There is no universal wait that can ensure every site has finished rendering; use a selector or condition tied to the page content you need.

// Baseline for a conventional page:
page.MustWaitLoad()

// For a page with a known readiness condition, wait for that
// condition using the appropriate Rod selector/wait API before capture.
// For scroll-triggered lazy content, scroll the page as required first.

imageBytes, err := page.Screenshot(true, nil)
if err != nil {
    return err
}

Rod’s implementation and examples are on the project’s moving main branch. Check the API for the Rod version in your module and the Chromium build used in deployment before relying on version-specific behavior. The research for this guide does not establish a release-pinned compatibility matrix.

Configure the output

Rod’s screenshot request supports output settings. The official example demonstrates JPEG output, quality 90, a clip rectangle, and FromSurface: true. A clip limits the requested region; omit it when you want the un-clipped full-page capture.

imageBytes, err := page.Screenshot(true, &proto.PageCaptureScreenshot{
    Format:      proto.PageCaptureScreenshotFormatJpeg,
    Quality:     90,
    FromSurface: true,
})
if err != nil {
    return err
}
if err := os.WriteFile("full-page.jpg", imageBytes, 0o644); err != nil {
    return err
}

Add the protocol package import when using the request directly:

import "github.com/go-rod/rod/lib/proto"

The examples establish these request options, but do not establish how clipping interacts with very long full-page captures across browser versions. Validate any clipped capture against the exact layout and browser build you use. For PNG, the default request is suitable when you do not need to configure a different format.

Common problems and fixes

Symptom Likely cause What to do
The screenshot is blank or missing page content. The capture ran before the content you care about rendered, or the page requires client-side work after the load event. Wait for the page load event as a baseline, then wait for a page-specific selector or readiness condition. Check that navigation completed and the target URL is correct.
Images below the fold are absent. The site loads images lazily when they approach the viewport. Scroll through the relevant regions before capturing, then wait for the images or page state you need. A full-page capture does not promise to force every offscreen resource to load.
A fixed header or footer repeats in the output. ScrollScreenshot captures multiple viewport segments and stitches them together. Use FixedTop or FixedBottom to skip the repeated areas, or try the direct full-page method if its viewport resizing works for that page.
The layout differs from the visible browser viewport. The full-page method temporarily expands the viewport to the page’s CSS content dimensions; responsive CSS can respond to that change. Try ScrollScreenshot, which leaves viewport dimensions alone, and compare the result on the target page.
Browser startup or connection fails. A compatible browser may not be available to Rod in the execution environment, or the browser process may have failed to start. Check the browser installation and launch environment, then consult Rod’s setup guidance for the version you use. Avoid using MustConnect in a service where a panic would terminate work.
The file is missing or empty. The screenshot returned an error, the write failed, or the process lacks permission for the output path. Check both returned errors, use a writable path, and ensure the destination directory exists.
The process uses too much memory on a very long page. A tall screenshot and its encoded or decoded image buffers can be large. Try scroll-and-stitch only if it suits the page, reduce the capture area where appropriate, and process captures with bounded concurrency. Rod’s reviewed sources do not specify a safe maximum page size or memory threshold.

Performance, reliability, and cost

Both methods depend on page rendering and browser work. The full-page path captures using the measured content dimensions after temporarily resizing the viewport; the scrolling path takes multiple captures and stitches them. The available Rod sources provide no comparative benchmark, so measure representative pages in your own environment if latency or throughput matters.

For reliability, wait for the state required by your task, handle returned errors, and verify that the output file was written. A load event or DOM stability wait does not guarantee that every third-party resource or animated element has reached a desired state. Extremely tall pages can consume significant memory, and the reviewed sources do not give a maximum reliable dimension.

Rod is a library, so the screenshot workflow runs in the browser environment you operate. Budget for browser provisioning, compute, storage, and maintenance of the capture service if you run it in production. Rod’s documentation cited here does not state a per-screenshot service price.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. Its full-page option loads lazy images, and you can capture a page without provisioning your own browser process. See the ScreenshotNeo API documentation for request options.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

Cookie and consent banners, newsletter popups, and chat widgets from supported platforms are removed before the shot, and each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status. The MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

FAQ

Does Screenshot(true, nil) save a file?

No. It returns screenshot bytes. Write those bytes to a file yourself, or use Rod’s MustScreenshotFullPage convenience wrapper when its panic-on-error behavior and default save behavior fit your program.

Does full-page capture load every lazy image?

Do not assume so. The capture API does not promise to trigger every site’s lazy-loading behavior. Scroll or wait for the page-specific content before capturing when needed.

Can I use JPEG instead of PNG?

Yes. Pass a screenshot request with the JPEG format and a quality setting, as in the output example. Rod’s official example uses quality 90.

Which method is always faster?

Rod’s reviewed documentation gives no comparative speed benchmark. The direct method resizes the viewport for capture; the scrolling method captures and stitches segments. Choose based on the page behavior, then benchmark your own workload if speed matters.