Next.js Instrumentation ja OpenTelemetry: Tuotannon observoitavuus App Routerissa (2026)

Käytännönläheinen opas Next.js Instrumentation.ts:n ja OpenTelemetryn tuotantoasennukseen App Routerissa. Sisältää @vercel/otel-konfiguraation, onRequestError-koukun ja Edge Runtime -kiertotiet staff-eng-näkökulmasta.

Next.js Instrumentation OpenTelemetry 2026

Päivitetty: 12. elokuuta 2026

Next.js Instrumentation on instrumentation.ts-tiedostoon perustuva sisäänrakennettu mekanismi, joka ajaa mielivaltaisen alustuskoodin ennen kuin ensimmäistäkään pyyntöä käsitellään. Käytännössä se on ainoa oikea paikka rekisteröidä OpenTelemetry-SDK, initialisoida Sentry tai käynnistää tietokantayhteysaltaan lämmitys. Yhdistettynä @vercel/otel-pakettiin ja onRequestError-koukkuun se antaa App Routerissa täyden jäljityksen server-komponenteista Server Actioneihin, middlewareen ja Route Handlereihin. Käyn tässä oppaassa läpi tuotantokelpoisen konfiguraation, jonka olen itse ajanut sisään monorepossa staff-eng-tason tiimissä (ja josta löytyy ne muutamat kompastuskivet, joista dokumentaatio vaikenee).

  • instrumentation.ts ajetaan kerran per Node.js- tai Edge-runtime-instanssi, ei per pyyntö. Se on rekisteröintipiste, ei middleware.
  • @vercel/otel (v1.13+) tarjoaa nollakonfiguraation OpenTelemetry-integraation kaikille App Routerin renderöintitiloille, mukaan lukien PPR ja Cache Components.
  • onRequestError-koukku (Next.js 15+) välittää palvelinvirheet strukturoidusti Sentryyn, Datadogiin tai omaan lokijärjestelmään ilman try/catch-boilerplaatteja.
  • Edge Runtime tukee OpenTelemetryä vain rajatusti. Pelkkä fetch-instrumentointi ja manuaaliset spanit toimivat, koska Node.js-natiiviperformancesta puuttuu API:ta.
  • Server Actionit jäljitetään automaattisesti spanina executeMutationAction, mutta niiden metatietoihin (form data, referrer) pitää lisätä käyttäjän oma attribuuttilogiikka.
  • OTLP-endpointin ympäristömuuttujat kannattaa asettaa next.config.ts:n serverExternalPackages-listalle, jotta Turbopack ei bundlaa OpenTelemetry-natiivimoduuleja.

Miksi Next.js instrumentation on välttämätön tuotannossa

Kun App Router siirtyi oletuksena palvelinkomponentteihin, jäljitys muuttui kertaheitolla vaikeammaksi. Yksi HTTP-pyyntö voi laukaista kymmeniä rinnakkaisia fetch-kutsuja, kolme Server Actionia, kaksi middleware-suoritusta ja useita use cache-välimuistihakuja. Ja kaikki tämä pyörii eri process-runtimeissa. Ilman keskitettyä instrumentointia jokainen kirjastototeutus (Prisma, Drizzle, Auth.js, react-query) joutuu itse patchaamaan omat kutsunsa, ja jäljityskonteksti katkeaa Server Componentin ja Client Componentin rajapinnalla.

Käytännössä olen nähnyt kolme oiretta, joita instrumentation korjaa. Ensinnäkin Vercel-lokeissa näkyy 500-virhe ilman stack tracea, koska React on jo ehtinyt flushata vastauksen suoratoistona. Toiseksi Server Action epäonnistuu, mutta client näkee vain geneerisen "Something went wrong" -viestin. Kolmanneksi välimuistihäkkyrä ei paljasta, mikä fetch-kutsu oikeasti osui verkkoon versus mikä palautui use cache-tulosjoukosta. OpenTelemetry ratkaisee kaikki kolme, koska se propagoi traceparent-header-kontekstia läpi koko renderöinnin.

