fix(proxy): remove first-referrer cookie stamping from Studio and Docs middleware (#43190)

Summary
- Reverts the middleware changes to `apps/studio` and `apps/docs` from
#43153 that caused full page reloads on every client-side navigation in
Studio
- Root cause: broadening the `middleware` matchers to match all routes
and returning `NextResponse.next()` unconditionally interferes with
client-side navigation in the multi-zone production setup (`www` proxies
`/dashboard/*` → Studio, `/docs/*` → Docs)
- Cookie stamping is unnecessary in Studio and Docs because `apps/www`
sits in front of both apps in production and already handles
first-referrer cookie attribution for all incoming traffic
- The `apps/www` middleware, packages/common cookie utilities, and
telemetry changes from #43153 are left intact

Test plan
- Verify client-side navigation works without full page reloads in
Studio (production or preview deploy)
- Verify first-referrer cookie is still stamped via `www` middleware on
initial visit
This commit is contained in:
Ivan Vasilov authored and GitHub committed 2026-02-25 20:56:50 +01:00
1 parent fc35630951
commit 87ee98ed3d
2 files changed
+23 -79

No files matched your search

+11 -33
View File
@@ -1,23 +1,17 @@
import { clientSdkIds } from '~/content/navigation.references'
import { BASE_PATH } from '~/lib/constants'
import { stampFirstReferrerCookie } from 'common/first-referrer-cookie'
import { isbot } from 'isbot'
import { NextResponse, type NextRequest } from 'next/server'
import { clientSdkIds } from '~/content/navigation.references'
import { BASE_PATH } from '~/lib/constants'
const REFERENCE_PATH = `${BASE_PATH ?? ''}/reference`
export function middleware(request: NextRequest) {
const url = new URL(request.url)
// Non-reference paths: just handle the first-referrer cookie and pass through
if (!url.pathname.startsWith(REFERENCE_PATH)) {
const response = NextResponse.next()
stampFirstReferrerCookie(request, response)
return response
return NextResponse.next()
}
// Reference paths: existing rewrite logic with cookie stamping on every response
if (isbot(request.headers.get('user-agent'))) {
let [, lib, maybeVersion, ...slug] = url.pathname.replace(REFERENCE_PATH, '').split('/')
@@ -30,9 +24,7 @@ export function middleware(request: NextRequest) {
if (slug.length > 0) {
const rewriteUrl = new URL(url)
rewriteUrl.pathname = (BASE_PATH ?? '') + '/api/crawlers'
const response = NextResponse.rewrite(rewriteUrl)
stampFirstReferrerCookie(request, response)
return response
return NextResponse.rewrite(rewriteUrl)
}
}
}
@@ -41,42 +33,28 @@ export function middleware(request: NextRequest) {
if (lib === 'cli') {
const rewritePath = [REFERENCE_PATH, 'cli'].join('/')
const response = NextResponse.rewrite(new URL(rewritePath, request.url))
stampFirstReferrerCookie(request, response)
return response
return NextResponse.rewrite(new URL(rewritePath, request.url))
}
if (lib === 'api') {
const rewritePath = [REFERENCE_PATH, 'api'].join('/')
const response = NextResponse.rewrite(new URL(rewritePath, request.url))
stampFirstReferrerCookie(request, response)
return response
return NextResponse.rewrite(new URL(rewritePath, request.url))
}
if (lib?.startsWith('self-hosting-')) {
const rewritePath = [REFERENCE_PATH, lib].join('/')
const response = NextResponse.rewrite(new URL(rewritePath, request.url))
stampFirstReferrerCookie(request, response)
return response
return NextResponse.rewrite(new URL(rewritePath, request.url))
}
if (clientSdkIds.includes(lib)) {
const version = /v\d+/.test(maybeVersion) ? maybeVersion : null
const rewritePath = [REFERENCE_PATH, lib, version].filter(Boolean).join('/')
const response = NextResponse.rewrite(new URL(rewritePath, request.url))
stampFirstReferrerCookie(request, response)
return response
return NextResponse.rewrite(new URL(rewritePath, request.url))
}
const response = NextResponse.next()
stampFirstReferrerCookie(request, response)
return response
return NextResponse.next()
}
export const config = {
matcher: [
// Broadened from `/reference/:path*` to stamp first-referrer cookies on all
// docs pages, not just reference paths. Excludes Next.js internals and static files.
'/((?!api|_next/static|_next/image|_next/data|favicon.ico|__nextjs).*)',
],
matcher: '/reference/:path*',
}
+12 -46
View File
@@ -1,12 +1,9 @@
import {
FIRST_REFERRER_COOKIE_NAME,
shouldRefreshCookie,
stampFirstReferrerCookie,
} from 'common/first-referrer-cookie'
import { NextResponse } from 'next/server'
import { IS_PLATFORM } from 'lib/constants'
import type { NextRequest } from 'next/server'
import { IS_PLATFORM } from '@/lib/constants'
export const config = {
matcher: '/api/:function*',
}
// [Joshen] Return 404 for all next.js API endpoints EXCEPT the ones we use in hosted:
const HOSTED_SUPPORTED_API_URLS = [
@@ -32,44 +29,13 @@ const HOSTED_SUPPORTED_API_URLS = [
]
export function proxy(request: NextRequest) {
// API route filtering for hosted platform
if (request.nextUrl.pathname.startsWith('/api/')) {
if (
IS_PLATFORM &&
!HOSTED_SUPPORTED_API_URLS.some((url) => request.nextUrl.pathname.endsWith(url))
) {
return Response.json(
{ success: false, message: 'Endpoint not supported on hosted' },
{ status: 404 }
)
}
// Valid API route — pass through without middleware interference
return
}
// Belt & suspenders: stamp first-referrer cookie for direct Studio visits.
// Primary stamping happens in www/docs middleware; this catches edge cases
// like bookmarked Studio URLs with UTMs or direct-to-Studio paid traffic.
//
// IMPORTANT: Only return NextResponse.next() when we actually need to set a
// cookie. In the multi-zone production setup (www proxies /dashboard/* → Studio),
// returning an explicit NextResponse.next() unconditionally causes the response
// to flow through Next.js's middleware response pipeline, which interferes with
// client-side navigation and triggers full page reloads. Returning undefined
// lets Next.js handle the request completely untouched.
const referrer = request.headers.get('referer') ?? ''
const { stamp } = shouldRefreshCookie(request.cookies.has(FIRST_REFERRER_COOKIE_NAME), {
referrer,
url: request.url,
})
if (stamp) {
const response = NextResponse.next()
stampFirstReferrerCookie(request, response)
return response
if (
IS_PLATFORM &&
!HOSTED_SUPPORTED_API_URLS.some((url) => request.nextUrl.pathname.endsWith(url))
) {
return Response.json(
{ success: false, message: 'Endpoint not supported on hosted' },
{ status: 404 }
)
}
}
export const config = {
matcher: ['/((?!_next/static|_next/image|_next/data|favicon.ico|__nextjs).*)'],
}