From 031a2e9a5626ed8829b86a89cdc8e1e2458cf2e8 Mon Sep 17 00:00:00 2001 From: Nolann Biron Date: Thu, 10 Sep 2026 13:12:58 +0200 Subject: [PATCH] Stop tracking events for sites served through /url/ on GitBook's own host --- .changeset/relative-insights-visitor-urls.md | 2 ++ .../[siteURL]/[siteData]/(content)/layout.tsx | 8 ++++++-- .../[siteData]/~gitbook/embed/layout.tsx | 5 ++++- .../[siteURL]/[siteData]/(content)/layout.tsx | 5 +++-- .../[siteData]/~gitbook/embed/layout.tsx | 2 +- packages/gitbook/src/lib/tracking.ts | 18 ++++++++++++++---- 6 files changed, 30 insertions(+), 10 deletions(-) diff --git a/.changeset/relative-insights-visitor-urls.md b/.changeset/relative-insights-visitor-urls.md index fe848a113..d0a5fb9a8 100644 --- a/.changeset/relative-insights-visitor-urls.md +++ b/.changeset/relative-insights-visitor-urls.md @@ -3,3 +3,5 @@ --- Fix analytics and adaptive content silently breaking on sites served from a different host than the one configured (apex vs www, domain alias, CDN). The insights and visitor-claims endpoints are now requested relative to the page's own origin instead of the configured host, which a prerendered page cannot know. + +Stop tracking events when a site is served through `/url/:url` on GitBook's own host, the access mode used by local development and preview deployments. That traffic is not the site's and no longer reaches its analytics. diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/layout.tsx b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/layout.tsx index 7129cfba1..6adf593a5 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/layout.tsx +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/layout.tsx @@ -18,9 +18,13 @@ export default async function SiteDynamicLayout({ params, children, }: React.PropsWithChildren) { - const { context, visitorAuthClaims } = await getDynamicSiteContext(await params); + const resolvedParams = await params; + const { context, visitorAuthClaims } = await getDynamicSiteContext(resolvedParams); const forcedTheme = await getThemeFromMiddleware(); - const withTracking = shouldTrackEvents(await headers()); + const withTracking = shouldTrackEvents({ + mode: resolvedParams.mode, + headers: await headers(), + }); return ( ) { const resolvedParams = await params; const { context, visitorAuthClaims } = await getEmbeddableStaticContext(resolvedParams); - const withTracking = shouldTrackEvents(await headers()); + const withTracking = shouldTrackEvents({ + mode: resolvedParams.mode, + headers: await headers(), + }); // The forced theme (`?theme=`) comes through the route context (set by the middleware), not a // request header, so the embed can honor it while staying statically rendered. RND-11571 const forcedTheme = getSiteURLDataFromParams(resolvedParams).embedTheme ?? null; diff --git a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/(content)/layout.tsx b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/(content)/layout.tsx index b86e8a387..ca6676baf 100644 --- a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/(content)/layout.tsx +++ b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/(content)/layout.tsx @@ -15,8 +15,9 @@ export default async function SiteStaticLayout({ params, children, }: React.PropsWithChildren) { - const { context, visitorAuthClaims } = await getStaticSiteContext(await params); - const withTracking = shouldTrackEvents(); + const resolvedParams = await params; + const { context, visitorAuthClaims } = await getStaticSiteContext(resolvedParams); + const withTracking = shouldTrackEvents({ mode: resolvedParams.mode }); return ( ) { const resolvedParams = await params; const { context, visitorAuthClaims } = await getEmbeddableStaticContext(resolvedParams); - const withTracking = shouldTrackEvents(); + const withTracking = shouldTrackEvents({ mode: resolvedParams.mode }); // The forced theme (`?theme=`) is threaded through the route context by the middleware so the // embed stays statically rendered — read it from the params rather than a request header. RND-11571 const forcedTheme = getSiteURLDataFromParams(resolvedParams).embedTheme ?? null; diff --git a/packages/gitbook/src/lib/tracking.ts b/packages/gitbook/src/lib/tracking.ts index 2bb066d87..061f7f1c2 100644 --- a/packages/gitbook/src/lib/tracking.ts +++ b/packages/gitbook/src/lib/tracking.ts @@ -9,16 +9,26 @@ import { getLogger } from './logger'; /** * Return true if events should be tracked on the site. * Can be called from the static context or the dynamic context. - * In the static context, only an env variable is checked. - * In the dynamic context, the request headers are checked - this allows the middleware + * In the static context, only the env variable and the serving mode are checked. + * In the dynamic context, the request headers are checked too - this allows the middleware * to disable tracking for preview requests. */ -export function shouldTrackEvents(headers?: Awaited>): boolean { +export function shouldTrackEvents(args: { + /** Serving mode, from the route params. */ + mode: string; + headers?: Awaited>; +}): boolean { if (GITBOOK_DISABLE_TRACKING) { return false; } - const disableTrackingHeader = headers?.get('x-gitbook-disable-tracking'); + // `url` mode only serves `/url/:url` on GitBook's own host — local dev and preview + // deployments. That traffic is not the site's, so it must stay out of its analytics. + if (args.mode === 'url') { + return false; + } + + const disableTrackingHeader = args.headers?.get('x-gitbook-disable-tracking'); if (disableTrackingHeader === 'true') { return false;