Ja tässä on staff-eng-tason vinkki: älä yritä rakentaa observoitavuutta jälkikäteen kunkin palvelun sisään. Rekisteröi yksi OTLP-eksportteri instrumentation.ts:ssä, ja päädyt siihen, että Turbopack-buildisi, monorepo-jaetut kirjastosi ja jokainen React Server Component tuottaa spanit samaan taustajärjestelmään ilman ylimääräistä koodia.

Miten instrumentation.ts toimii App Routerissa

instrumentation.ts on Next.js:n konventio. Sijoita tiedosto projektin juureen (tai src/-hakemiston juureen, jos käytät sitä), vie register-funktio, ja Next.js kutsuu sitä täsmälleen kerran per Node.js- tai Edge-instanssi ennen ensimmäistä pyyntöä. Ei per request. Ei per renderöinti. Kerran per prosessi.

Vähimmäistoteutus näyttää tältä:

// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation-node');
  }
  if (process.env.NEXT_RUNTIME === 'edge') {
    await import('./instrumentation-edge');
  }
}

Dynaaminen import() ei ole tyylivalinta, vaan pakko. Jos importaat OpenTelemetry-Node-SDK:n staattisesti moduulin ylätasolla, Turbopack yrittää bundlata sen myös Edge Runtimeen ja saat rakennusvirheen Module not found: Can't resolve 'perf_hooks'. Runtime-tarkistus varmistaa, että vain oikea toteutus latautuu. Törmäsin tähän itse ensimmäisessä produktioviikossa, joten säästä itseltäsi tunti debuggausta.

Toinen olennainen asia. register-funktio saa palauttaa Promise:n, ja Next.js odottaa sen ratkeamista ennen kuin ensimmäinen pyyntö menee läpi. Tämä on ainoa hetki, jolloin sinulla on synkroninen, deterministinen alustusikkuna. Käytä sitä hyväksi: rekisteröi tracer provider, aloita OpenTelemetry SDK, lämmitä connection pool, lataa feature-flag-cache. Kaikki tapahtuu ennen liikennettä.

OpenTelemetry-integraatio askel askeleelta

Suositelluin tapa vuonna 2026 on käyttää Vercelin virallista @vercel/otel-pakettia, joka kääntää raaka-OpenTelemetryn Next.js-konventioihin. Se konfiguroi automaattisesti fetch-instrumentoinnin, propagoi traceparent-headeria, ja lisää spanit jokaiselle App Routerin renderöintivaiheelle. Katso yksityiskohdat Next.js:n virallisesta OpenTelemetry-oppaasta.

Asenna riippuvuudet:

npm install @vercel/otel @opentelemetry/api \
  @opentelemetry/sdk-logs @opentelemetry/api-logs \
  @opentelemetry/instrumentation

Node-runtimen konfiguraatio:

// instrumentation-node.ts
import { registerOTel } from '@vercel/otel';

export function register() {
  registerOTel({
    serviceName: 'launchpad-web',
    // OTLP-endpoint luetaan OTEL_EXPORTER_OTLP_ENDPOINT-ympäristömuuttujasta
    // headerit OTEL_EXPORTER_OTLP_HEADERS:sta
    instrumentations: [
      // Custom instrumentaatiot voi lisätä tähän, esim. @opentelemetry/instrumentation-pg
    ],
    attributes: {
      'deployment.environment': process.env.VERCEL_ENV ?? 'development',
      'service.version': process.env.VERCEL_GIT_COMMIT_SHA ?? 'local',
    },
  });
}

Ja next.config.ts:n puolella pitää estää natiivimoduulien bundlaus:

// next.config.ts
import type { NextConfig } from 'next';

const config: NextConfig = {
  serverExternalPackages: [
    '@opentelemetry/sdk-node',
    '@opentelemetry/auto-instrumentations-node',
    'require-in-the-middle',
  ],
};

export default config;

