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.



