Skip to content

fix(tanstackstart-react): Initialize the SDK at Nitro server startup - #26088

Closed
chargome wants to merge 6 commits into
developfrom
fix-tanstack-nitro-first
Closed

chargome wants to merge 6 commits into
developfrom
fix-tanstack-nitro-first

Conversation

@chargome

@chargome chargome commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Why: Nitro imports the TanStack Start server entry lazily on the first request. When Sentry.init runs from an instrument.server import in the server entry, as on Vercel where --import isn't available, it initializes too late for the first request's http.server span. The Vite plugin now adds a Nitro plugin that imports the service entries at server startup. It uses the same specifiers as Nitro, so no second SDK copy is bundled. The plugin uses top-level await so serverless platforms don't send the first request before init. Services that import the Nitro runtime are not awaited, to avoid an import cycle deadlock, and a 3s timeout is a fallback. You can turn it off with preloadServerEntry: false.

Unsure about: The preload reads Nitro's internal #nitro/virtual/vite-services template. If that template changes shape, nothing is preloaded and we're back to the current behavior.

Closes #25970

@chargome

chargome commented Nov 4, 2026

Copy link
Copy Markdown
Member Author

bugbot run

@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

size-limit report 📦

Compared: 5dab66a (PR test merge using develop at eb0e92e)

Against: eb0e92e (develop) · base run #65112

✅ Baseline matches: this diff contains only this PR's changes.

Path Size % Change Change
@sentry/browser 30.4 kB - -
@sentry/browser - with treeshaking flags 28.54 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 28.43 kB - -
@sentry/browser (incl. Tracing) 52.73 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 52.73 kB - -
@sentry/browser (incl. Tracing, Profiling) 55.7 kB - -
@sentry/browser (incl. Tracing, Replay) 93.03 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 81.63 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 97.78 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 110.77 kB - -
@sentry/browser (incl. Feedback) 47.98 kB - -
@sentry/browser (incl. sendFeedback) 35.5 kB - -
@sentry/browser (incl. FeedbackAsync) 40.61 kB - -
@sentry/browser (incl. Metrics) 31.44 kB - -
@sentry/browser (incl. Logs) 31.73 kB - -
@sentry/browser (incl. Metrics & Logs) 32.45 kB - -
@sentry/react 32.29 kB - -
@sentry/react (incl. Tracing) 55.1 kB - -
@sentry/vue 38.67 kB - -
@sentry/vue (incl. Tracing) 55.75 kB - -
@sentry/svelte 30.43 kB - -
@sentry/remix (Remix 3 client bundle) 57.82 kB - -
CDN Bundle 32.2 kB - -
CDN Bundle (incl. Tracing) 53.26 kB - -
CDN Bundle (incl. Logs, Metrics) 34.47 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 55.2 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 75.88 kB - -
CDN Bundle (incl. Tracing, Replay) 91.47 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 93.4 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 97.58 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 99.58 kB - -
CDN Bundle - uncompressed 94.67 kB - -
CDN Bundle (incl. Tracing) - uncompressed 157.67 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 101.14 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 163.41 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 232.71 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 279.36 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 285.08 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 293.06 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 298.77 kB - -
@sentry/nextjs (client) 58.05 kB - -
@sentry/sveltekit (client) 53.24 kB - -
@sentry/core/server 44.25 kB - -
@sentry/core/browser 13.93 kB - -
@sentry/node 156.6 kB +0.02% +21 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 82.28 kB - -
@sentry/node - without tracing 96.91 kB +0.02% +18 B 🔺
@sentry/node - without channel injection 136.94 kB +0.02% +17 B 🔺
@sentry/aws-serverless 105.26 kB +0.02% +18 B 🔺
@sentry/cloudflare (withSentry) - minified 200.67 kB - -
@sentry/cloudflare (withSentry) 499.05 kB - -
@sentry/nextjs/cloudflare (withSentry) - minified 222.67 kB - -

View base workflow run

@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Optional E2E tests

Some optional checks were not successful. They do not block the merge. 6 not successful, 50 successful. See the run.

