Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Enable request interception before loading the local file, then resolve every request explicitly. Abort requests that must never leave the machine; continue only the document, scripts, styles, images, fonts or data your capture actually needs. Puppeteer pauses intercepted requests until your handler calls request.abort(), request.continue() or request.respond(). If the handler leaves one unresolved, navigation or rendering can hang.

The reliable pattern

A local URL does not guarantee a request-free page. An HTML file can reference a remote stylesheet, image, font, script, iframe, analytics endpoint or API. JavaScript can also create requests after the document appears. Puppeteer’s page-level interception API lets you make a policy for each request.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.setRequestInterception(true);
  page.on('request', request => {
    // Strict policy: do not allow any page request.
    void request.abort();
  });

  await page.goto('file:///absolute/path/to/report.html', {
    waitUntil: 'load'
  });
  await page.screenshot({path: 'report.png', fullPage: true});
  await browser.close();
})();

Install interception and its listener before goto, setContent, or another operation that causes the page to load resources. The strict example blocks every intercepted request. It is appropriate when the file is self-contained and must not access the network, but it can remove required resources and change the image.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why every intercepted request must be resolved

After interception is enabled, requests stall unless Puppeteer can continue, respond to, abort them, or complete them from the browser cache. This behavior is deliberate: it gives your code control, but it also means a missing branch is a deadlock.

#1 Best Overall
Sale
Pearson Computer Networking, 8E
  • brand: Pearson
  • Computer Networking, 8e
  • Call request.abort() for a request that should not happen.
  • Call request.continue() when the page needs the resource.
  • Call request.respond() when you want to provide a synthetic response.
  • Do not leave a conditional branch without one of those outcomes.

Use void before the promise in an event callback, or attach error handling if your application needs to record failures. If another listener or library may handle the same request, use Puppeteer’s current interception guidance to check whether it has already been handled before resolving it a second time.

Choose a blocking policy

Block everything

Block-all is the strongest no-network rule. It prevents remote assets, API calls, tracking, advertisements and third-party frames. It also prevents legitimate dependencies. A page that imports a stylesheet from a CDN may render unstyled; a chart that fetches JSON will be empty; a web font may fall back; and an image whose source is an HTTPS URL will not appear.

await page.setRequestInterception(true);
page.on('request', request => {
  void request.abort();
});

Use this policy when the file embeds all CSS, JavaScript, images and fonts, or when visual fidelity is less important than proving that no page request is allowed.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Allow only selected resource types

A selective policy preserves resources required for rendering while rejecting everything else. Puppeteer exposes the request’s resource type, such as document, stylesheet, script, image, font, xhr and fetch.

const allowedTypes = new Set([
  'document',
  'stylesheet',
  'script',
  'image',
  'font'
]);

await page.setRequestInterception(true);
page.on('request', request => {
  if (allowedTypes.has(request.resourceType())) {
    void request.continue();
  } else {
    void request.abort();
  }
});

This is only a starting policy, not a universal recipe. If the local document loads data through fetch or XHR, add those types. If it embeds a frame, decide whether document requests to that origin are acceptable. Resource type alone does not express your trust boundary, so combine it with URL checks when external origins must be denied.

Allow specific origins or paths

When a page needs a local asset server or one approved host, inspect the URL as well as the type.

const allowedTypes = new Set(['document', 'stylesheet', 'script', 'image', 'font']);
const allowedOrigins = new Set([
  'file://',
  'http://127.0.0.1:3000',
  'http://localhost:3000'
]);

function isAllowed(request) {
  if (!allowedTypes.has(request.resourceType())) return false;

  const url = new URL(request.url());
  if (url.protocol === 'file:') return true;
  return allowedOrigins.has(url.origin);
}

await page.setRequestInterception(true);
page.on('request', request => {
  if (isAllowed(request)) {
    void request.continue();
  } else {
    void request.abort();
  }
});

Adjust the origins to your environment. Do not automatically allow every http or https URL merely because the main document is local. A local page can still contact arbitrary hosts through scripts, forms, frames or data requests.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Provide a controlled response

Some captures need a deterministic response rather than a real network call. For example, you can answer a known API endpoint with fixture data and abort all other requests.

page.on('request', async request => {
  if (request.url() === 'http://127.0.0.1:3000/api/report') {
    await request.respond({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ total: 42, status: 'ready' })
    });
    return;
  }
  void request.abort();
});

Keep the response branch mutually exclusive. A request must not be both responded to and aborted.

Complete capture example with diagnostics

The following script records the requests it blocks, allows only local files and selected loopback resources, and always resolves each request. It also handles browser shutdown if navigation fails.

const path = require('node:path');
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    const allowedTypes = new Set([
      'document', 'stylesheet', 'script', 'image', 'font', 'xhr', 'fetch'
    ]);
    const allowedOrigins = new Set([
      'http://127.0.0.1:3000',
      'http://localhost:3000'
    ]);

    await page.setRequestInterception(true);
    page.on('request', request => {
      if (request.isInterceptResolutionHandled &&
          request.isInterceptResolutionHandled()) {
        return;
      }

      let allowed = false;
      try {
        const url = new URL(request.url());
        allowed = url.protocol === 'file:' ||
          (allowedTypes.has(request.resourceType()) &&
           allowedOrigins.has(url.origin));
      } catch {
        allowed = false;
      }

      if (allowed) {
        void request.continue();
      } else {
        console.error('Blocked:', request.resourceType(), request.url());
        void request.abort();
      }
    });

    const fileUrl = 'file://' + path.resolve('report.html');
    await page.goto(fileUrl, {waitUntil: 'load', timeout: 30000});
    await page.screenshot({path: 'report.png', fullPage: true});
  } finally {
    await browser.close();
  }
})();

