> ## Documentation Index
> Fetch the complete documentation index at: https://docs.surfsky.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Speed optimization

> Measure browser startup, page loading, and automation time before tuning throughput.

Measure one representative job before changing its settings. Record browser startup time, navigation time, time spent waiting or interacting, proxy traffic, and whether the result contains the data you need.

The connection from your worker to Surfsky carries automation commands. The browser's proxy carries website traffic. These are separate paths; changing the website proxy does not automatically shorten the worker's CDP round trips.

## Pick the region first

Surfsky runs in two regions, Germany and the United States. Choose the one closest to your automation worker in the [dashboard](https://app.surfsky.io). Each region has its own API base URL; use the one shown for your account. Network latency between the worker and browser adds to the time needed for each CDP command.

The region applies to the whole account and can be changed at most once every 24 hours, with no browsers running. Choose the proxy location separately, based on the location required by your task. A browser in Germany can use a US proxy.

<span id="do-work-in-parallel" />

<span id="latency" />

<span id="latency-is-per-command" />

<span id="local-vs-cloud" />

<span id="optimization-tips" />

<span id="performance-metrics" />

<span id="prefer-the-bulk-operation" />

<span id="quick-example" />

<span id="speed-strategies" />

## Reduce command round trips

Collect several values in one page evaluation when they belong to the same read operation. With Playwright, for example:

```python theme={null}
links = await page.locator("a").evaluate_all(
    "elements => elements.map(a => ({text: a.textContent, href: a.href}))"
)
```

Do not run dependent actions concurrently. A click that submits a form must happen after the fields are filled.

<span id="read-the-api-not-the-dom" />

<span id="stop-waiting-for-the-whole-page" />

## Wait for the content you need

Navigate with `domcontentloaded`, then wait for a selector or response that shows the required content is ready:

```python theme={null}
await page.goto("https://example.com", wait_until="domcontentloaded")
await page.locator("h1").wait_for()
```

Use `load` when the job needs later resources, such as a screenshot of the complete page. Avoid `networkidle` on pages with continuous background requests. A short fixed delay can appear fast while returning incomplete data.

If the page loads the required data from a JSON response, [capture that response](/debugging#reading-the-pages-requests) before doing additional DOM queries.

<span id="block-what-you-do-not-need" />

<span id="traffic-is-the-other-bill" />

## Block unneeded resources

For text extraction, test whether you can omit images, fonts, or media. In the Python SDK:

```python theme={null}
# Inside an AsyncSurfsky client context.
async with client.browser(block_resources={"image", "font", "media"}) as browser:
    await browser.goto("https://example.com")
    print(await browser.title())
```

See the [Python SDK setup](/sdk/python) for imports and credentials. You can also [block domains](/proxies#proxy-blacklist) in the browser start request.

Compare output after each change. Images may contain the data you need, fonts affect screenshot layout, and blocking challenge resources can stop navigation.

For assets you need to load, test [domain routing](/proxies#domain-routing) through a cheaper datacenter proxy. Keep the target site on the residential connection and compare asset loading time and traffic cost before applying the rules to more jobs.

<span id="reuse-a-warm-cache" />

<span id="skip-the-login" />

<span id="smart-proxy-rotation" />

## Reuse state where it helps

A [persistent profile](/sessions) can avoid repeated logins when its saved state is still valid. Enable the storage types the site needs, and use the default context when reconnecting.

A [shared cache](/shared-cache) can reduce repeated downloads across sessions. Measure a warm run and keep cache keys separate for workloads that need independent cache state.

Reuse a browser for multiple related pages when the jobs can safely share cookies and storage. This saves startup work, but it also carries state from one job to the next.

<span id="browser-limits" />

<span id="browser-pool-management" />

<span id="rate-limiting" />

<span id="scaling-out" />

<span id="watch-your-limits" />

## Scale with a bounded pool

Use the [SDK pool](/concurrency#use-an-sdk-pool) with an explicit concurrency setting. Increase it gradually and measure completed results per minute alongside target-site failures and traffic per result. Start the next browser while the current job finishes so startup overlaps with work instead of adding to it.

More workers stop helping when browser capacity, API rate limits, proxy throughput, or the target site becomes the bottleneck. See [limits](/limits) for the signals to monitor.

<span id="closing-browsers" />

<span id="common-mistakes" />

<span id="need-more-speed" />

<span id="resilient-error-handling" />

## Close what you open

Stop the browser when the job ends, including error paths. Choose `inactive_kill_timeout` to allow legitimate pauses, and investigate sessions left running after a worker exits.

For long-running pools, retire a browser after a job leaves unexpected state or repeated failures. Do not recycle on an arbitrary schedule without checking whether it improves the workload.

If command round trips dominate after reducing unnecessary calls, move your worker closer to the browser or [switch the region](#pick-the-region-first). Keep the same proxy and target URLs so the measurement isolates that change.