Check Status
❌ CI: Optional E2E Tests / E2E cloudflare-pi-durable (latest) Test (optional) Failing after 57s Details
❌ CI: Optional E2E Tests / E2E node-eve Test (optional) Failing after 2m Details
❌ CI: Optional E2E Tests / E2E node-flue (latest) Test (optional) Failing after 1m Details
❌ CI: Optional E2E Tests / E2E nextjs-16-cacheComponents (cloudflare) Test (optional) Failing after 10m Details
❌ CI: Optional E2E Tests / E2E node-eve (orchestrion) Test (optional) Failing after 3m Details
❌ CI: Optional E2E Tests / E2E node-eve (latest) Test (optional) Failing after 1m Details
Successful checks
Check Status
✅ CI: Optional E2E Tests / E2E astro-7 Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E astro-7-static Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E debug-id-sourcemaps Test (optional) Successful in 50s Details
✅ CI: Optional E2E Tests / E2E cloudflare-think Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-fastify-6 (tracer provider) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E cloudflare-agent Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-14 (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E cloudflare-think (ai v6) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-express-send-to-sentry Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E deno (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-mastra Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-flue Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E cloudflare-flue-send-to-sentry Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nestjs-12 (latest) Test (optional) Successful in 48s Details
✅ CI: Optional E2E Tests / E2E node-pi-durable (latest) Test (optional) Successful in 35s Details
✅ CI: Optional E2E Tests / E2E node-express-mcp-v2 Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (vinext) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-openai-decisions (latest) Test (optional) Successful in 31s Details
✅ CI: Optional E2E Tests / E2E cloudflare-mastra Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-openai-decisions Test (optional) Successful in 52s Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (deno) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps (static / nuxt generate) Test (optional) Successful in 54s Details
✅ CI: Optional E2E Tests / E2E node-express-mcp-v1 Test (optional) Successful in 39s Details
✅ CI: Optional E2E Tests / E2E nextjs-16-tunnel (cloudflare) Test (optional) Successful in 57s Details
✅ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-15 (latest) Test (optional) Successful in 3m Details
✅ CI: Optional E2E Tests / E2E node-express-send-to-sentry-static Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nuxt-5 Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E vue-3 (no Options API) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-15 (turbo) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (vinext, bun) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nextjs-orpc Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E react-router-7-framework-instrumentation Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E cloudflare-pi-durable Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react-cloudflare Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries (cloudflare) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (cloudflare, latest) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nuxt-5 (tunnel-dev) Test (optional) Successful in 48s Details
✅ CI: Optional E2E Tests / E2E supabase-nextjs Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-pi-durable Test (optional) Successful in 42s Details
✅ CI: Optional E2E Tests / E2E node-mastra (latest) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps (client source maps kept) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E cloudflare-workers-send-to-sentry Test (optional) Successful in 3m Details
✅ CI: Optional E2E Tests / E2E solidstart-2 Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-fastify-6 Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react-sourcemaps Test (optional) Successful in 58s Details
✅ CI: Optional E2E Tests / E2E react-send-to-sentry Test (optional) Successful in 3m Details

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@chargome

chargome commented Nov 4, 2026

Copy link
Copy Markdown
Member Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@chargome chargome self-assigned this Nov 4, 2026
@chargome

chargome commented Nov 4, 2026

Copy link
Copy Markdown
Member Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@chargome

chargome commented Nov 4, 2026

Copy link
Copy Markdown
Member Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@chargome

chargome commented Nov 4, 2026

Copy link
Copy Markdown
Member Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

chargome and others added 5 commits November 4, 2026 15:07
Nitro loads TanStack Start's server entry on the first request, so an
instrument import at the top of the server entry only initializes the SDK
during that request. Without node --import, as on Vercel, the first request
of every server instance has no http.server span.

The Vite plugin now adds a Nitro plugin that imports the server entry at
startup. It imports the same chunk Nitro loads lazily, so the SDK is not
bundled a second time.

Fixes #25970
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nit test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read the service entries from Nitro's vite-services module instead of
deriving them, so the preload uses the same specifiers and skips server
environments that are not Nitro services. Add a preloadServerEntry option to
opt out, and check in the e2e test that the preload did not time out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o services

Also formats the e2e Vite config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chargome
chargome force-pushed the fix-tanstack-nitro-first branch from 0aad0ec to 7c08218 Compare November 4, 2026 14:11
…e Nitro build

Older Nitro betas generate the module that imports the services as
#nitro-vite-setup, so the preload did nothing there. The preload module now
loads that module itself, under either name.

The service build leaves some packages as plain imports, so it could not see
when one of them imports the Nitro runtime. Whether a service entry imports
the preload module is now decided from the module graph of the Nitro build.

Also logs with debug enabled, shares the Nitro options type, and no longer
starts an unused app server in the init-in-server-entry e2e variant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chargome

chargome commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3e30f13. Configure here.

'if (awaited.length > 0) {',
' let loaded = false;',
' let timeout;',
` await Promise.race([Promise.all(awaited).then(() => { loaded = true; }), new Promise(resolve => { timeout = setTimeout(resolve, ${PRELOAD_TIMEOUT_MS}); })]);`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Startup timer is not unref'd

Low Severity

The generated preload uses setTimeout without unref() or safeUnref(). That timer is injected into the Nitro server bundle, so a pending 3s fallback can keep a Node process alive. This was flagged because the project review rules require unref on timers in server runtime paths.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit 3e30f13. Configure here.

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.

Keeping the timer referenced on purpose. It only matters when the preload never settles, which happens with an import cycle the build did not detect. Then nothing else keeps the event loop alive, so with unref() Node would exit with code 13 (unsettled top-level await) before the timeout fires, which is the case the timeout exists for. The timer is cleared as soon as the preload finishes, and it keeps the process alive for at most 3s while the server starts.

@chargome

Copy link
Copy Markdown
Member Author

Closing in favor of #26224, which initializes the SDK from a Nitro plugin with Nitro's public module API. That avoids depending on Nitro internals and the startup race this PR had to handle. The hardening from here moves over to #26224.

@chargome chargome closed this Nov 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TanStack Start on Nitro: first request per server instance has no http.server span without --import

2 participants