# Block Images, Fonts and Media in Playwright to Cut Proxy Bandwidth

Source: https://ipvolt.com/guides/playwright-block-images-save-bandwidth
Markdown: https://ipvolt.com/guides/playwright-block-images-save-bandwidth.md

[Home](https://ipvolt.com/index.md) / [Guides](https://ipvolt.com/guides.md) / [Block Images, Fonts and Media in Playwright to Cut Proxy Bandwidth](https://ipvolt.com/guides/playwright-block-images-save-bandwidth.md)

Category: Integration
Published: 2026-10-09
Reading time: 7 minutes
Author: ipvolt

Block images, fonts and media in Playwright with route() in Python and Node.js, for a whole browser context or by host, plus the cache and service worker caveats.

## The starting point

Abort the heavy requests a scraping job does not need, for every page in a browser context, then measure whether the run really moved fewer bytes.

## Measure the job before you block anything

Images, fonts and video can make up much of what a page downloads, and many scraping jobs never read them. Blocking them with request interception is a common way to reduce proxy traffic. Whether it helps your job depends on your pages, so start with a measurement: run the job once as it is and record where the bytes go. [scrapescope](/guides/scraper-bandwidth-report) is a free, open-source local meter that reports bytes per host and per resource type for a Playwright run and models the effect of blocking images, media and fonts.

The examples below use Playwright 1.63 and the placeholder https://example.com/. Point TARGET_URL at a page you are allowed to automate, and keep proxy settings and credentials in your own launch or context options; none are needed to try the blocking itself.


## Block by resource type in Python

Playwright labels every request with the resource type the rendering engine perceived: document, stylesheet, image, media, font, script, texttrack, xhr, fetch, eventsource, websocket, manifest or other. Register one route for all URLs on the browser context and abort the three heavy types; every other request continues unchanged.

Save as block_resources.py and run python block_resources.py after pip install playwright and playwright install chromium. The context also blocks service workers, for the reason explained further down.

### block_resources.py · Playwright for Python

```python
import os

from playwright.sync_api import Route, sync_playwright

TARGET_URL = os.environ.get("TARGET_URL", "https://example.com/")
BLOCKED_TYPES = {"image", "media", "font"}


def block_heavy(route: Route) -> None:
    if route.request.resource_type in BLOCKED_TYPES:
        route.abort()
    else:
        route.continue_()


with sync_playwright() as p:
    browser = p.chromium.launch()
    try:
        context = browser.new_context(service_workers="block")
        context.route("**/*", block_heavy)
        page = context.new_page()
        response = page.goto(TARGET_URL, wait_until="load")
        print({"status": response.status if response else None})
        context.close()
    finally:
        browser.close()
```


## Block by resource type in Node.js

The Node.js version is the same route with the same three types. route.request().resourceType() returns the type, route.abort() fails the request before it leaves the browser, and route.continue() sends everything else as it was.

Save as block-resources.mjs and run node block-resources.mjs after npm install playwright and npx playwright install chromium.

### block-resources.mjs · Playwright Chromium

```javascript
import { chromium } from 'playwright';

const TARGET_URL = process.env.TARGET_URL ?? 'https://example.com/';
const BLOCKED_TYPES = new Set(['image', 'media', 'font']);

const browser = await chromium.launch();
try {
  const context = await browser.newContext({ serviceWorkers: 'block' });
  await context.route('**/*', route =>
    BLOCKED_TYPES.has(route.request().resourceType()) ? route.abort() : route.continue());
  const page = await context.newPage();
  const response = await page.goto(TARGET_URL, { waitUntil: 'load' });
  console.log({ status: response?.status() });
  await context.close();
} finally {
  await browser.close();
}
```


## Apply it to every page in the browser context

A route on the browser context applies to requests made by any page in that context: pages you open later, second tabs and popups a page opens with window.open(). A route on a single page applies to that page only, so a popup or a new tab would load its images and fonts again. Register the context route before the first page loads, and put it in one helper so every job uses the same rule.

When a request matches both, page routes take precedence over browser context routes, and when several page routes match, the most recently registered one wins. Keep one blocking route per context to avoid surprises.

### context-wide.mjs · one route for every page

```javascript
import { chromium } from 'playwright';

const TARGET_URL = process.env.TARGET_URL ?? 'https://example.com/';
const BLOCKED_TYPES = new Set(['image', 'media', 'font']);

// One route on the context covers every page the context opens, including
// tabs opened later and popups. Register it before the first page loads.
async function blockHeavyResources(context) {
  await context.route('**/*', route =>
    BLOCKED_TYPES.has(route.request().resourceType()) ? route.abort() : route.continue());
}

const browser = await chromium.launch();
try {
  const context = await browser.newContext({ serviceWorkers: 'block' });
  await blockHeavyResources(context);
  const first = await context.newPage();
  const second = await context.newPage();
  await Promise.all([
    first.goto(TARGET_URL, { waitUntil: 'load' }),
    second.goto(TARGET_URL, { waitUntil: 'load' }),
  ]);
  console.log({ pages: context.pages().length });
  await context.close();
} finally {
  await browser.close();
}
```


## Block specific heavy hosts by URL pattern

Sometimes one host is the problem: a video CDN, an ad server or a font service. route() also takes a URL pattern, so you can abort everything from those hosts and leave the resource types alone. A glob such as https://media.example.net/** matches every URL on one host; a regular expression can list several. The hosts below are placeholders; take the real names from your measurement rather than from a generic blocklist.

### block_hosts.py · abort named hosts

```python
import os
import re

from playwright.sync_api import sync_playwright

TARGET_URL = os.environ.get("TARGET_URL", "https://example.com/")
# A glob covers every URL on one host; a regular expression can cover several.
HEAVY_HOST = "https://media.example.net/**"
HEAVY_HOSTS = re.compile(r"^https://(video\.example\.org|fonts\.example\.com)/")

with sync_playwright() as p:
    browser = p.chromium.launch()
    try:
        context = browser.new_context(service_workers="block")
        context.route(HEAVY_HOST, lambda route: route.abort())
        context.route(HEAVY_HOSTS, lambda route: route.abort())
        page = context.new_page()
        response = page.goto(TARGET_URL, wait_until="load")
        print({"status": response.status if response else None})
        context.close()
    finally:
        browser.close()
```


## Routing turns off the HTTP cache

Playwright's API reference states for both page.route() and browserContext.route(): "Enabling routing disables http cache." Once any route is registered, stylesheets, scripts and other repeated assets that the browser would normally reuse from its cache are downloaded again for each page. On a job that loads many pages in one context, that extra transfer can offset part of what blocking removes, or all of it. This is why a single-page test is not enough: measure the whole run.


## Block service workers while you intercept

Requests that a page's service worker handles can bypass your routes: the API reference notes that browserContext.route() will not intercept requests intercepted by Service Worker, and recommends disabling service workers when using request interception by setting serviceWorkers to 'block'. That is the service_workers="block" (Python) and serviceWorkers: 'block' (Node.js) context option in every example above. A site that depends on a service worker, for example to cache content for offline use, can behave differently once it cannot register one.


## Blocking can change what the page does

Blocking images, media and fonts can break extraction, change how a page behaves or attract anti-bot scrutiny. A page may wait for an image before rendering the data you read, lazy-load content only after media events, or treat a browser that never downloads images as unusual. Check that the values you extract are unchanged after enabling the route, and keep blocking within what the site's terms allow.


## Measure again and compare

Run the same job twice through the same provider, once without the route and once with it, and compare the two runs. A second measured run is the evidence; a model or an estimate is a prediction. [scrapescope](/guides/scraper-bandwidth-report) can compare two reports side by side in its viewer, including bytes per host and per resource type, so you can see what the route removed and what the lost HTTP cache added back.


## Before you ship

- Measure the job once without blocking before changing anything.
- Register the route on the browser context, before the first page loads.
- Block service workers in the context options while routes are active.
- Confirm that the extracted values are unchanged after blocking.
- Compare a second measured run through the same provider before relying on the change.

## Sources & further reading

- [Playwright: browserContext.route() (routing notes, v1.63)](https://playwright.dev/docs/api/class-browsercontext#browser-context-route)
- [Playwright: page.route()](https://playwright.dev/docs/api/class-page#page-route)
- [Playwright: request.resourceType()](https://playwright.dev/docs/api/class-request#request-resource-type)
- [Playwright: the serviceWorkers context option](https://playwright.dev/docs/api/class-browser#browser-new-context-option-service-workers)
- [Playwright for Python: browser_context.route()](https://playwright.dev/python/docs/api/class-browsercontext#browser-context-route)
- [Playwright issue 1090: routes and service workers](https://github.com/microsoft/playwright/issues/1090)

## Related guides

- [Configure a proxy in Playwright](https://ipvolt.com/guides/playwright-proxy-setup.md)
- [Rotating vs sticky proxies: plan for session continuity](https://ipvolt.com/guides/rotating-vs-sticky-proxies.md)
- [Fix ERR_TUNNEL_CONNECTION_FAILED in Playwright and Puppeteer](https://ipvolt.com/guides/fix-err-tunnel-connection-failed.md)

## About ipvolt

Examples use generic proxy settings, with links to the original technical documentation. Product-specific behavior must be checked with your provider. ipvolt is still in development.

## Know when access opens.

ipvolt · In development

We’re building proxy infrastructure for developers and data teams. Join the interest list for a heads-up when ipvolt is ready.

Consent: One email when access opens. Nothing else.

[Get early access](https://ipvolt.com/guides/playwright-block-images-save-bandwidth#waitlist-closing). Use the email form on this page to join the interest list.

[Privacy](https://ipvolt.com/privacy)

