We Tracked Nothing for Months Because of a Copy-Paste Error
Discovery: The Silent Dashboard
It was late March. I was doing a routine check in Search Console and the numbers looked solid: 47,000 impressions and 2,300 clicks over the past 28 days. Good growth. Then I switched over to our Google Analytics 4 dashboard and my stomach dropped. Zero users. Zero sessions. Every graph was a flat line.
My first thought was "maybe everyone's running AdBlock?" But that wouldn't explain the massive discrepancy with Search Console data. I immediately pulled up our site's source code. The Google Tag Manager snippet was there, gtag.js was loading. But then one line caught my eye:
gtag('config', 'G-MEASUREMENT_ID');
My heart sank. G-MEASUREMENT_ID. The literal placeholder text from Google's documentation. Not a real Measurement ID. Our site had been running the Analytics code for months, loading gtag.js on every page view, but sending data absolutely nowhere. We'd been flying blind.
Diagnosis: How We Got Here
I immediately dove into our Git history. It took about 10 seconds to find the issue:
// _includes/analytics.html
{% if site.google_analytics %}
<script async src="https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ site.google_analytics }}');
</script>
{% endif %}
The code itself was correct. The problem was in _config.yml:
google_analytics: G-MEASUREMENT_ID
When we first set up the site, we'd added this line but never replaced it with our actual ID. Jekyll's build process didn't warn us because technically there was a value present. The browser console showed no errors because gtag.js happily accepts invalid IDs (it just doesn't send any data).
Worse: we were running AICompatible.com in 11 languages. Each language had its own _config.yml, and every single one had the same placeholder:
_config.en.ymlโ G-MEASUREMENT_ID_config.tr.ymlโ G-MEASUREMENT_ID_config.de.ymlโ G-MEASUREMENT_ID- ...and 8 more languages
Why Didn't We Notice Sooner?
I'll be honest: we'd been procrastinating on properly setting up Analytics. We were operating under a "content first, metrics later" philosophy. We'd look at Search Console's organic traffic data and tell ourselves "the site is growing." But we were completely blind to critical data like user behavior, on-page engagement, and conversion funnels.
The Fix: Bulk Updates via Admin API
First, I needed to create a proper GA4 property. Instead of doing it manually through the UI, I decided to use the Google Analytics Admin API. It would be a learning opportunity, and if we needed automation in the future, we'd have the foundation ready.
1. Creating the Property
from google.analytics.admin import AnalyticsAdminServiceClient
from google.analytics.admin_v1beta.types import Property
client = AnalyticsAdminServiceClient()
property = Property()
property.parent = "accounts/YOUR_ACCOUNT_ID"
property.display_name = "AICompatible.com"
property.time_zone = "Europe/Istanbul"
property.currency_code = "USD"
response = client.create_property(property=property)
measurement_id = response.name.split('/')[-1] # properties/123456789 -> 123456789
But to get the actual Measurement ID (in G-XXXXXXXXXX format), I needed to create a data stream:
from google.analytics.admin_v1beta.types import DataStream
stream = DataStream()
stream.type_ = DataStream.DataStreamType.WEB_DATA_STREAM
stream.display_name = "AICompatible Web"
stream.web_stream_data = {"default_uri": "https://aicompatible.com"}
stream_response = client.create_data_stream(
parent=response.name,
data_stream=stream
)
real_measurement_id = stream_response.web_stream_data.measurement_id
print(f"Real ID: {real_measurement_id}") # G-ABC123XYZ
2. Updating All Config Files
Rather than manually updating 11 files (and risking typos), I wrote a quick Python script:
import os
import re
MEASUREMENT_ID = "G-ABC123XYZ" # Real ID from the API
CONFIG_FILES = [
"_config.yml", "_config.en.yml", "_config.tr.yml",
"_config.de.yml", "_config.fr.yml", "_config.es.yml",
"_config.it.yml", "_config.pt.yml", "_config.nl.yml",
"_config.pl.yml", "_config.ja.yml", "_config.zh.yml"
]
for config_file in CONFIG_FILES:
with open(config_file, 'r') as f:
content = f.read()
updated = re.sub(
r'google_analytics:\s*G-MEASUREMENT_ID',
f'google_analytics: {MEASUREMENT_ID}',
content
)
with open(config_file, 'w') as f:
f.write(updated)
print(f"โ Updated {config_file}")
3. Verification
After deploying, I opened Chrome DevTools and watched the Network tab for collect?v=2 requests. The real Measurement ID was now being sent. Within 10 minutes, I saw our first real-time users appear in the GA4 dashboard.
Lessons Learned
This bug taught us three critical lessons:
- Placeholders are silent killers: They don't throw build errors, they don't show up in the console. But they sabotage your work invisibly.
- Missing metrics are worse than bad metrics: "No data coming in" is more dangerous than "bad data" because you might not notice the problem for months.
- Multilingual sites are risk multipliers: A single config mistake propagates to 11 copies, and manual fixes increase the chance of introducing new errors.
After this experience, we added a new check to AICompatible.com's crawler: it scans all third-party scripts (Analytics, Tag Manager, etc.) and detects placeholder IDs. So far, we've found similar issues on about 8% of the 200+ AI tool sites we've crawled. It's a common startup problem: moving fast while skipping fundamental infrastructure details.
If you have Google Analytics installed but aren't seeing data in your dashboard, check your source code. If you see placeholders like G-MEASUREMENT_ID, UA-XXXXX-Y, or GTM-XXXXXX, you're in the same boat we were. Our crawler automatically detects these configuration errors and shows you exactly which line they're on. Sometimes the biggest problems hide in the simplest oversights.