Skip to content

Add c15t to tanstack.com - #1364

Draft
KayleeWilliams wants to merge 2 commits into
TanStack:mainfrom
KayleeWilliams:kaylee/add-c15t-consent-integration
Draft

KayleeWilliams wants to merge 2 commits into
TanStack:mainfrom
KayleeWilliams:kaylee/add-c15t-consent-integration

Conversation

@KayleeWilliams

@KayleeWilliams KayleeWilliams commented Oct 12, 2026 •

Copy link
Copy Markdown

Visitors now choose what tanstack.com measures. Google Analytics, NameThatHost, Scarf and YouTube or Vimeo embeds wait for that choice. The banner comes in the server HTML, so it shows in the first frame and does not move the page.

c15t runs the banner and the consent records. The policy and the stored choices live in Inth, at tanstack-tanstack.inth.app.

Videos

Homepage bar on a large window. The bar covers the stats strip under the hero. When you scroll, it stops under the navbar until you choose.

sticky-bar-dark.mp4
sticky-bar-light.mp4

All window sizes. The homepage at 16 sizes from 390 × 844 to 3840 × 2160, then the floating banner on /blog at 5 sizes.

Too large will update later

What changes

Consent

  • ConsentRoot in src/routes/__root.tsx holds the visitor's consent state. The root loader gets that state on the server.
  • consentManifest() in vite.config.ts puts the Inth policy into the server build. The server does not ask Inth for the policy on each page. After a policy change in Inth, rebuild the site.
  • There are two categories. Measurement covers Analytics. Marketing covers the video embeds.
  • The homepage shows a bar (variant="bar"). All other pages show c15t's floating card.
  • Accept and Reject have the same button style, as the policy needs.
  • The footer has a "Privacy settings" link. It opens the consent dialog.
  • The dev tools get a c15t panel.

Scripts that wait for consent

  • Google Analytics loads through c15t's gtag helper. It still goes through the /_a proxy. Page views from before the choice stay in a queue. They go out only if the visitor accepts.
  • NameThatHost loads through c15t.
  • Scarf beacons and clipboard events stay in a queue until the choice. c15t adds no Scarf script. It only tells our Scarf code the decision.
  • Sentry still starts in src/router.tsx for all visitors, so it still catches hydration errors. Until a visitor allows Analytics, c15t removes user data, IP addresses and session IDs from the reports.
  • Markdown YouTube and Vimeo iframes show a placeholder until the visitor allows marketing. The placeholder has a "Privacy settings" button and a link to the video on YouTube or Vimeo.

Fonts

  • Bricolage Grotesque, IBM Plex Mono, DM Sans, Manrope, JetBrains Mono and the game's Inter now come from public/fonts. Before, Google Fonts got each visitor's IP address before any choice.
  • This also removes a render-blocking stylesheet from Google. Most of the LCP gain on desktop below comes from this change.
  • public/_headers caches /fonts/* for one year. If a font file changes, give it a new name.

Other

  • @sentry/* goes from 10.47 to 10.76.1. The c15t integration for Sentry needs 10.67 or later.
  • src/utils/analytics/events.ts is now registry.ts. Lists in tracker blockers match the dev module URL /analytics/events.ts. With a blocker on, the page did not hydrate in dev.
  • A Vite plugin gives the Cloudflare Worker the real @c15t/tanstack-start/server entry. The browser export condition gives the Worker a stub that throws.
  • Commit 0a61c04 tells shared caches not to store server-rendered HTML. That HTML now holds each visitor's consent state, so a cached copy could show the banner again. A shared cache could also give one visitor's state to a different visitor.

Performance

Lab test, median of 7 runs, base commit 862ccc3d against this branch. Mobile is 412 × 823 with the CPU slowed 4× on a 1.6 Mbps link with 150 ms latency. Desktop is 1350 × 940 with no throttle.

Base Branch
Mobile /, LCP 3252 ms 3240 ms
Mobile /, Total Blocking Time 166 ms 228 ms
Mobile /, banner paint — 1839 ms (first paint is 1844 ms)
Mobile /, Reject All response (INP) — 72 ms
Desktop /, LCP 180 ms 100 ms
Desktop /, Reject All response (INP) — 48 ms
CLS, all pages 0 0
  • The banner paints before hydration in 7 of 7 runs. A small inline script records clicks on it before hydration.
  • First load adds 93 KB of gzipped JavaScript. c15t's runtime is about 92 KB of that. Total Blocking Time on mobile goes up by 62 ms because of it.
  • Our banner styles and the sticky hook add about 2 KB.
  • The sticky hook adds about 0.5 ms of style work per scroll step while the bar shows, with the CPU slowed 4×. There are no long tasks and no extra dropped frames. After the visitor chooses, a scroll costs the same as on the base commit.
  • The font change helps mobile LCP. This test does not measure c15t without the font change.

Checks

  • pnpm test
  • Lab test above, on a production build
  • Videos: homepage at 16 window sizes and /blog at 5, in light and dark mode
  • Accept, Reject and Customize on a preview deploy, with each choice checked in Inth

Loaders and server functions set Cache-Control on the whole response.
getOSSStats sets "public, max-age=300, stale-while-revalidate=3600", so
any page that loads stats, including the homepage, told browsers and
proxies they could reuse its HTML for 5 minutes and serve it stale for
up to an hour.

That was harmless while every visitor got the same HTML. With the
consent banner, the server renders the visitor's own consent state from
their c15t cookie: whether to show the banner, their recorded choices
and their subject ID. A visitor who accepted and opened the homepage in
a new tab got the cached pre-choice HTML from the browser's disk cache,
so the banner came back until c15t re-read the cookie on the next focus.
A shared cache could hand one visitor's consent state to another.

The hosting layer now sets "Cache-Control: private, no-cache" on every
HTML response and drops Cloudflare-CDN-Cache-Control. Browsers still
keep the page, but revalidate it on each visit. JSON, images and other
non-HTML responses keep the headers their routes set.
Add a c15t consent banner and hold Google Analytics, NameThatHost, Scarf
and video embeds until the visitor chooses. Sentry strips user data until
Analytics is allowed. Self-host fonts so Google Fonts no longer sees each
visitor's IP before a choice.
@changeset-bot

changeset-bot Bot commented Oct 12, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d8e90e1

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Oct 12, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@socket-security

Copy link
Copy Markdown

@KayleeWilliams KayleeWilliams changed the title Ask visitors for consent before loading trackers Add c15t to tanstack.com Oct 12, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant