How we measure the CO₂ impact of this website

Measuring website emissions with Playwright, CDP and the Sustainable Web Design Model.


In this article

We wanted to explore how we could surface measurable CO₂ savings, making our environmental impact visible means it’s more likely to be monitored and acted upon.

The recent migration of etch.co from Next.js to Eleventy gave us a good test case. A 24% performance improvement should show up as a significant CO₂ difference between the two versions.

There were two hard requirements for the measurement approach:

  1. Automation: Checking the CO₂ savings should be something that can run regularly so the numbers stay honest as the site changes.
  2. No client-side JS: Shipping extra code to the browser would add to the CO₂, so the code has to be build or server side only.

We ended up settling on a combination of tools.

The SWDM is used to calculate digital carbon emissions. It’s a complex model that takes into account global grid intensity and energy intensity data, but the @tgwf/co2 library provided by the Green Web Foundation makes it pretty straightforward to use.

This same library can account for green hosting by checking the hostname against the Green Web Foundation API, this all gets factored into the final calculation.

import { co2 as Co2, hosting } from '@tgwf/co2';

const greenHosting = await hosting(hostname);
const swd = new Co2({
  model: 'swd',
  version: 4,
  rating: true
});
const {
  total: gramsOfCO2,
  rating
} = swd.perVisit(totalBytesReceived, greenHosting);

CDP exposes Network.loadingFinished.encodedDataLength, the real size of each response after compression. That’s the input SWDM expects and it’s more accurate than Playwright’s own response.sizes() or the browser’s performance.getEntriesByType('resource'), which miss or undercount some requests.

const cdp = await ctx.newCDPSession(page);
await cdp.send('Network.enable');

let totalBytes = 0;
cdp.on('Network.loadingFinished', (evt) => {
  totalBytes += evt.encodedDataLength ?? 0;
});

We did consider simpler options, such as using the bundle size from the build output or Playwright’s response.sizes() but they didn’t accurately represent the fully rendered page. We found that CDP’s encodedDataLength got as close as possible to what the browser actually pulled down.

Playwright drives a headless Chromium in an isolated browser context per URL, so there’s no cross-URL cookie or cache leakage affecting the numbers.

import { chromium } from 'playwright';

const browser = await chromium.launch();
const ctx = await browser.newContext();
const page = await ctx.newPage();
await page.goto(url, { waitUntil: 'networkidle' });

Measuring four pages on both versions returned the following results:

Page Next.js g/visit 11ty g/visit Δ CO₂
Home 0.0872 0.0399 −54%
Blog index 0.0889 0.0401 −55%
Blog post 0.0888 0.0312 −65%
Team 0.0924 0.0485 −48%

On average, that’s about a 55% reduction in CO₂ per visit.

Imagining this site gets 100,000 visits a month, that works out to about 60kg of CO₂ saved per year — roughly the same as driving 350km in an average petrol car.

To keep us accountable we’ve added the score to the website’s footer on every page.

The measurement script writes a JSON file with the full per-page breakdown. A GitHub Action runs on the first of each month and opens a PR with the refreshed JSON if anything changed. The footer reads from that JSON and renders the number that matches the page type you’re looking at.

The script measures one representative page per page type (home, blog index, etc) and the footer renders the matching number. Pages outside that set (like contact or a service page) fall back to the mean across the measured set, so there’s always a reasonable number to show.

We also played with generating a shields.io-style SVG badge from the same data, which could be an option for public repo readmes.

If you’re interested in seeing the source for the script, badge generator, and GitHub Action, an example is available at github.com/etchteam/co2-measurement.

The W3C Web Sustainability Guidelines cite research suggesting internet-related emissions now surpass those of the aviation industry, and emissions run through nearly all of its criteria.

A monthly number in the footer feels proportionate for a simple static site like this one. Getting visibility on the emissions is the first step to reducing them.

The next step is to take this further on higher-traffic or more complex apps where it may be worth considering things like carbon budgets per route, thresholds that alert when a traffic spike pushes emissions up unexpectedly, or even surfacing the impact of file uploads or downloads directly within the UI.