The handled-check method can differ between Puppeteer releases; consult the interception guide that matches your installed version. The important invariant is unchanged: one listener must make exactly one resolution decision for every request it receives.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Controls that are related but different

Service-worker bypass

A service worker can intercept requests inside the page. Puppeteer provides a separate setting to bypass service workers. Use it when service-worker behavior is causing unexpected responses, but do not treat bypass as a replacement for a request-interception policy.

Offline mode

Offline emulation makes the browser behave as though its network is unavailable. It is useful for testing offline behavior, yet it is not the same as inspecting and resolving each request. Interception gives you a per-request allow, deny or synthetic-response decision.

Network-idle waits

waitUntil: 'networkidle0' and related waits are synchronization conditions. They wait for little or no activity; they do not prevent activity. A page can finish an idle wait after making unwanted calls, and a blocked request can prevent the condition from being reached if your handler leaves it unresolved.

Making the local file render correctly

Start by identifying dependencies. Search the HTML and stylesheets for http://, https://, remote fonts, CSS url() values, iframe sources and script-created fetches. Then choose one of three approaches:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Embed assets as data URLs or inline CSS and use block-all interception.
  2. Serve approved assets from a local origin and allow that origin and the resource types it needs.
  3. Use fixture responses for data endpoints, while aborting every other request.

After a capture, compare the output with interception disabled once in a controlled environment. Missing fonts, blank image boxes, unstyled markup and empty data widgets usually indicate an over-broad deny rule rather than a Puppeteer screenshot defect.

Troubleshooting

Navigation hangs or times out

The usual cause is an unresolved intercepted request. Ensure the listener is attached before navigation and that every if, exception path and early return ends in continue, abort or respond. Also check that a second listener is not claiming the same request first.

The screenshot is unstyled or missing images

Your policy probably aborts a required stylesheet, image, font or script. Log request.resourceType() and request.url(), then allow only the missing dependency or its approved origin. Do not switch to allow-all without deciding whether that violates your no-network requirement.

Charts or dynamic data are empty

Permit the required xhr or fetch request, or respond with deterministic fixture JSON. If the page needs a script to run after loading, ensure script is allowed. Add a deliberate wait for the application’s ready selector rather than relying solely on network-idle timing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Requests still appear in logs

Seeing a request event does not mean it reached the network; interception reports requests so your handler can decide. Confirm that the handler calls abort for the URL and that it is attached to the page performing the load. Frames and popups may require their own page-level handling.

“Already handled” errors

Multiple request listeners, plugins or framework helpers may resolve the same request. Consolidate policy in one listener where possible. Otherwise use the current Puppeteer interception guard before continuing, responding or aborting.

Local-file security or browser restrictions interfere

A file:// document and a local HTTP server do not have identical origin behavior. If relative paths, modules or cross-origin rules make the file unreliable, serve the project from a loopback server and allow only that exact origin. This changes the document URL, so update relative paths and your allowlist accordingly.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, reliability and cost considerations

Interception adds a decision callback for each request. A simple synchronous allow/deny check is normally cheaper and more predictable than performing network or filesystem work inside the handler. Avoid long asynchronous operations before resolving a request; they extend page load time and can create timeout failures.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Blocking unnecessary third-party resources can make captures more deterministic and reduce rendering time. Conversely, allowing every dependency may improve fidelity but introduces changing remote content, DNS or TLS failures, rate limits and privacy exposure. For repeatable builds, pin assets locally or serve fixtures and keep the allowlist narrow.

Set a navigation timeout appropriate to the page, log blocked URLs during development, and fail the job when a required selector never appears. Treat a successful PNG file as insufficient proof of correctness: verify that fonts, images and dynamic sections expected by the document are present.

Or skip the browser setup

For an API capture instead of maintaining Puppeteer policies, ScreenshotNeo accepts one GET request and returns a PNG, JPEG, WebP or PDF. Its cleanup step accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status.

cURL:

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

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo documentation for options and response details. Its MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is available on every plan. Create a free ScreenshotNeo account.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

FAQ

Does a file:// URL automatically prevent internet access?

No. The file’s source location does not describe what its markup and scripts do. External resources and script-created requests remain possible until you enforce a policy.

Can I use interception only to observe requests?

Interception pauses them, so observation code must still resolve each request. If you only need logging, log the details and then continue or abort according to an explicit rule.

Should I always block third-party scripts?

Block them when they are outside the capture’s requirements or trust boundary. If the visual result depends on one, allow that dependency narrowly or replace it with a local fixture.

Frequently Asked Questions

What is the safest default for a self-contained HTML file?

Enable interception before loading it and abort every request. Verify that all required assets are embedded; otherwise use a selective allowlist.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is offline mode enough to guarantee that no request is made?

No. Offline emulation and interception serve different purposes. Use interception when you need an explicit per-request decision.

Why did enabling interception make Puppeteer freeze?

At least one intercepted request was never continued, responded to or aborted, or another listener handled it first. Ensure every path resolves exactly once.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.