From f83949187f330002de3a64a27def5b1cf3fc3070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 28 Mar 2026 10:43:08 +0100 Subject: [PATCH 1/8] feat: inject traceparent meta tag when OpenTelemetry is active When an OTel exporter is configured, Fresh now automatically adds a tag to the of rendered HTML pages. This enables client-side OTel instrumentation (e.g., @opentelemetry/instrumentation-document-load) to connect browser traces to the server-side span that rendered the page. The tag follows the W3C Trace Context format: Only injected when a valid span context exists (real OTel exporter configured), so there is zero overhead when OTel is not in use. Closes #2830 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../fresh/src/runtime/server/preact_hooks.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/fresh/src/runtime/server/preact_hooks.ts b/packages/fresh/src/runtime/server/preact_hooks.ts index 320439bb97e..95226e2f54e 100644 --- a/packages/fresh/src/runtime/server/preact_hooks.ts +++ b/packages/fresh/src/runtime/server/preact_hooks.ts @@ -35,6 +35,7 @@ import { getCodeFrame } from "../../dev/middlewares/error_overlay/code_frame.ts" import { escapeScript } from "../../utils.ts"; import { HeadContext } from "../head.ts"; import { useContext } from "preact/hooks"; +import { isSpanContextValid, trace } from "@opentelemetry/api"; interface InternalPreactOptions extends PreactOptions { [OptionsType.ATTR](name: string, value: unknown): string | void; @@ -282,6 +283,24 @@ options[OptionsType.DIFF] = (vnode) => { } } + // Inject W3C traceparent meta tag when OpenTelemetry is active, + // enabling client-side tracing to connect to the server span. + const activeSpan = trace.getActiveSpan(); + if (activeSpan) { + const spanCtx = activeSpan.spanContext(); + if (isSpanContextValid(spanCtx)) { + const flags = (spanCtx.traceFlags & 1) ? "01" : "00"; + items.push( + // deno-lint-ignore no-explicit-any + h("meta", { + name: "traceparent", + content: + `00-${spanCtx.traceId}-${spanCtx.spanId}-${flags}`, + } as any), + ); + } + } + // deno-lint-ignore no-explicit-any items.push(h(RemainingHead, null) as VNode); From ab88649535f6da75bb792fdcac80e5b1cb596b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 29 Mar 2026 19:33:03 +0200 Subject: [PATCH 2/8] fmt --- packages/fresh/src/runtime/server/preact_hooks.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/fresh/src/runtime/server/preact_hooks.ts b/packages/fresh/src/runtime/server/preact_hooks.ts index 95226e2f54e..e724c0b95fd 100644 --- a/packages/fresh/src/runtime/server/preact_hooks.ts +++ b/packages/fresh/src/runtime/server/preact_hooks.ts @@ -294,8 +294,7 @@ options[OptionsType.DIFF] = (vnode) => { // deno-lint-ignore no-explicit-any h("meta", { name: "traceparent", - content: - `00-${spanCtx.traceId}-${spanCtx.spanId}-${flags}`, + content: `00-${spanCtx.traceId}-${spanCtx.spanId}-${flags}`, } as any), ); } From dbfc15d56c54f6c77e9464059abea28b57c94266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 29 Mar 2026 20:27:37 +0200 Subject: [PATCH 3/8] fix: resolve lint errors and add traceparent docs Move `as any` cast to a single line so the deno-lint-ignore directive covers it, fixing ban-unused-ignore and no-explicit-any errors on CI. Also document the new traceparent meta tag injection in the OpenTelemetry docs page. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/opentelemetry.md | 18 ++++++++++++++++++ .../fresh/src/runtime/server/preact_hooks.ts | 11 ++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/latest/advanced/opentelemetry.md b/docs/latest/advanced/opentelemetry.md index 49ef0becde5..f979fb1678a 100644 --- a/docs/latest/advanced/opentelemetry.md +++ b/docs/latest/advanced/opentelemetry.md @@ -127,3 +127,21 @@ deno task start Open `http://localhost:16686` to browse traces. You'll see each request broken down into its middleware, handler, and rendering spans. + +## Client-side trace correlation + +When an OpenTelemetry exporter is active, Fresh automatically injects a +[W3C Trace Context](https://www.w3.org/TR/trace-context/) `` tag into the +`` of every rendered page: + +```html + + + + +``` + +This allows client-side OpenTelemetry instrumentation (such as +[`@opentelemetry/instrumentation-document-load`](https://www.npmjs.com/package/@opentelemetry/instrumentation-document-load)) +to link browser performance traces back to the server-side span that rendered the +page, giving you end-to-end visibility from server rendering through page load. diff --git a/packages/fresh/src/runtime/server/preact_hooks.ts b/packages/fresh/src/runtime/server/preact_hooks.ts index e724c0b95fd..cf335d0ec13 100644 --- a/packages/fresh/src/runtime/server/preact_hooks.ts +++ b/packages/fresh/src/runtime/server/preact_hooks.ts @@ -290,13 +290,10 @@ options[OptionsType.DIFF] = (vnode) => { const spanCtx = activeSpan.spanContext(); if (isSpanContextValid(spanCtx)) { const flags = (spanCtx.traceFlags & 1) ? "01" : "00"; - items.push( - // deno-lint-ignore no-explicit-any - h("meta", { - name: "traceparent", - content: `00-${spanCtx.traceId}-${spanCtx.spanId}-${flags}`, - } as any), - ); + const traceparent = + `00-${spanCtx.traceId}-${spanCtx.spanId}-${flags}`; + // deno-lint-ignore no-explicit-any + items.push(h("meta", { name: "traceparent", content: traceparent }) as any); } } From dc27e6d9f2b7d89e165de1face40f8eba521327b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 29 Mar 2026 20:28:25 +0200 Subject: [PATCH 4/8] fmt --- docs/latest/advanced/opentelemetry.md | 10 +++++++--- packages/fresh/src/runtime/server/preact_hooks.ts | 4 +++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/latest/advanced/opentelemetry.md b/docs/latest/advanced/opentelemetry.md index f979fb1678a..eee70c88093 100644 --- a/docs/latest/advanced/opentelemetry.md +++ b/docs/latest/advanced/opentelemetry.md @@ -136,12 +136,16 @@ When an OpenTelemetry exporter is active, Fresh automatically injects a ```html - + ``` This allows client-side OpenTelemetry instrumentation (such as [`@opentelemetry/instrumentation-document-load`](https://www.npmjs.com/package/@opentelemetry/instrumentation-document-load)) -to link browser performance traces back to the server-side span that rendered the -page, giving you end-to-end visibility from server rendering through page load. +to link browser performance traces back to the server-side span that rendered +the page, giving you end-to-end visibility from server rendering through page +load. diff --git a/packages/fresh/src/runtime/server/preact_hooks.ts b/packages/fresh/src/runtime/server/preact_hooks.ts index cf335d0ec13..201ff384970 100644 --- a/packages/fresh/src/runtime/server/preact_hooks.ts +++ b/packages/fresh/src/runtime/server/preact_hooks.ts @@ -293,7 +293,9 @@ options[OptionsType.DIFF] = (vnode) => { const traceparent = `00-${spanCtx.traceId}-${spanCtx.spanId}-${flags}`; // deno-lint-ignore no-explicit-any - items.push(h("meta", { name: "traceparent", content: traceparent }) as any); + items.push( + h("meta", { name: "traceparent", content: traceparent }) as any, + ); } } From 77fbac066fb4c0e8d4c3f1c9eb5b10d312d21c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 29 Mar 2026 20:34:47 +0200 Subject: [PATCH 5/8] fix: move lint-ignore to directly above the line with `as any` The ignore directive only applies to the immediately following line. After formatting, `items.push(` was on its own line, so the directive covered that instead of the `as any` cast. Move the comment inside the push() call, directly above the line that needs it. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/fresh/src/runtime/server/preact_hooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fresh/src/runtime/server/preact_hooks.ts b/packages/fresh/src/runtime/server/preact_hooks.ts index 201ff384970..535a8945a87 100644 --- a/packages/fresh/src/runtime/server/preact_hooks.ts +++ b/packages/fresh/src/runtime/server/preact_hooks.ts @@ -292,8 +292,8 @@ options[OptionsType.DIFF] = (vnode) => { const flags = (spanCtx.traceFlags & 1) ? "01" : "00"; const traceparent = `00-${spanCtx.traceId}-${spanCtx.spanId}-${flags}`; - // deno-lint-ignore no-explicit-any items.push( + // deno-lint-ignore no-explicit-any h("meta", { name: "traceparent", content: traceparent }) as any, ); } From dbc79f0b3292788723c0c1fb7aa7cab4147b6c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 29 Mar 2026 20:40:01 +0200 Subject: [PATCH 6/8] fix: replace npmjs link with GitHub repo link npmjs.com returns 403 to automated link checkers. Link to the OpenTelemetry GitHub repo instead. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/opentelemetry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/latest/advanced/opentelemetry.md b/docs/latest/advanced/opentelemetry.md index eee70c88093..4d6d338f74c 100644 --- a/docs/latest/advanced/opentelemetry.md +++ b/docs/latest/advanced/opentelemetry.md @@ -145,7 +145,7 @@ When an OpenTelemetry exporter is active, Fresh automatically injects a ``` This allows client-side OpenTelemetry instrumentation (such as -[`@opentelemetry/instrumentation-document-load`](https://www.npmjs.com/package/@opentelemetry/instrumentation-document-load)) +[`@opentelemetry/instrumentation-document-load`](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/web/opentelemetry-instrumentation-document-load)) to link browser performance traces back to the server-side span that rendered the page, giving you end-to-end visibility from server rendering through page load. From fcdce3ba500ca80cd0d93f8a024ba8ae6457c0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 29 Mar 2026 20:45:05 +0200 Subject: [PATCH 7/8] fix: correct GitHub link path for instrumentation-document-load The package is under packages/, not plugins/web/. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/opentelemetry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/latest/advanced/opentelemetry.md b/docs/latest/advanced/opentelemetry.md index 4d6d338f74c..a1c5ba92c92 100644 --- a/docs/latest/advanced/opentelemetry.md +++ b/docs/latest/advanced/opentelemetry.md @@ -145,7 +145,7 @@ When an OpenTelemetry exporter is active, Fresh automatically injects a ``` This allows client-side OpenTelemetry instrumentation (such as -[`@opentelemetry/instrumentation-document-load`](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/web/opentelemetry-instrumentation-document-load)) +[`@opentelemetry/instrumentation-document-load`](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-document-load)) to link browser performance traces back to the server-side span that rendered the page, giving you end-to-end visibility from server rendering through page load. From 6f4ffc79e7aa4e7ad1f9f2e7a7c5464db9d90733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 29 Mar 2026 20:46:09 +0200 Subject: [PATCH 8/8] fix: use permalink for instrumentation-document-load link Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/latest/advanced/opentelemetry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/latest/advanced/opentelemetry.md b/docs/latest/advanced/opentelemetry.md index a1c5ba92c92..6b715ca6d20 100644 --- a/docs/latest/advanced/opentelemetry.md +++ b/docs/latest/advanced/opentelemetry.md @@ -145,7 +145,7 @@ When an OpenTelemetry exporter is active, Fresh automatically injects a ``` This allows client-side OpenTelemetry instrumentation (such as -[`@opentelemetry/instrumentation-document-load`](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-document-load)) +[`@opentelemetry/instrumentation-document-load`](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/c9d62be989802534c01373e8ab41e13747d7ee3e/packages/instrumentation-document-load)) to link browser performance traces back to the server-side span that rendered the page, giving you end-to-end visibility from server rendering through page load.