Measure the carbon footprint of React component rendering, data fetching, and user interactions.
In a React application, the functional unit is a user-facing operation:
| Functional Unit | Example | Why It Matters |
|---|---|---|
| Component render | <Dashboard /> mount |
Repeated on every page visit |
| Data fetch + transform | useQuery → parse → display |
Happens on every API call |
| User interaction | Form submit, filter, sort | Triggered per user action |
| SSR page generation | Next.js getServerSideProps |
Runs on every request server-side |
| Static build | Next.js getStaticProps |
Runs once per build, scales with pages |
Don't profile: individual useState calls, React internals, or trivial DOM updates. Focus on operations the user waits for.
Create a reusable hook for profiling any async operation in your components:
// hooks/useSciProfile.ts
import { profileTool, printResult } from 'sci-profiler/src/sciProfiler';
import type { ProfileResult } from 'sci-profiler/src/sciProfiler';
import { useCallback, useRef } from 'react';
export function useSciProfile() {
const results = useRef<ProfileResult[]>([]);
const profile = useCallback(async <T,>(
name: string,
operation: () => Promise<T>,
inputBytes?: number,
measureOutput?: (result: T) => number,
): Promise<T> => {
const result = await profileTool(name, async () => {
return operation();
}, inputBytes, measureOutput);
printResult(result);
results.current.push(result);
// Return the original operation result (unwrap from profiling)
return operation();
}, []);
return { profile, results: results.current };
}// components/UserList.tsx
import { useEffect, useState } from 'react';
import { profileTool, toJsonLine } from 'sci-profiler/src/sciProfiler';
export function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
(async () => {
const result = await profileTool(
'fetch-users',
async () => {
const res = await fetch('/api/users');
return res.json();
},
0,
(data) => JSON.stringify(data).length,
);
setUsers(result); // profileTool returns the operation result
// Send SCI data to your analytics
if (process.env.NODE_ENV === 'development') {
console.log(JSON.stringify(toJsonLine(result)));
}
})();
}, []);
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}Measure how much carbon a heavy render costs:
import { profileTool, printResult } from 'sci-profiler/src/sciProfiler';
async function measureRender() {
const result = await profileTool(
'dashboard-render',
async () => {
const { renderToString } = await import('react-dom/server');
const html = renderToString(<Dashboard data={bigDataset} />);
return html;
},
JSON.stringify(bigDataset).length,
(html) => html.length,
);
printResult(result);
}Profile getServerSideProps to measure server-side carbon cost:
// pages/dashboard.tsx
import { profileTool, toJsonLine } from 'sci-profiler/src/sciProfiler';
import { appendFileSync } from 'fs';
export async function getServerSideProps(context) {
const result = await profileTool(
'ssr-dashboard',
async () => {
const data = await fetchDashboardData(context.params.id);
return { props: { data } };
},
);
// Append to JSONL log (same format as sci-profiler-php)
appendFileSync('/tmp/sci-profiler/sci-profiler.jsonl',
JSON.stringify(toJsonLine(result)) + '\n'
);
// profileTool returns the operation result
return { props: { data: result } };
}Profile build-time page generation:
// pages/posts/[slug].tsx
import { profileTool, generateJsonLines } from 'sci-profiler/src/sciProfiler';
const buildResults = [];
export async function getStaticProps({ params }) {
const result = await profileTool(
`ssg-post-${params.slug}`,
async () => {
const post = await getPostBySlug(params.slug);
const html = await markdownToHtml(post.content);
return { props: { post: { ...post, html } } };
},
);
buildResults.push(result);
return result; // profileTool returns the operation result
}
// In a build script, after all pages are generated:
// console.log(generateJsonLines(buildResults));// lib/sci-config.ts — import once at app entry point
import { configureSci } from 'sci-profiler/src/sciProfiler';
// Only configure in development/staging
if (process.env.NODE_ENV !== 'production') {
configureSci({
devicePowerW: 15,
machine: process.env.SCI_PROFILER_MACHINE || 'Dev MacBook Air M2',
});
}Or via environment variables in .env.local:
SCI_PROFILER_DEVICE_POWER_W=15
SCI_PROFILER_CARBON_INTENSITY=50
SCI_PROFILER_MACHINE="Dev MacBook Air M2, 8GB"# Extract SCI scores from dev console output
grep '^{' /tmp/sci-profiler/sci-profiler.jsonl | \
jq -r '[.tool, .["sci.sci_mgco2eq"]] | @tsv' | \
sort -t$'\t' -k2 -rn | head -10# SSR pages (per-request cost)
grep '^{' sci-results.jsonl | \
jq 'select(.tool | startswith("ssr-"))' | \
jq -s '{ssr_total: [.[]["sci.sci_mgco2eq"]] | add, ssr_count: length}'
# SSG pages (one-time build cost)
grep '^{' sci-results.jsonl | \
jq 'select(.tool | startswith("ssg-"))' | \
jq -s '{ssg_total: [.[]["sci.sci_mgco2eq"]] | add, ssg_count: length}'# Average SCI for fetch operations
grep '^{' sci-results.jsonl | \
jq 'select(.tool | startswith("fetch-"))' | \
jq -s '{avg_sci: ([.[]["sci.sci_mgco2eq"]] | add / length), avg_time_ms: ([.[]["time.wall_time_ms"]] | add / length)}'| Pattern | Problem | Fix |
|---|---|---|
| Large bundle re-render | High SCI on dashboard-render |
Code split, lazy load heavy components |
| Redundant fetches | Multiple fetch-* for same data |
Add caching layer (SWR, React Query) |
| SSR for static content | High per-request ssr-* cost |
Switch to SSG (getStaticProps) |
| Unoptimized images | Large io.output_bytes on render |
Use next/image, WebP, lazy loading |
| Client-side JSON parsing | High SCI on data transform | Move transforms to API, return minimal payload |
#!/bin/bash
# ci/carbon-budget.sh — fail if SCI exceeds budget
MAX_SCI_PER_RENDER=100 # mgCO₂eq budget per render
npm run build 2>/dev/null
npx tsx benchmark/render-test.ts 2>/dev/null | grep '^{' > results.jsonl
WORST=$(cat results.jsonl | jq -s '[.[]["sci.sci_mgco2eq"]] | max')
echo "Worst render SCI: ${WORST} mgCO₂eq (budget: ${MAX_SCI_PER_RENDER})"
if (( $(echo "$WORST > $MAX_SCI_PER_RENDER" | bc -l) )); then
echo "FAIL: Carbon budget exceeded"
exit 1
fi