Jos jätät serverExternalPackages-listan pois, Turbopack yrittää staattisesti analysoida require-in-the-middle:a (kirjastoa, joka koukkaa Node.js:n require-funktion) ja koko build kaatuu. Tämä on yleisin syy, miksi ihmiset raportoivat "OpenTelemetry ei toimi Next.js 15/16:ssa". Honestly, tästä olisi voinut säästyä puolen päivän issue-linjauksen, jos dokumentaatio olisi maininnut sen etusivulla.

onRequestError-koukku ja virheiden jäljitys

Next.js 15 esitteli onRequestError-koukun, joka on vastaus vanhaan ongelmaan. Server-komponentin virhe ei koskaan päädy client-puolen error boundaryyn samassa muodossa, ja perinteinen Express-tyylinen error middleware ei toimi RSC-mallissa. Koukku ajetaan aina, kun renderöinti heittää poikkeuksen. Server Componentissa, Server Actionissa, Route Handlerissa tai middlewaressa.

// instrumentation.ts
export async function register() {
  // ...aiempi register-logiikka
}

export const onRequestError: import('next/server').OnRequestError = async (
  error,
  request,
  context
) => {
  // Rakenna strukturoitu virhelokitietue
  const payload = {
    message: error instanceof Error ? error.message : String(error),
    stack: error instanceof Error ? error.stack : undefined,
    digest: (error as { digest?: string }).digest,
    path: request.path,
    method: request.method,
    routerKind: context.routerKind, // 'App Router' | 'Pages Router'
    routePath: context.routePath,
    routeType: context.routeType,   // 'render' | 'route' | 'action' | 'middleware'
    revalidateReason: context.revalidateReason,
    renderSource: context.renderSource,
  };

  // Lähetä eteenpäin OTLP-lokina, Sentryyn tai omaan pipeline-järjestelmään
  await fetch(process.env.LOG_INGEST_URL!, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
    // Vercel-serverless: keepalive varmistaa, että request lähtee vaikka function suljetaan
    keepalive: true,
  });
};

Kolme asiaa, jotka on helppo unohtaa. Ensinnäkin digest-kenttä on ainoa vakaa tunniste, joka näkyy myös client-puolen error boundaryssa. Kirjaa se aina, jotta support voi yhdistää käyttäjän raportin oikeaan lokiriviin. Toiseksi routeType: 'action' tarkoittaa Server Actionia, ja näiden virheet on usein liiketoimintakriittisiä ja ansaitsevat oman hälytyskynnyksen. Kolmanneksi keepalive: true on välttämätön Vercelin serverlessissä, muuten function-instanssi voi sammua ennen kuin logi ehtii lähteä. Menetin viime projektissa noin 2 % virhelogeista ennen kuin lisäsin tuon flagin.

Server Actionien ja middlewaren jäljitys

Server Actions on käytännössä RPC. Client kutsuu server-funktiota multipart-POST:in kautta, ja OpenTelemetry näkee tämän automaattisesti spanina nimeltä executeMutationAction. Oletusattribuutit ovat kuitenkin niukat, käytännössä pelkkä actionin ID-hash. Jotta jäljitteet ovat käyttökelpoisia tuotannossa, kannattaa rikastaa spania manuaalisesti:

// app/actions/create-invoice.ts
'use server';

import { trace } from '@opentelemetry/api';

export async function createInvoice(formData: FormData) {
  const span = trace.getActiveSpan();
  span?.setAttributes({
    'app.action': 'createInvoice',
    'app.customer_id': formData.get('customerId')?.toString() ?? 'unknown',
    'app.amount_cents': Number(formData.get('amountCents') ?? 0),
  });

  // ... liiketoimintalogiikka
}

Middlewaren jäljitys on hankalampi tapaus. Middleware ajetaan aina Edge Runtimessa, jossa OpenTelemetry Node SDK ei toimi. Käytännössä ratkaisu on kaksitasoinen. Ensin middleware asettaa traceparent-headerin manuaalisesti pyyntöön, jotta downstream Node.js-renderöinnit saavat oikean parent context -kontekstin. Sen jälkeen middlewaren omat metriikat (esim. autentikointitarkistuksen latenssi) lähetetään suoraan OTLP HTTP -endpointille fetch:n avulla.

