From 87ee98ed3d0f0fb92014984c4c2b1fdde7ebdee3 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Wed, 25 Feb 2026 20:56:50 +0100 Subject: [PATCH] fix(proxy): remove first-referrer cookie stamping from Studio and Docs middleware (#43190) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- apps/docs/middleware.ts | 44 ++++++++----------------------- apps/studio/proxy.ts | 58 +++++++++-------------------------------- 2 files changed, 23 insertions(+), 79 deletions(-) diff --git a/apps/docs/middleware.ts b/apps/docs/middleware.ts index 02b6499dd9c..ea55aafa52e 100644 --- a/apps/docs/middleware.ts +++ b/apps/docs/middleware.ts @@ -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*', } diff --git a/apps/studio/proxy.ts b/apps/studio/proxy.ts index ed872ad8d73..27c941fb1c1 100644 --- a/apps/studio/proxy.ts +++ b/apps/studio/proxy.ts @@ -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).*)'], -}