· kaynak dev.to (home feed)
Next.js saha notları: Fluid Compute instance yeniden kullanımı bir isteğin state'ini başka bir isteğin trace'ine sızdırabiliyor
dev.to'daki saha notları, OpenTelemetry'nin Next.js App Router'a nasıl bağlanacağını ve modül düzeyindeki isteğe bağlı state'in Vercel Fluid Compute üzerinde eşzamanlı istekler arasında neden sızdığını anlatıyor.

Saha notları neleri kapsıyor
dev.to'daki bir saha notları yazısı, bir Next.js App Router uygulamasına dağıtılmış tracing ve merkezi hata raporlamanın nasıl bağlanacağını adım adım anlatıyor ve bunun sonucunda ortaya çıkan production hatasını belgeliyor: gerçek trafik altında, log satırları ve trace attribute'ları yanlış kullanıcının ID'sini taşıyordu, çünkü modül düzeyindeki current-user değişkeni aynı warm instance üzerinde çalışan eşzamanlı istekler tarafından paylaşılıyordu.
İstek başına hook değil, tek bir boot hook'u
Yazıya göre instrumentation.ts proje kökünde ya da src/ içinde yer alıyor ve Next.js onu uygulamanın kendi modüllerinden önce yüklüyor. register() export'u tam olarak bir kez, yeni bir server instance'ı başladığında tetikleniyor — asla istek başına, route başına ya da render başına değil. Next.js 15'ten bu yana bu davranış stabil ve deneysel bir flag gerektirmiyor; bu detay, Next.js 13 veya 14 için yazılmış kurulum snippet'lerini kopyalayan geliştiricileri yanıltıyor. Bu boot zamanı semantiği, dosyayı bir tracing SDK'yı başlatmak veya uzun ömürlü bağlantılar açmak için doğru yer, o anda uçuştaki istekle bağlantılı herhangi bir şey için ise yanlış yer haline getiriyor.
SDK'nın kendisi için yazı, @vercel/otel kullanıyor; registerOTel() çağrısı standart Node OpenTelemetry SDK'yı sarmalıyor ve exporter seçimini, span-processor yapılandırmasını ve resource attribute'larını tek bir kurulumun arkasına gizliyor. OTEL_EXPORTER_OTLP_ENDPOINT ayarlamak span'ları Honeycomb, Axiom, Datadog ya da self-hosted bir OpenTelemetry Collector gibi bir collector'a yönlendiriyor; Vercel'de ise kayıt bir kez yapıldığında platformun Observability sekmesi aynı trace verisini otomatik olarak alıyor. Tam SDK Node.js runtime gerektiriyor; yazar bunu giderek daralan bir kısıt olarak nitelendiriyor: Fluid Compute, Node.js'yi fiilen varsayılan yapıyor ve eski edge runtime zaten state'li bir SDK'ya hiç uygun değildi.
İstek state'i neden trace'ler arasına sızdı
Hata, mevcut kullanıcının ID'sini modül düzeyindeki bir değişkende saklayan küçük bir helper'dan kaynaklanıyordu; böylece bir log satırına eklemek için onu beş katman fonksiyon çağrısı boyunca taşımaya gerek kalmıyordu. Helper, next dev sürecinin nadiren çakışan istekleri işlediği local development ortamında sorunsuz davranıyordu. Production eşzamanlılığında ise istek A'nın kimliğini istek B'nin log'larına ve trace attribute'larına iliştiriyordu.
Yazıya göre neden, Fluid Compute'un temel davranışı: tek bir warm fonksiyon instance'ını canlı tutuyor ve istek başına yeni bir instance başlatmak yerine ona hizmet eden birçok eşzamanlı isteği ondan karşılıyor. Bu cold-start gecikmesine yardımcı oluyor, ancak modül düzeyindeki bir değişkeni instance üzerindeki her istek tarafından paylaşılan bir state'e dönüştürüyor ve iç içe geçen istekler birbirinin değerlerinin üzerine yazıyor.
Çözüm, node:async_hooks'tan AsyncLocalStorage: bir değeri modüle değil, mevcut async çağrı zincirine bağlıyor. Yazar, bunun OpenTelemetry'nin context manager'ının dayandığı temel primitive olduğuna dikkat çekiyor ve Next.js'in kendi headers() ve cookies() fonksiyonlarının, geliştiricilerin tüm çağrı yığını boyunca bir request nesnesi taşımalarına gerek kalmadan istek başına davranabilmesinin de aynı sebepten olduğunu söylüyor.
Tüm route'u gören hata hook'u
onRequestError, instrumentation.ts'ten opsiyonel bir export ve Next.js 15'ten bu yana stabil; Next.js bir Server Component render ederken, bir Route Handler çalıştırırken, bir Server Action yürütürken ya da Middleware içinde yakalanmamış bir hata yakaladığında — framework kendi hata yanıtını oluşturmadan önce — çalışıyor. Hata nesnesini, path, method ve headers içeren bir request nesnesini ve routerKind, routePath ile render, route, action veya middleware değerlerinden birine eşit bir routeType taşıyan bir context nesnesini alıyor. Bu route düzeyindeki context, dağınık bir try/catch kümesinin sağlayamayacağı bir detay: bir bileşen içindeki hata boundary'si yalnızca kendi subtree'sini bilir, hangi route'a ait olduğunu ya da hangi fazın başarısız olduğunu bilmez. Yazar, route handler'lar arasına serpiştirilmiş her ad-hoc console.error'ı bu tek hook'ta birleştirdi; böylece uygulamadaki başka hiçbir şey hataların nasıl raporlanacağına karar vermiyor.
Log'ları trace'lere bağlamak
Tek başına bir span ID'si, bir olay sırasında log'u okuyan kişiye pek bir şey kazandırmaz; log, onu üreten istekle aynı trace ID'sini taşımalıdır. Vercel'in Runtime Logs'u console çıktısını otomatik olarak yakalar ama ID'ler açıkça eklenmedikçe satırları trace'lerle ilişkilendirmez; yazı, aktif span context'ini @opentelemetry/api'den okuyup her kayda basan bir pino child logger'ı gösteriyor. Daha geniş bir açıdan, yazı; trace'lerin, yapılandırılmış log'ların ve metriklerin aynı isteğin farklı yüzlerini yakaladığını ve yalnızca birinin gönderilmesinin diğerlerinin dolduracağı boşluklar bıraktığını savunuyor.
Neden önemli
Serverless soyutlaması uygulama kodunun altında değişiyor: bir instance artık bir istekle eş anlamlı değil, dolayısıyla herhangi bir global, modül düzeyindeki mevcut state önbelleği ya da istek verisi tutan singleton, istekler arası bir veri sızıntısına dönüşüyor. Sonuçları dağınık log'ların ötesine geçiyor; çünkü bir kullanıcının kimliği başka bir kullanıcının trace'ine iliştirilebilir. Yazının önerisi Vercel'in ötesinde genellenebilir: register() içinde bir kez başlatın, hataları onRequestError içinde bir kez raporlayın ve istek kapsamlı her şeyi AsyncLocalStorage içinde tutun — OpenTelemetry SDK ve Next.js'in zaten依赖 ettiği mekanizmanın aynısı.
- #next-js
- #opentelemetry
- #observability
- #vercel
- #serverless