Suivre les émissions carbone des LLMs dans Next.js — guide pas à pas
Instrumentez votre application Next.js pour mesurer et journaliser l'empreinte CO₂ de chaque appel API OpenAI, Anthropic ou Mistral avec carbon-llm. Aucun prompt collecté, prêt pour la déclaration ESRS E1.
La Corporate Sustainability Reporting Directive (CSRD) de l'UE et sa norme ESRS E1 imposent aux entreprises de déclarer leurs émissions de gaz à effet de serre Scope 3 — incluant les services numériques achetés. L'inférence LLM relève du Scope 3 Catégorie 1 (biens et services achetés) ou de la Catégorie 11 (utilisation des produits vendus) selon votre modèle économique.
Next.js est le framework dominant pour les applications IA en production : server actions, routes API et endpoints de streaming appellent tous des fournisseurs LLM. L'instrumentation appartient à cette couche serveur — pas au client — car le client ne voit jamais les comptes de tokens.
carbon-llm est conçu précisément pour ce cas d'usage : un seul POST par appel LLM, aucune donnée de prompt, retour immédiat, et appels en fire-and-forget qui n'ajoutent jamais de latence à vos réponses.
Ajoutez votre clé API à .env.local. Next.js n'expose pas au navigateur les variables sans le préfixe NEXT_PUBLIC_, votre clé est donc sécurisée.
.env.local
CARBON_LLM_API_KEY=isv_live_xxxxxxxxxxxxInstrumentez une Server Action Next.js qui appelle OpenAI. L'appel fetch vers /v1/track est en fire-and-forget après la réponse, il ne bloque donc jamais l'utilisateur :
app/actions/chat.ts
"use server"
import OpenAI from "openai"
const openai = new OpenAI()
const CARBON_API = "https://carbon-llm.com/api/v1/track"
export async function chat(prompt: string) {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: prompt }],
})
// Fire-and-forget — jamais awaited, ne bloque jamais l'utilisateur
fetch(CARBON_API, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.CARBON_LLM_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
tenant_id: "default",
model: response.model,
prompt_tokens: response.usage?.prompt_tokens ?? 0,
completion_tokens: response.usage?.completion_tokens ?? 0,
}),
}).catch(() => {}) // silencieux en cas d'erreur réseau
return response.choices[0].message.content
}Si vous utilisez le Pages Router ou avez besoin d'un endpoint en Edge runtime, le pattern est identique — appelez /v1/track après avoir collecté l'objet usage :
pages/api/chat.ts
import type { NextApiRequest, NextApiResponse } from "next"
import Anthropic from "@anthropic-ai/sdk"
const anthropic = new Anthropic()
const CARBON_API = "https://carbon-llm.com/api/v1/track"
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { prompt } = req.body as { prompt: string }
const message = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
})
fetch(CARBON_API, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.CARBON_LLM_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
tenant_id: "default",
model: message.model,
prompt_tokens: message.usage.input_tokens,
completion_tokens: message.usage.output_tokens,
}),
}).catch(() => {})
res.json({ text: (message.content[0] as { text: string }).text })
}Avec le streaming, les comptes de tokens ne sont disponibles qu'une fois le flux terminé. Accumulez-les et appelez /v1/track à la fermeture du stream :
app/api/chat/route.ts — streaming
import { openai } from "@ai-sdk/openai"
import { streamText } from "ai"
const CARBON_API = "https://carbon-llm.com/api/v1/track"
export const runtime = "edge"
export async function POST(req: Request) {
const { messages } = await req.json()
const result = streamText({
model: openai("gpt-4o"),
messages,
onFinish({ usage }) {
// Appelé une fois le flux terminé — safe pour /v1/track
fetch(CARBON_API, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.CARBON_LLM_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
tenant_id: "default",
model: "gpt-4o",
prompt_tokens: usage.promptTokens,
completion_tokens: usage.completionTokens,
}),
}).catch(() => {})
},
})
return result.toDataStreamResponse()
}Si vous construisez un SaaS et avez besoin d'une ventilation des émissions par client (obligatoire pour les rapports CSRD multi-clients), passez un tenant_id :
Suivi par tenant
body: JSON.stringify({
tenant_id: session.organizationId, // votre identifiant client / organisation
model: response.model,
prompt_tokens: response.usage?.prompt_tokens ?? 0,
completion_tokens: response.usage?.completion_tokens ?? 0,
})Après l'instrumentation, ouvrez le tableau de bord carbon-llm et déclenchez un véritable appel LLM depuis votre application. Vous devriez voir l'évènement apparaître dans le flux Évènements en quelques secondes.
Une fois les données accumulées, allez dans Rapports → Générer un rapport ESRS E1. Choisissez une plage de dates, sélectionnez votre ou vos tenants, et exportez en PDF ou JSON. Le rapport PDF inclut les sources des facteurs d'émission, la méthodologie et l'attribution Scope 3 requises par ESRS E1.
Prêt à démarrer le suivi ?
Gratuit jusqu'à 100 000 évènements/mois, phase d'accès anticipé.