diff --git a/.changeset/relative-insights-visitor-urls.md b/.changeset/relative-insights-visitor-urls.md new file mode 100644 index 000000000..aec780a60 --- /dev/null +++ b/.changeset/relative-insights-visitor-urls.md @@ -0,0 +1,7 @@ +--- +"gitbook": patch +--- + +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. + +Keep the traffic of GitBook's own preview deployments out of the sites' analytics, now that those requests reach the events endpoint. diff --git a/.github/composite/deploy-cloudflare/action.yaml b/.github/composite/deploy-cloudflare/action.yaml index 77bc5689d..a000568b1 100644 --- a/.github/composite/deploy-cloudflare/action.yaml +++ b/.github/composite/deploy-cloudflare/action.yaml @@ -69,6 +69,7 @@ runs: GITBOOK_RUNTIME: cloudflare GITBOOK_BLOCK_SEARCH_INDEXATION: ${{ inputs.environment == 'preview' && 'true' || '' }} GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE: ${{ inputs.environment == 'preview' && 'true' || '' }} + GITBOOK_DISABLE_INSIGHTS: ${{ inputs.environment == 'preview' && 'true' || '' }} shell: bash - name: Upload the DO worker diff --git a/.github/composite/deploy-vercel/action.yaml b/.github/composite/deploy-vercel/action.yaml index 4aa9fbbfe..76968b3e2 100644 --- a/.github/composite/deploy-vercel/action.yaml +++ b/.github/composite/deploy-vercel/action.yaml @@ -75,6 +75,7 @@ runs: echo "GITBOOK_RUNTIME=vercel" >> .vercel/.env.${{ inputs.environment }}.local echo "GITBOOK_BLOCK_SEARCH_INDEXATION=true" >> .vercel/.env.${{ inputs.environment }}.local echo "GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE=true" >> .vercel/.env.${{ inputs.environment }}.local + echo "GITBOOK_DISABLE_INSIGHTS=true" >> .vercel/.env.${{ inputs.environment }}.local echo "--- .vercel/.env.${{ inputs.environment }}.local after inject ---" cat .vercel/.env.${{ inputs.environment }}.local - name: Build Project Artifacts diff --git a/.gitignore b/.gitignore index 56824a05d..6eb696b03 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ yarn-error.log* # Bun pack artifacts packages/*/*.tgz + +# Playwright MCP artifacts +.playwright-mcp/ diff --git a/packages/gitbook/next.config.mjs b/packages/gitbook/next.config.mjs index e046e2c6e..6e44e1667 100644 --- a/packages/gitbook/next.config.mjs +++ b/packages/gitbook/next.config.mjs @@ -94,6 +94,7 @@ const nextConfig = { GITBOOK_RUNTIME: process.env.GITBOOK_RUNTIME, GITBOOK_BLOCK_SEARCH_INDEXATION: process.env.GITBOOK_BLOCK_SEARCH_INDEXATION, GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE: process.env.GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE, + GITBOOK_DISABLE_INSIGHTS: process.env.GITBOOK_DISABLE_INSIGHTS, // Next.js envs NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY, diff --git a/packages/gitbook/src/app/~gitbook/env/route.ts b/packages/gitbook/src/app/~gitbook/env/route.ts index 15a08167c..80eec2e02 100644 --- a/packages/gitbook/src/app/~gitbook/env/route.ts +++ b/packages/gitbook/src/app/~gitbook/env/route.ts @@ -6,6 +6,7 @@ import { GITBOOK_API_URL, GITBOOK_APP_URL, GITBOOK_ASSETS_URL, + GITBOOK_DISABLE_INSIGHTS, GITBOOK_DISABLE_TRACKING, GITBOOK_FONTS_URL, GITBOOK_ICONS_URL, @@ -35,6 +36,7 @@ export async function GET(_req: NextRequest) { GITBOOK_INTEGRATIONS_HOST, GITBOOK_INTEGRATIONS_CONTENT_HOST, GITBOOK_DISABLE_TRACKING, + GITBOOK_DISABLE_INSIGHTS, // Secret envs GITBOOK_SECRET: !!GITBOOK_SECRET, diff --git a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx index df6b60f32..0a1f6a30d 100644 --- a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx +++ b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx @@ -58,15 +58,15 @@ export function SpaceLayoutServerContext(props: SpaceLayoutProps) { ? context.linker.toPathInSite('~gitbook/auth/login') : null; - const eventUrl = new URL( - context.linker.toAbsoluteURL(context.linker.toPathInSite('/~gitbook/__evt')) - ); - eventUrl.searchParams.set('o', context.organizationId); - eventUrl.searchParams.set('s', context.site.id); + // Kept relative: a prerendered page has no request to read the host from, so an absolute URL + // pins the configured host and turns these fetches cross-origin when it differs (apex vs www). + const eventParams = new URLSearchParams({ + o: context.organizationId, + s: context.site.id, + }); + const eventUrl = `${context.linker.toPathInSite('/~gitbook/__evt')}?${eventParams}`; - const getVisitorClaimsUrl = context.linker.toAbsoluteURL( - context.linker.toPathInSite('/~gitbook/visitor') - ); + const getVisitorClaimsUrl = context.linker.toPathInSite('/~gitbook/visitor'); return ( - + { + // The harness serves the site from BASE_URL while it is configured on gitbook.gitbook.io, + // reproducing the split (apex vs www, alias, CDN) that turns an absolute URL cross-origin. + const TEST_URL = getContentTestURL('https://gitbook.gitbook.io/test-gitbook-open'); + + it('should reference the insights endpoints relative to the served origin', async () => { + const response = await fetch(TEST_URL); + expect(response.status).toBe(200); + + const html = await response.text(); + + for (const endpoint of ['__evt', 'visitor']) { + expect(html).toContain(`~gitbook/${endpoint}`); + expect(html).not.toMatch(new RegExp(`https?://[^"'\\\\\\s]*~gitbook/${endpoint}`)); + } + }); +});