How I Built a Carbon Footprint Badge for Websites
Last year, I stumbled on a concerning statistic: the average website produces 0.81g of CO2 per page view. That's not trivial when you multiply it by billions of daily page loads.
I decided to build something that would help developers visualize—and care about—their site's environmental impact. Enter: Carbon Badge.
The Idea
Website carbon footprint badges already exist, but I wanted something:
- Easy to embed on any website (single line of HTML)
- Automatically updated (no manual recalculation)
- Visually honest (green if you're good, red if you're not)
- Built on real data (not hand-wavy estimates)
The Data Layer: Google PageSpeed Insights API
Instead of inventing my own metrics, I decided to piggyback on Google's PageSpeed Insights API, which already measures page load performance and analyzes Core Web Vitals.
Turns out Google publishes research linking page load time to energy consumption: faster pages = less CPU work = less CO2.
const fetchPSIMetrics = async (targetUrl) => {
const response = await fetch(
`https://www.googleapis.com/pagespeedonline/v5/runPagespeed`,
{ url: targetUrl, key: PSI_API_KEY, strategy: 'mobile' }
);
const metrics = response.lighthouseResult.metrics;
const loadTime = metrics.first_contentful_paint / 1000; // seconds
return loadTime * CO2_PER_SECOND; // ~0.003g per second
};
The Frontend Badge
Users get an embeddable widget with a single line of code:
<script src="https://carbon-badge.com/badge.js" data-url="https://yoursite.com"></script>
This loads a tiny iframe that:
- Calls the backend API
- Fetches PSI metrics for your domain
- Calculates CO2 in real-time
- Renders a colored badge (green/yellow/red)
The Calculation Engine
I didn't just use raw load time. I weighted multiple performance factors:
function calculateCarbonFootprint(psiMetrics) {
const weights = {
loadTime: 0.4, // Biggest factor
firstContentfulPaint: 0.3,
largestContentfulPaint: 0.2,
totalBlockingTime: 0.1
};
const normalizedScore = Object.entries(weights).reduce((score, [metric, weight]) => {
const normalized = Math.min(psiMetrics[metric] / 3000, 1);
return score + (normalized * weight);
}, 0);
return 0.81 * normalizedScore; // 0.81g is average
}
Handling the Cache Problem
PageSpeed Insights is slow—30-60 seconds per run. I couldn't make users wait.
Solution: Async background updates
app.get('/api/carbon', (req, res) => {
const cached = cache.get(req.query.url);
res.json(cached || { co2: 0.81, stale: true });
// Background: refresh if older than 24h
if (!cached || Date.now() - cached.updated > 86400000) {
queue.add({ task: 'refresh_psi', url: req.query.url });
}
});
Users get instant results, and we refresh periodically in the background.
The Feedback Loop
Here's the clever part: when developers see their badge is red, many optimize to make it green. I've had users email saying: "Your badge motivated me to cut my page load time by 50%." That's the whole point.
What I Learned
- Use existing APIs. Don't reinvent metrics—build on proven data (Google PSI, Carbon Trust)
- Cache aggressively. External APIs are slow; cache + async updates is essential
- Visual feedback drives behavior. A badge is more motivating than an email report
- Be honest about limitations. PSI doesn't measure server hosting emissions; document caveats
Head to Carbon Badge, add your site's URL, and grab the embed code. Check back in a month—I bet you'll be optimizing.
Top comments (0)