Jos rakennat middlewareen esimerkiksi rate-limitin tai autentikointikerroksen kuten olen käsitellyt Next.js Proxy -oppaassa, kannattaa lisätä jokaiseen päätökseen (allow, deny, redirect) oma metriikka. Muuten hylätyt pyynnöt eivät koskaan päädy Node.js-runtimen jäljitteisiin ja jäät sokeaksi.

Datadog, Sentry ja Vercelin oma observoitavuus

OTLP-standardi tarkoittaa, että sama instrumentation.ts voi lähettää dataa useaan taustajärjestelmään ilman koodimuutoksia. Vain ympäristömuuttujat vaihtuvat. Alla vertailu vaihtoehdoista, joita olen käyttänyt tuotannossa:

OminaisuusVercel ObservabilityDatadogSentry
OTLP-tukiNatiivi, ei konfiguraatiotaOTLP HTTP + AgentOTLP + oma SDK
Server Actions -spanitAutomaattinenVaatii @vercel/otel:nSentry SDK v8+ automaattinen
VirhejäljitysVercel Log DrainsAPM Error TrackingEnsiluokkainen, source maps
Hinta (2026, arvio)Sisältyy Pro-tasoon$31/host/kk$26/kk 100k errors
Edge RuntimeTäysi tukiRajattu (fetch-only)Täysi tuki v8+
Setup-aika~5 min~30 min (Agent-asennus)~15 min
Ihanteellinen käyttötapausVercel-only stackMulti-cloud, k8sFrontend + backend virheet

Käytännön suositus staff-eng-tiimille: aloita Vercel Observabilityllä, koska se ei vaadi omaa infra-tiimiä. Kun liikenne kasvaa ja tarvitset custom dashboardeja, siirry Datadogiin tai Grafana Cloudiin. Sentry on erikoistapaus. Se on ylivoimainen virhejäljityksessä, mutta ei kilpaile täysimittaisen APM:n kanssa. Yhdistelmä Sentry (virheet) + Datadog (metriikat/spanit) on tavallinen tuotantosetup, ja olen itsekin ajanut sen jo kahdessa työpaikassa.

Jos käytät Vercelin natiivialustaa, katso Vercel OpenTelemetry -dokumentaatio. Sieltä löytyvät valmiit konfiguraatiot yleisimmille taustajärjestelmille.

Edge Runtime -rajoitteet ja kiertotiet

Edge Runtime on rajattu V8-isolaatti, joka ei tarjoa perf_hooks-, fs- tai diagnostics_channel-API:ta. Käytännössä tämä tarkoittaa, että OpenTelemetryn Node SDK ei käynnisty ollenkaan Edge-ympäristössä. Jos yrität importata sen, saat build-virheen. Middleware, Edge Route Handlerit ja Edge Server Componentit vaativat kevyemmän lähestymistavan.

Käytännössä toimivin ratkaisu on @opentelemetry/api-paketti (pelkkä API, ei SDK:ta) yhdistettynä omaan OTLP HTTP -eksportteriin:

// instrumentation-edge.ts
import { trace, context, SpanKind } from '@opentelemetry/api';

// Kevyt manuaalinen tracer, joka lähettää OTLP HTTP -formaatissa
export function register() {
  // Minimaalinen tracer-toteutus, vain fetch-instrumentaatio ja manuaaliset spanit
  // ...
}

Vaihtoehtoisesti, jos observoitavuus ei ole kriittistä middlewaressa, voit hyväksyä sokean pisteen ja luottaa siihen, että onRequestError-koukku ottaa kiinni virheet myös Edge-runtimessa. OpenTelemetryn spesifikaatio kuvaa vähimmäisvaatimukset manuaaliselle tracer-toteutukselle.

Tuotannon checklist ja yleiset virheet

