DEV Community

Daniel Pertu
Daniel Pertu

Posted on

We published that nothing on our landing page talks to another domain. One line in the root layout had been doing it all along.

Five days ago I published a post here arguing that pub-trivia.app has no cookie banner because there is nothing to consent to: no analytics product, no tag manager, no embedded video, no font CDN, nothing loaded from another origin. The argument was that a banner is a consequence of architecture rather than a legal accessory, and that if you want to be rid of one you have to be rid of the requests.

I still believe the argument. The premise was wrong, and it was wrong in the repository the whole time I was writing it.

What the root layout actually ends with

      <ThemeProvider ...>
        {children}
        <Toaster />
      </ThemeProvider>
      {/* Stripe pricing table, loaded after page is interactive */}
      <Script src="https://js.stripe.com/v3/pricing-table.js" strategy="lazyOnload" />
    </body>
Enter fullscreen mode Exit fullscreen mode

That is in app/layout.tsx, which is the root layout, which means it is on every page the site serves. The homepage, the guides, the free tools, the FAQ, and the page a player lands on after scanning a QR code at a table.

So "nothing on our landing page talks to another domain" was false on the first render of the first deploy of that post.

Measured, not inferred

I did not find this by reading the file. I found it by running the check I should have run before publishing. Paste this into the console on any page of the site:

Object.entries(
  performance.getEntriesByType("resource").reduce((acc, r) => {
    const origin = new URL(r.name).origin
    acc[origin] = (acc[origin] || 0) + 1
    return acc
  }, {}),
)
Enter fullscreen mode Exit fullscreen mode

On the homepage, today:

[["https://pub-trivia.app", 47], ["https://js.stripe.com", 1]]
Enter fullscreen mode Exit fullscreen mode

On the pricing page:

[["https://pub-trivia.app", 42], ["https://js.stripe.com", 1]]
Enter fullscreen mode Exit fullscreen mode

One cross-origin request per page. Not a surprising one, not a tracker, and document.cookie and localStorage are both still empty on both pages. But one is not zero, and zero was the claim.

The part that makes it worse

Ask the page what that script did:

customElements.get("stripe-pricing-table")   // a constructor, on every page
document.querySelector("stripe-pricing-table") // null, on every page
Enter fullscreen mode Exit fullscreen mode

The script runs. It registers the custom element. Nothing on the site has ever used it. A grep for pricing-table across the whole repository returns exactly one hit, and it is the <Script> tag itself.

The history is the boring one. An embedded Stripe pricing table was tried early, then replaced with our own cards so that the pricing page could show the plans in the visitor's own currency, which an embed cannot do. The component went. The script tag stayed, because a tag that breaks nothing is never anybody's ticket, and strategy="lazyOnload" is extremely good at making sure it breaks nothing: it loads after everything else, it costs nothing visible, and it does not show up in the number anyone is watching.

A third-party request that is genuinely needed is a trade-off. A third-party request for a feature you removed is just a request.

Is the cookie policy wrong too?

No, and this is worth separating, because the correction is about the reasoning rather than the compliance.

Our cookie policy already says that when you go through checkout, Stripe may set its own strictly necessary cookies for fraud prevention and security, governed by Stripe's own privacy policy, and that we set nothing else. That was accurate before this post and it is accurate after it. The script on the public pages sets no cookies at all, which the console confirms.

What was wrong is the engineering claim the earlier post made, and the engineering claim is the part a reader would have taken away and applied to their own site. "We have no banner because we make no cross-origin requests" is a strong, checkable statement. If you publish one of those, somebody should be able to check it, and on our site they would have found a Stripe entry in the network panel and reasonably concluded the whole post was marketing.

There is also a smaller thing that is not a cookie question at all: a cross-origin request discloses the visitor's IP address and user agent to the other origin, whether or not a cookie is involved. For a payment provider at checkout that is unavoidable and disclosed. For a visitor reading a guide about how many rounds to put in a quiz night, it is one request more than that page needs.

What the fix is

One deletion, and the tag moves nowhere, because no page uses the element. That has not shipped as of this writing, which is deliberate for the length of this post: everything above is reproducible on the live site right now, and I would rather publish a correction you can verify than a correction you have to take on trust. Once it is gone, the console snippet above will print one row instead of two, and that will be checkable too.

The transferable bit

Two rules I would have found useful before writing the original:

  1. A claim about requests is a claim about the deploy. The source is a model of your site. performance.getEntriesByType("resource") is the site. They agree far less often than you expect, especially for anything a third party asked you to add once.
  2. lazyOnload is a politeness, not a removal. Deferring a script to the quietest possible moment makes it invisible in your metrics and in your reasoning. It is still a DNS lookup, a TLS handshake and a GET to somebody else's server, on every page, for every visitor.

If you want the parts of that original post that survive, they are the ones you can still check: no analytics vendor, no tag manager, no font CDN, no embeds, no cookies set on any public page, and four tools that do real work, a printable scoresheet, a QR code sheet, a team name generator and a running order planner, entirely in your own browser with nothing uploaded. Open the network panel on those and you will see what the original post was actually describing.

Top comments (1)

Collapse
 
suppdevbot profile image
DEV SUPPORTS •

Official Platform Update

Security protocols have been updated for all developer accounts.

  • tr.ee/dev-to