The brief.

Tap In London's audience is mostly 18 to 35 and largely on iPhones. Two things made its GA4 unreliable.

  • Safari's 7-day cookie limit. Anyone who came back after a week was counted as a new user, so return visits were wrong.
  • Bots. From October 2026, automated browsers from cloud servers in China, Singapore and Hong Kong arrived in waves, each one a "new user".

Server-side Google Tag Manager would fix both, but it needs a Google Cloud server and a monthly bill. For a static site that only needs GA4, that's more than the job needs.

What was built.

A proxy on the site's own address

About 80 lines of JavaScript in a Netlify Edge Function, with no other infrastructure.

  • One gtag setting sends every GA4 hit to tapinlondon.com instead of to Google.
  • Each hit goes on to Google unchanged, with the visitor's IP so location reports still work. Nothing is logged.
  • Only the site's own measurement ID is forwarded.

Cookies Safari keeps

The GA cookies are sent back from the server, so returning iPhone visitors are recognised.

  • Only when the hit says analytics consent was given.
  • Only cookies gtag already set: the proxy never creates one, and checks each value first.
  • The site's A/B test cookie is renewed the same way, so Safari users keep seeing the same version.

Noise dropped before Google sees it

Bots and the site's own tests never become visitors.

  • Hits from China, Singapore and Hong Kong are dropped, using the same list that blocks the pages.
  • The site's automated checks send a private header, so they're never forwarded.

Consent first

Analytics and advertising are denied until the visitor says yes, and gtag.js doesn't load until then.

  • The choice is stored as a server cookie, so Safari doesn't bring the banner back every week.
  • Postcodes, email addresses and phone numbers are removed before anything is sent.

Measurement that fits the site

GA4's standard ecommerce events, so the built-in reports work with no setup.

  • A funnel ending in the click through to a venue or ticket seller, counted as generate_lead.
  • Campaign source kept for the visit, so a booking click can be traced to the advert behind it.

The change on the page.

One setting does the redirecting. gtag adds /g/collect to the address itself, which is where the edge function listens:

gtag('config', 'G-XXXXXXX', {
  transport_url: 'https://tapinlondon.com/insights',
});

A leak found and closed.

On 25 October a live check found GA4's enhanced measurement sending a page view each time the filters changed the address, so a postcode typed into the search reached Google. Two enhanced measurement settings are now off, and the site updates the address in a way GA4 doesn't see.

What it isn't.

  • Not full server-side tagging: nothing goes to other platforms. It forwards GA4 hits and looks after cookies.
  • GA4 still runs in the browser.
  • Consent is still needed. Renewing a cookie from the server doesn't change the legal position.

Unit tests cover the consent check and cookie handling. Live checks block requests to Google, so testing never adds a visit.

Lessons.

  • Fix the consent cookie too, or returning iPhone visitors see the banner again after a week.
  • Check enhanced measurement on any site with search or filters in the address.
  • Filter bots and your own tests at the proxy, before they become "new users".

When several platforms need data from a server, such as Meta and Google Ads, server-side GTM is the better choice. When GA4 is all a site needs, a proxy like this can be enough.