Skip to content

feat(trace-view): Show where server spans are for reused pages in the trace - #130771

Merged
s1gr1d merged 3 commits into
masterfrom
sig/ssr-trace-placeholder
Nov 13, 2026
Merged

s1gr1d merged 3 commits into
masterfrom
sig/ssr-trace-placeholder

Conversation

@s1gr1d

@s1gr1d s1gr1d commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Stacked on #130769

When the server reuses HTML for a page (a static page, or the static shell of a partially static page), the server spans of that render are in another trace. Until now, the waterfall showed nothing in their place, so it looked like instrumentation was missing.

This adds a placeholder row under the document request of each pageload with page.server_rendering.type:

  • No bar on the timeline. The render can be hours older than the page load, so the row fills the timeline column with a pattern and the text "Rendered earlier: not on this timeline".
  • Visible when the pageload is collapsed, next to the server request, like a server transaction.

For a fully static page: https://sentry-4x3dkn58u.sentry.dev/explore/traces/trace/cdf98425fa8746f79352a8394e385f32/?node=sr-85846057874639b8-server-render&timestamp=1794319075

image

For a partially static page: https://sentry-4x3dkn58u.sentry.dev/explore/traces/trace/d5f46a9d8bc640ca8d72c415f1b9b0a7/?fov=0.19530032969846994%2C71.30445552967653&node=span-9e805f433be01de5&timestamp=1794326348

image

@s1gr1d
s1gr1d added this pull request to stack #130772 November 11, 2026 10:54
@s1gr1d
s1gr1d requested a review from a team as a code owner November 11, 2026 10:54
@s1gr1d s1gr1d changed the title feat(trace-view): Show where server spans are for reused pages in the… feat(trace-view): Show where server spans are for reused pages in the trace Nov 11, 2026
@github-actions github-actions Bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Nov 11, 2026
@s1gr1d

s1gr1d commented Nov 11, 2026

Copy link
Copy Markdown
Member Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 05deaea. Configure here.