Kun otin instrumentation-järjestelmän tuotantoon monorepossa, jossa on kolme Next.js-appia ja jaettu design-system-paketti, törmäsin kolmeen ei-ilmeiseen ongelmaan. Näiden ratkaisut kannattaa tarkistaa ennen deployta:

  1. Duplikaatit spanit Vercelillä. Jos rekisteröit sekä @vercel/otel:n että Vercelin sisäisen instrumentaation, saat jokaisesta fetch-kutsusta kaksi spania. Ratkaisu: aseta propagators: []-optio registerOTel-kutsuun, jos Vercelin oma propagaatio on käytössä.
  2. Monorepon jaetut kirjastot menettävät kontekstin. Jos design-system-paketti tekee omia fetch-kutsuja (esim. analytiikkaa varten), ne eivät automaattisesti liity vanhemman spanin kontekstiin, ellei paketti käytä @opentelemetry/api:a itsenäisesti. Ratkaisu: propagoi context eksplisiittisesti tai käytä context.with()-wrapperia.
  3. Turbopack ja instrumentation.ts hot reload. Kehityksessä instrumentation.ts saatetaan ajaa useasti hot reloadin yhteydessä, mikä johtaa moninkertaisiin tracer provider -rekisteröinteihin ja muistivuotoihin dev-serverillä. Suojaa singleton-tarkistuksella: if (globalThis.__otelRegistered) return; globalThis.__otelRegistered = true;.

Kun observoitavuus on paikoillaan, saat myös hyvän lähtökohdan välimuistin analysointiin. Jokainen use cache-hit näkyy omana spaninaan, ja voit vertailla latenssia välimuistin osumien ja miss-tapausten välillä. Jos vielä opettelet direktiivin toimintaa, katso oppaani Next.js 16 Cache Components ja use cache -direktiivi.

Usein kysytyt kysymykset

Toimiiko OpenTelemetry Next.js 16:n kanssa suoraan ilman lisäasennuksia?

Ei kokonaan. Next.js 16 tunnistaa instrumentation.ts-tiedoston ja ajaa register-funktion automaattisesti, mutta OpenTelemetry SDK:n asennus ja konfigurointi on tehtävä itse tai @vercel/otel-paketin avulla. Vercel-hosting lisää lisäksi omat automaattiset spanit ilman erillistä setupia.

Voiko Next.js instrumentation.ts:ää käyttää Sentryn kanssa?

Kyllä. Sentry SDK v8+ tarjoaa @sentry/nextjs-paketin, joka tekee automaattisen integraation instrumentation.ts:n kautta. Alusta Sentry register-funktiossa Node-runtimelle ja käytä onRequestError-koukkua kutsumaan Sentry.captureRequestError.

Miksi instrumentation.ts ei toimi Edge Runtimessa?

Se toimii, mutta rajatusti. Edge Runtime on V8-isolaatti ilman Node.js-natiivimoduuleja, joten OpenTelemetryn Node SDK ei käynnisty. Käytä pelkkää @opentelemetry/api-pakettia ja lähetä spanit manuaalisesti OTLP HTTP -endpointille, tai vaihda middleware Node.js-runtimeen (v15.3+).

Miten jäljitän Server Actionin epäonnistumisen tuotannossa?

Rekisteröi onRequestError-koukku, joka tarkistaa context.routeType === 'action'. Kirjaa myös error.digest-arvo, sillä se on ainoa tunniste, jonka client näkee, ja sen avulla voit yhdistää käyttäjän ilmoituksen palvelinlokiin.

Kannattaako käyttää @vercel/otel vai raaka OpenTelemetry SDK?

Käytä @vercel/otel:iä oletuksena, koska se abstraktoi Next.js:n renderöintielinkaaren ja lisää oikeat propagaattorit. Siirry raa'an OpenTelemetry SDK:n käyttöön vasta jos tarvitset custom-samplereja, exotic-eksporttereja tai integroit ei-Vercel-hostattuun infrastruktuuriin, jossa haluat täyden hallinnan.

Mei-Lin Wu
Tietoa Kirjoittajasta Mei-Lin Wu

Front-end architect at a SaaS. Owns the build system, the design system, and the war stories about both.