Files
supabase/apps/studio/start.ts
Alaister Young ad181489b1 feat(studio): adopt @sentry/tanstackstart-react server instrumentation on the TanStack build (#47724)
Stacked on #47666 (base `alaister/tanstack-sentry-init`; retarget to
`master` when that merges). **Supersedes #47721** (the manual
`@sentry/node` wrapper). Client stays on #47666's `@sentry/react` setup.

Adopts the official `@sentry/tanstackstart-react` SDK **on the server
only**, after a spike (#47723) evaluating the full unified client+server
SDK. The spike found the SDK's **browser**
`tanstackRouterBrowserTracingIntegration` is a broken no-op stub at
10.59.0/10.64.0 — so the client stays on `@sentry/react` (whose
equivalent integration is a real, working implementation, already
shipped in #47666). The **server** exports, however, are a clear upgrade
and slot in cleanly.

### What this adds (server-side, TanStack build only)
- **`instrument.server.mjs`** — `Sentry.init` from
`@sentry/tanstackstart-react`, mirroring `sentry.server.config.ts` +
`release: VERCEL_GIT_COMMIT_SHA`.
- **`start.ts`** — `sentryGlobalRequestMiddleware` +
`sentryGlobalFunctionMiddleware` at the front of the existing
`createStart(...)` middleware. **This is the win**: it captures request-
and server-function errors *including the ones swallowed into 500s* —
the exact class the manual wrapper (and the Next server SDK) miss.
- **`api/server.js` / `scripts/serve.js`** — gated
(`STUDIO_FRAMEWORK==='tanstack'`) instrument init +
`wrapFetchWithSentry` on the handler.
- **`vite.config.ts`** — `sentryTanstackStart({ …,
autoInstrumentMiddleware: false })` as the last plugin: source-map
upload + release injection (skips gracefully without an auth token).
Middleware is wired explicitly rather than via the plugin's
string-rewrite.

### Guarantees
- **Client untouched** — the `@sentry/nextjs`→`@sentry/react` alias and
#47666's client init are unchanged.
- **Next untouched** — `instrumentation.ts` / `sentry.server.config.ts`
etc. stay as-is; all new code is TanStack-gated.
- **No server SDK in the client bundle** — verified after build: no
`@sentry/node` / server middleware / `wrapFetchWithSentry` in
`dist/client/assets` (`start.ts`'s server import is tree-shaken out).

### Verified
TanStack build exit 0 (past `assertNoChunkCycles`), post-build server
boot served `/api/get-utc-time → 200`, `tsc --noEmit` clean,
prettier/eslint clean. Node smoke: no-DSN init is a clean no-op; wrapped
handler returns 200.

### To test (deploy with a server DSN)
Throw a server error from an `/api/*` route (or a `/_serverFn/*`) —
including one that gets turned into a 500 without rethrowing — and
confirm a server event in Sentry with `release` = the deploy SHA.
Compared to #47721, the swallowed-500 case should now be captured via
the middleware.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added Sentry integration for the Studio app’s TanStack Start runtime,
including request and server-function instrumentation.
* Wrapped server request handling to capture errors reliably, with
tracing enabled.
* Updated build tooling to conditionally upload source maps when
credentials are present.

* **Bug Fixes**
* Improved resilience by safely falling back to a no-op Sentry setup if
instrumentation cannot be loaded.
* Ensured existing request protection remains enabled while adding
observability middleware.

* **Chores / Config**
* Added `SKIP_ASSET_UPLOAD` to the build environment list to control
cache/build behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-07-10 16:52:07 +08:00

47 lines
2.1 KiB
TypeScript

import {
sentryGlobalFunctionMiddleware,
sentryGlobalRequestMiddleware,
} from '@sentry/tanstackstart-react'
import { createMiddleware, createStart } from '@tanstack/react-start'
import { BASE_PATH, IS_PLATFORM } from '@/lib/constants'
import { isHostedSupportedApiPath } from '@/lib/hosted-api-allowlist'
// Self-hosted-only API routes must 404 in platform (hosted) mode. Under the
// Next pages router this lives in middleware (proxy.ts), but TanStack Start
// has no middleware runtime, so the guard is migrated here as a global
// request middleware sharing the same allowlist (lib/hosted-api-allowlist.ts).
// On Vercel our `/api/*` (and `/_serverFn/*`) requests are rewritten to the
// api/server.js function which runs the Start handler, so createStartHandler
// runs this server-side for every API request — even though pages are served
// as a static SPA shell. The guard therefore covers all API routes from a
// single place.
const platformApiGuard = createMiddleware({ type: 'request' }).server(({ request, next }) => {
const { pathname } = new URL(request.url)
// Path relative to the configured basePath — mirrors Next's basePath-
// relative middleware matcher.
const relativePath =
BASE_PATH && pathname.startsWith(BASE_PATH) ? pathname.slice(BASE_PATH.length) : pathname
if (IS_PLATFORM && relativePath.startsWith('/api/') && !isHostedSupportedApiPath(relativePath)) {
return Response.json(
{ success: false, message: 'Endpoint not supported on hosted' },
{ status: 404 }
)
}
return next()
})
// Sentry's global middlewares go at the FRONT so they wrap the whole request /
// server-function lifecycle — including errors that downstream code swallows
// into a 500, which the manual `@sentry/nextjs` approach never sees. The SDK's
// Vite plugin can auto-wrap these arrays instead, but we disable that
// (`autoInstrumentMiddleware: false` in vite.config.ts) and wire them
// explicitly so the instrumentation is visible in source.
export const startInstance = createStart(() => ({
requestMiddleware: [sentryGlobalRequestMiddleware, platformApiGuard],
functionMiddleware: [sentryGlobalFunctionMiddleware],
}))