@s1gr1d
s1gr1d requested a review from chargome November 11, 2026 11:16
@s1gr1d
s1gr1d requested a review from nsdeschenes November 11, 2026 11:18
@s1gr1d
s1gr1d force-pushed the sig/ssr-trace-placeholder branch from b72efa6 to cbfdd52 Compare November 11, 2026 14:05
s1gr1d added a commit to getsentry/sentry-javascript that referenced this pull request Nov 11, 2026
**Why:** Pageloads of pages prerendered during `vite build` look like
they have no server instrumentation. Now these pages get an empty
`sentry-cache-origin` meta tag, and the pageload gets
`page.server_rendering.type: static`. Unlike Next.js (#25318), the
pageload gets no link, because the SDK does not reliably send spans from
the build.

**Unsure about:** To leave out `ssr = false` shells, the server checks
for `node_ids:` in Kit's boot script, which Kit writes only for
server-rendered pages
([source](https://github.com/sveltejs/kit/blob/%40sveltejs/kit%403.2.0/packages/kit/src/runtime/server/page/render.js#L452-L467)).


Example (with UI changes from
getsentry/sentry#130771 applied) or
pre-rendered, static page:
https://sentry-4x3dkn58u.sentry.dev/explore/traces/trace/97190404f66048bd89da29cc0a21304f/

<img width="1382" height="406" alt="image"
src="https://github.com/user-attachments/assets/8317ca3a-42c7-4e92-973a-7bf22376b1d6"
/>


Linear:
[JSSDK-37](https://linear.app/getsentry/issue/JSSDK-37/sveltekit-page-options)

@nsdeschenes nsdeschenes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small clarification question that's non-blocking.

Rest looks good to me 🚀

Comment on lines +28 to +29
// Not the pageload id, so lookups by span id still find the pageload.
this.id = `${pageload.id}-server-render`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused here, the comment says it's not the pageload id but we're getting the value from pageload.id?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, the comment was confusing. I'll reword that.

@s1gr1d
s1gr1d force-pushed the sig/ssr-trace-placeholder branch from cbfdd52 to a5491f1 Compare November 13, 2026 09:47
Base automatically changed from sig/ssr-trace-connection to master November 13, 2026 10:20
s1gr1d added a commit that referenced this pull request Nov 13, 2026
…ls (#130769)

A pageload of a static or partially static page can come from HTML that
the server rendered earlier for other page loads. The server spans of
that render are in another trace.

The new section reads `page.server_rendering.type` and the
`cache_origin` and `server_request` span links. It shows which part of
the page was reused and which part was rendered for this page load, and
links to the trace of each.

The section is behind performance-trace-cache-lifecycle.


"Show Spans" will highlight the dynamic server span within the trace
<img width="470" height="172" alt="image"
src="https://github.com/user-attachments/assets/a2610afb-596d-463b-9af5-b01c8c5940db"
/>

When both parts are in another trace (e.g. the dynamic server part
arrived too late):
<img width="465" height="241" alt="image"
src="https://github.com/user-attachments/assets/3e1692e5-fadd-4b6e-b510-1e47247ae9e8"
/>


Some trace view example links can be seen in the other PR in the stack:
#130771
… waterfall

When the server reused HTML for a pageload, the waterfall had no server
spans for that page and no hint why. Users looked for the render in the
wrong place, or thought that instrumentation was missing.

Add a placeholder row under the document request of each pageload with
page.server_rendering.type. The row draws no bar, because the render can be
hours older than the page load. Selecting it opens the Server Rendering
section of the pageload, with links to the trace of the render.

The row is behind performance-trace-cache-lifecycle.
Show the pageload ID and the node actions in the header, like other
drawers. Explain why the server spans are not in this trace and that
the waterfall row only marks where the reused render fits.
@s1gr1d
s1gr1d force-pushed the sig/ssr-trace-placeholder branch from a5491f1 to 08feb48 Compare November 13, 2026 10:20
@s1gr1d
s1gr1d enabled stacked auto-merge (squash) November 13, 2026 10:20
@s1gr1d
s1gr1d merged commit a98fb40 into master Nov 13, 2026
75 checks passed
@s1gr1d
s1gr1d deleted the sig/ssr-trace-placeholder branch November 13, 2026 10:24
s1gr1d added a commit to getsentry/sentry-javascript that referenced this pull request Nov 13, 2026
**Why:** Visitors of a Nuxt page cached by a route rule (`cache`, `swr`,
`isr` on Vercel/Netlify) continued the server trace of the request that
rendered it. Like Next.js (#25318) and SvelteKit (#26301), these pages
now get a `sentry-cache-origin` meta tag in place of the trace tags. The
pageload is `static` and links the render if it was sampled. `isr` only
counts as cached on presets with a platform cache: on `node-server`,
Nitro renders `isr` pages per request. Tested on a Vercel deploy.





**How to review:** Start with `runtime/utils/pageMetaTags.ts` (server
decision) and `runtime/utils/cacheOriginPageload.ts` (pageload).

**Unsure about:** `withCacheOrigin` now exists in Next.js, SvelteKit and
Nuxt. I plan to share it from `@sentry/browser` in a follow-up.


Example with the UI changes from
getsentry/sentry#130771
<img width="1374" height="390" alt="image"
src="https://github.com/user-attachments/assets/d066316e-63ec-44ba-8105-3acdbeb46fe7"
/>


https://sentry-4x3dkn58u.sentry.dev/explore/traces/trace/e7492219ab72481989d6a2cd670fe8d9/


Linear: [JSSDK-36
](https://linear.app/getsentry/issue/JSSDK-36/nuxt-rendering-modes)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
s1gr1d added a commit that referenced this pull request Nov 13, 2026
Follow-up to #130771, which added the Server Rendering details query
with a "placeholder" referrer.

The span drawer and the placeholder drawer used
`api.explore.log-item-details`. This is an Explore logs referrer. Both
drawers now use `api.trace-view.span-detail`, which is registered.

Both drawers request the same span, so they keep the same referrer and
still share one cache entry.
s1gr1d added a commit to getsentry/sentry-javascript that referenced this pull request Nov 13, 2026
Stacked on #26301.

**Why:** Vercel ISR (`config.isr`) caches the HTML of one request and
serves it to every visitor until it expires. That HTML holds the live
`sentry-trace` and `baggage` tags of the render, so every visitor
continued the same old server trace. Next.js had the same bug, fixed in
#25141. Now a page that Vercel renders for its ISR cache gets a
`sentry-cache-origin` tag with the render's `sentry-trace` in place of
the trace tags. The pageload starts its own trace, gets
`page.server_rendering.type: static`, and links the render with a
`cache_origin` link, as in Next.js (#25318).

To detect an ISR render, `sentryHandle` reads the `x-vercel-isr` request
header. Vercel sends it to every invocation of an ISR function, also for
background revalidations and for routes that get `config.isr` from a
layout. The adapter's own `__pathname` marker is gone before Kit sees
the request, and `handle` cannot see the route config (tested both on a
Vercel deployment).

Like a `use cache` hit, the pageload links only a sampled render. An
unsampled render still marks the pageload as `static`. A page with `ssr
= false` gets no tags, as in the build.

**How to review:** Start with `getCacheOrigin` in `handle.ts`. It
returns the render's trace (ISR), `''` (build) or `undefined` (rendered
for this request only). Then `withCacheOrigin` in
`client/traceMetaTags.ts`. The two browser-tracing variants only wrap
their existing span options.

**Unsure about:** A client can send `x-vercel-isr` to a normal SSR
route, and the function sees it. That only changes the pageload of the
request that sends it: it links its own server request instead of
continuing it. I think that is acceptable. I did not check draft mode
(`bypassToken`), where Vercel does not cache the render.

Example with the UI changes from
getsentry/sentry#130771 (a visitor that got an
already happened ISR render):
<img width="1378" height="418" alt="image"
src="https://github.com/user-attachments/assets/842594fd-38cc-418f-b981-369da8305375"
/>



https://sentry-4x3dkn58u.sentry.dev/explore/traces/trace/63b1dbe5fe3849f2b13df81530857c03/


Linear:
[JSSDK-37](https://linear.app/getsentry/issue/JSSDK-37/sveltekit-page-options)

This branch was successfully deployed

1 active deployment
Preview — 08feb484 Deployed Nov 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants