What Is Web Archiving? Formats, Evidence and Code

Web archiving saves web pages as they looked at a moment in time. Compare PNG, PDF, MHTML and WARC, see which hashes stay stable, and capture one in code.

By Simon Chiu · Updated

Web archiving is the practice of capturing web pages, with the date, address and context of the capture, and storing the copies so they can be found, viewed and trusted after the live page changes or disappears.

Libraries archive the web for history. Companies archive their own pages for compliance, legal disputes and change tracking: the terms of service on a given date, the price shown to customers, the claim made in an ad. This entry is part of the screenshot and rendering glossary.

A plain explanation

A web page is not a document; it is a set of instructions that a browser turns into a picture each time someone visits. Tomorrow the instructions, the data behind them or the third-party scripts may differ. Archiving freezes one visit. The two main schools are:

  • Record the traffic (WARC): save every HTTP request and response so the page can be replayed in a browser later.
  • Record the result: save what the browser rendered, as an image, a PDF or a single-file page (MHTML).

Replay keeps interactivity but can break when scripts expect a live server. A rendered image never breaks, but it cannot be clicked.

What one capture produces

Headlessbrowser full-page PNGPDFMHTML manifest.json URL, time, status,server IP, SHA-256of each file Write-oncestorage
One archive record: several renderings of the same visit, tied together by a manifest of facts and hashes.

We ran the archiving script below against https://example.com twice, a minute apart, and compared the files:

File Bytes Same SHA-256 on the second run?
Full-page PNG (1280 px wide) 79,353 yes
PDF (A4, backgrounds on) 162,579 no
MHTML snapshot 5,448 no

The PDF embeds its creation time and the MHTML file a generated boundary and date, so their hashes change on every capture even when the page did not. The PNG hash was identical, which makes it a cheap "did the page change?" signal for a static page. Pages with clocks, ads or rotating content will change it too; there, compare images visually instead.

How it works technically

A capture tool loads the page in a real browser, waits until the content is ready, and then serializes it. Screenshots and PDFs come from the browser's renderer. MHTML comes from Chrome's DevTools Protocol command Page.captureSnapshot, which bundles HTML, CSS and images into one MIME file. WARC capture needs a recording proxy or crawler, such as tools built on the Webrecorder libraries, that writes the raw HTTP exchange.

Trust comes from the record, not the format: the requested and final URL, a UTC timestamp, the HTTP status, the server address, and a hash of each file, stored somewhere that blocks later edits, for example object storage with an object lock.

How to archive a page in Puppeteer, Playwright and RenderScreenshot

Puppeteer, producing all three files and the manifest:

// archive.mjs: node archive.mjs <url> <outDir>
import { createHash } from 'node:crypto';
import { mkdir, writeFile } from 'node:fs/promises';
import puppeteer from 'puppeteer';

const [target = 'https://example.com', outDir = 'archive'] = process.argv.slice(2);
const sha256 = (buf) => createHash('sha256').update(buf).digest('hex');

await mkdir(outDir, { recursive: true });
const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1280, height: 800 });
  const capturedAt = new Date().toISOString();
  const response = await page.goto(target, { waitUntil: 'networkidle0' });

  const png = await page.screenshot({ fullPage: true, type: 'png' });
  const pdf = await page.pdf({ format: 'A4', printBackground: true });
  const cdp = await page.createCDPSession();
  const { data: mhtml } = await cdp.send('Page.captureSnapshot', { format: 'mhtml' });

  const files = { 'page.png': png, 'page.pdf': pdf, 'page.mhtml': Buffer.from(mhtml) };
  const manifest = {
    requestedUrl: target, finalUrl: page.url(), capturedAt,
    status: response?.status(), serverIp: response?.remoteAddress()?.ip,
    userAgent: await browser.userAgent(), files: {},
  };
  for (const [name, buf] of Object.entries(files)) {
    await writeFile(`${outDir}/${name}`, buf);
    manifest.files[name] = { bytes: buf.length, sha256: sha256(buf) };
  }
  await writeFile(`${outDir}/manifest.json`, JSON.stringify(manifest, null, 2));
} finally {
  await browser.close();
}

Playwright has the same screenshot and PDF calls (PDF in Chromium only), and await response.serverAddr() for the IP:

import { chromium } from 'playwright';

const browser = await chromium.launch();
try {
  const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
  const response = await page.goto('https://example.com', { waitUntil: 'networkidle' });
  await page.screenshot({ fullPage: true, path: 'page.png' });
  await page.pdf({ path: 'page.pdf', format: 'A4', printBackground: true });
  console.log(response.status(), (await response.serverAddr())?.ipAddress);
} finally {
  await browser.close();
}

Both scripts were tested on 2026-10-15 with Node 22.18, Puppeteer 24.43.1 (headless shell mode, Chrome 148) and Playwright 1.64.1 (Chromium 156).

RenderScreenshot captures the full page or a PDF. By default every request is a fresh render; cached renders are reused only when you set cache.available. With custom storage, each capture is also copied to your own S3-compatible bucket, where you can apply your own retention lock. See the capture options.

import { createHash } from 'node:crypto';
import { writeFile } from 'node:fs/promises';
import { Client, TakeOptions } from 'renderscreenshot';

const client = new Client(process.env.RS_API_KEY);
const capturedAt = new Date().toISOString();
const png = await client.take(TakeOptions.url('https://example.com').fullPage().blockCookieBanners());
const pdf = await client.take(TakeOptions.url('https://example.com').format('pdf').pdfPrintBackground());
await writeFile('page.png', png);
await writeFile('page.pdf', pdf);
console.log(capturedAt, createHash('sha256').update(png).digest('hex'));

The RenderScreenshot snippet follows the SDK docs and was not run for this page.

Common mistakes

  • Archiving without a record. A PNG with no URL, time and hash next to it proves little.
  • Using the PDF hash to detect change. As measured, it changes every time.
  • Capturing before the content loads. Archive a spinner and you have archived nothing; use a real wait strategy.
  • Capturing the cookie banner. It hides the content you meant to keep. Block or dismiss it.

For a deeper comparison of image archives and WARC, read screenshots vs WARC for website archiving, and see the website archiving use case. To archive pages on a schedule without running browsers, get a free API key.

Frequently asked questions

What is the difference between web archiving and a screenshot?
A screenshot is one possible output of web archiving. Archiving is the process: capture, record when and from where, hash, and store so the copy can be found and trusted later. A full archive often keeps a screenshot next to the page's code and HTTP data.
Is a screenshot enough as evidence of what a page showed?
It shows what a reader saw, which is often what matters. It does not prove where the image came from on its own. Record the URL, time, HTTP status and a hash at capture time, and store the files where they cannot be edited.
What is a WARC file?
WARC is the ISO standard container used by the Internet Archive and national libraries. It stores the raw HTTP requests and responses of a crawl, so the page can be replayed later in a tool such as a Wayback-style viewer.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.