mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
## Summary Re-lands the first-referrer cookie feature from #42768 (reverted in #43129) with middleware matcher fixes that prevent Studio traffic interference. **Tracks:** [GROWTH-651](https://linear.app/supabase/issue/GROWTH-651) ## What changed New shared module in `packages/common/first-referrer-cookie.ts` that handles stamping and parsing a first-referrer cookie (referrer, UTMs, click IDs, landing URL). Each app's middleware calls `stampFirstReferrerCookie` on the edge response — www and docs are the primary entry points, Studio is a fallback for direct visits with UTMs. On the telemetry side, `handlePageTelemetry` now takes an options object instead of positional args, reads the cookie on initial pageview, and overrides the referrer if the cookie captured an external source but the current referrer is internal (i.e., the user navigated cross-app). Also sends `first_referrer_cookie_present`/`consumed` properties so we can observe the handoff in PostHog. The docs middleware matcher was broadened from `/reference/:path*` to all docs pages so we stamp cookies site-wide, not just on reference paths. ## Root cause of original revert Two layers: 1. **Matcher gap**: www middleware ran on `/dashboard/*` traffic in prod due to Vercel Multi-Zone architecture (www is the gateway for `supabase.com`, proxying `/dashboard` → Studio, `/docs` → Docs). Middleware runs *before* rewrites, so www middleware executed on all proxied traffic. 2. **`_next/data` interception**: The matcher didn't exclude `_next/data` paths. Client-side navigation in Next.js fetches JSON via `/_next/data/...` — middleware intercepted these, returned `NextResponse.next()` with cookie mutations (which processes through the middleware response pipeline), and this interfered with the JSON responses, causing full page reloads in the SQL editor. ## How this PR fixes it | Fix | Detail | |---|---| | Exclude `_next/data` | All three matchers (`www`, `docs`, `studio`) exclude `_next/data` via negative lookahead | | Exclude `dashboard` + `docs` from www | www middleware no longer runs on proxied app traffic | | `/api/` path guard in Studio | Broadened matcher requires explicit path check for API route filtering | | `NextResponse.next()` semantics | Cookie stamping only happens on matched paths; unmatched paths never enter middleware | ### `NextResponse.next()` vs `undefined` nuance Returning an explicit `NextResponse.next()` with cookie mutations processes through Next.js's middleware response pipeline (headers are merged, cookies are set). Returning `undefined` (i.e. the request never matches the matcher) lets Next.js handle the request completely untouched. The matcher exclusions ensure `_next/data` and proxied app paths never enter middleware at all. ## Testing - ✅ 22 unit tests for shared cookie utilities (all pass) - ✅ Studio prod build succeeds, middleware recognized as `ƒ Proxy (Middleware)` - ✅ Playwright validation: client-side navigation works across 3 page transitions, `_next/data` requests return 200 OK without middleware interception, no full-page reloads - ❌ www/docs SSG builds require platform backend services (expected — same as master)
19 lines
812 B
TypeScript
19 lines
812 B
TypeScript
import { stampFirstReferrerCookie } from 'common/first-referrer-cookie'
|
|
import { NextResponse, type NextRequest } from 'next/server'
|
|
|
|
export function middleware(request: NextRequest) {
|
|
const response = NextResponse.next()
|
|
stampFirstReferrerCookie(request, response)
|
|
return response
|
|
}
|
|
|
|
export const config = {
|
|
matcher: [
|
|
// Match all paths except Next.js internals, static files, and proxied app paths.
|
|
// - _next/data: client-side navigation JSON fetches (MUST exclude to prevent full page reloads)
|
|
// - dashboard: Studio app (proxied via multi-zone, has its own cookie stamping in proxy.ts)
|
|
// - docs: Docs app (proxied via multi-zone in prod, has its own middleware for cookie stamping)
|
|
'/((?!api|_next/static|_next/image|_next/data|dashboard|docs|favicon.ico|__nextjs).*)',
|
|
],
|
|
}
|