Introducing integrated observability in SvelteKit
Understanding how your SvelteKit application behaves in production — from request flows to performance bottlenecks — is crucial for building reliable user experiences. SvelteKit now has first-class support for observability: built-in OpenTelemetry (https://opentelemetry.io/) tracing, and a dedicated instrumentation setup file that ensures your monitoring tools work seamlessly.
To opt in, upgrade SvelteKit and your adapter and add the following to your svelte.config.js:
svelte.configexport default { kit: { experimental: { tracing: { server: boolean; }; instrumentation: { server: boolean; }; }; }kit: { experimental: { tracing: { server: boolean; }; instrumentation: { server: boolean; }; }experimental: { tracing: { server: boolean; }tracing: { server: booleanserver: true }, instrumentation: { server: boolean; }instrumentation: { server: booleanserver: true } } } };First-party OpenTelemetry traces (#First-party-OpenTelemetry-traces)SvelteKit can now emit OpenTelemetry (https://opentelemetry.io) traces for the following:
• handle hook (handle functions running in a sequence will show up as children of each other and the root handle hook)
• load functions (includes universal load functions when they run on the server)
• Form actions (/docs/kit/form-actions)
• Remote functions (/docs/kit/remote-functions)
The emitted spans include attributes describing the current request, such as http.route, and surrounding context, such as the +page or +layout file associated with a load function. If there are additional attributes you think might be useful, please file an issue on the SvelteKit GitHub issue tracker (https://github.com/sveltejs/kit/issues).
A convenient home for all of your instrumentation (#A-convenient-home-for-all-of-your-instrumentation)Emitting traces alone is not enough: You also need to collect them and send them somewhere. Under normal circumstances, this can be a bit challenging. Because of the nature of observability instrumentation, it needs to be loaded prior to loading any of the code from your app. To aid in this, SvelteKit now supports a src/instrumentation.server.ts file which, assuming your adapter supports it, is guaranteed to be loaded prior to your application code.
In Node, your instrumentation might look something like this:
import { import NodeSDKNodeSDK } from ‘@opentelemetry/sdk-node’; import { import getNodeAutoInstrumentationsgetNodeAutoInstrumentations } from ‘@opentelemetry/auto-instrumentations-node’; import { import OTLPTraceExporterOTLPTraceExporter } from ‘@opentelemetry/exporter-trace-otlp-proto’; import { import createAddHookMessageChannelcreateAddHookMessageChannel } from ‘import-in-the-middle’; import { function register<Data = any>(specifier: string | URL, parentURL?: string | URL, options?: Module.RegisterOptions<Data>): void (+1 overload)Register a module that exports hooks that customize Node.js module resolution and loading behavior. See Customization hooks (https://nodejs.org/docs/latest-v22.x/api/module.html#customization-hooks).
This feature requires –allow-worker if used with the Permission Model (https://nodejs.org/docs/latest-v22.x/api/permissions.html#permission-model).
@sincev20.6.0, v18.19.0@paramspecifier Customization hooks to be registered; this should be the same string that would be passed to import(), except that if it is relative, it is resolved relative to parentURL.@paramparentURL f you want to resolve specifier relative to a base URL, such as import.meta.url, you can pass that URL here.register } from ‘module’;
const { const registerOptions: anyregisterOptions } = import createAddHookMessageChannelcreateAddHookMessageChannel();
register
This feature requires –allow-worker if used with the Permission Model (https://nodejs.org/docs/latest-v22.x/api/permissions.html#permission-model).
@sincev20.6.0, v18.19.0@paramspecifier Customization hooks to be registered; this should be the same string that would be passed to import(), except that if it is relative, it is resolved relative to parentURL.@paramparentURL f you want to resolve specifier relative to a base URL, such as import.meta.url, you can pass that URL here.register(‘import-in-the-middle/hook.mjs’, import.meta.ImportMeta.url: stringThe absolute file: URL of the module.
This is defined exactly the same as it is in browsers providing the URL of the current module file.
This enables useful patterns such as relative file loading:
import { readFileSync } from ‘node:fs’; const buffer = readFileSync(new URL(’./data.proto’, import.meta.url));url, const registerOptions: anyregisterOptions);
const const sdk: anysdk = new import NodeSDKNodeSDK({ serviceName: stringserviceName: ‘my-sveltekit-app’, traceExporter: anytraceExporter: new import OTLPTraceExporterOTLPTraceExporter(), instrumentations: any[]instrumentations: [import getNodeAutoInstrumentationsgetNodeAutoInstrumentations()] });
const sdk: anysdk.start();If you’re deploying to Vercel, it would look something like this:
import { import registerOTelregisterOTel } from ‘@vercel/otel’;
import registerOTelregisterOTel({ serviceName: stringserviceName: ‘my-sveltekit-app’ });Consult your platform’s documentation for specific instrumentation instructions. As of now, all of the official SvelteKit adapters with a server component (sorry, adapter-static) support instrumentation.server.ts.
Acknowledgements (#Acknowledgements)A huge thank-you to Lukas Stracke, who kicked us off on this adventure with his excellent talk at Svelte Summit 2025 (https://www.youtube.com/watch?v=hFVmFAyB_YA) and his initial draft PR for instrumentation.server.ts. Another thank-you to Sentry (https://sentry.io/welcome/) for allowing him to spend his working hours reviewing and testing our work.
Write a comment