Add GA4 to Jekyll Once, Then Verify Three Layers
A GitHub Pages site may contain many generated HTML pages, but the GA4 tag should have one source of truth. On this Jekyll site, I put it in _includes/head-custom.html, which _layouts/default.html loads once for every generated page.
The installation is complete only when three facts agree: the tag exists in the built HTML, the browser sends the request, and the intended property receives the event.
Create a web data stream and keep its Measurement ID
In Google Analytics:
- Create or select an Analytics account.
- Create a GA4 property.
- Add the website URL as a Web data stream.
- Copy the Measurement ID in the form
G-XXXXXXXXXX.
The Measurement ID connects browser events from the site to the correct GA4 data stream.
The ID is public configuration and will appear in page source. It identifies the destination property; it is not an API secret. Secrets do not belong in a public GitHub Pages repository.
Put the Google tag in the shared Jekyll head
The original gtag.js snippet is:
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
Replace both occurrences of G-XXXXXXXXXX with the Measurement ID. This site stores the snippet in _includes/head-custom.html, which is included once by _layouts/default.html.
Putting the snippet in each Markdown article would duplicate configuration and make future changes harder. The shared include also makes it possible to check one source file while Jekyll distributes the tag to every generated page.
This setup uses gtag.js directly; it does not require Google Tag Manager.
Verify the source, the browser, and GA4
After building and deploying, check three layers:
- Generated HTML: open page source and confirm that the Measurement ID appears once inside the page
<head>. - Browser request: open Developer Tools and look for a request to
google-analytics.com/g/collectoranalytics.google.com/g/collect. A successful transmission normally returns200, while an ad blocker may prevent the request entirely. - GA4 Realtime or DebugView: visit the site and confirm that the expected event reaches the intended property. DebugView is more useful when event parameters must be inspected.
If Realtime remains empty, first check the Measurement ID, duplicate or missing tags, deployment status, browser blocking, consent state, and JavaScript errors. Realtime and DebugView are immediate verification tools; standard reports can take longer to process. There is little value in interpreting reports until this collection path works.
The first reports confirm more than installation
The Home report below shows active users, new users, event count, and the Realtime panel. It confirms that GA4 is receiving events, but the small early sample should not be treated as a stable traffic pattern.

The next card group breaks the same traffic into country, page title, and session acquisition channel:

GA4 also organizes built-in reports around business objectives, user attributes, and technology:

These reports become useful only after their underlying events and dimensions match the questions being asked. Custom events and reports should come after the default collection has been verified and understood.
Installation success is not measurement success
The shared tag confirms that pages can send standard events. It does not yet measure every behavior that matters on this blog. The EN/VI switch changes content without navigation, so it needs an explicit event if language choice is a reporting question. The link to the banking lab also needs a defined event if that navigation is a site objective.
Before adding either event, define its stable name, parameters, trigger, and report. This prevents collecting clicks that have no decision attached to them.
The current implementation has a privacy decision to make
The tag in _includes/head-custom.html currently loads immediately. Language preference stored by the site and analytics storage serve different purposes. If the deployment requires user consent for analytics storage, consent behavior must be implemented and tested as part of the collection path; the tag snippet alone does not make that decision.
The useful stopping condition is therefore stricter than “the tag exists”: one copy in generated HTML, one successful browser collection request, the expected event and parameters in Realtime or DebugView, and a documented decision about what the site should collect.
Reference: Google Analytics — verify and troubleshoot setup and Google Analytics — set up events.