Skip to content

docs: add Google Analytics and a sitemap - #237

Merged
shenxianpeng merged 2 commits into
mainfrom
chore/google-analytics
Oct 18, 2026
Merged

shenxianpeng merged 2 commits into
mainfrom
chore/google-analytics

Conversation

@shenxianpeng

@shenxianpeng shenxianpeng commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

These docs are served from the cpp-linter.github.io origin, but unlike the organization site they have no Google Analytics. So there is no record of what readers look at once they leave the organization's home page. These were also the only cpp-linter docs without a sitemap. This PR adds the same property the organization site uses (G-JKH4SXC7HC). It also adds the maintainer flag from cpp-linter/cpp-linter.github.io#83, so the maintainers' own reading can be filtered out.

This PR only changes the site. The Google Analytics side (the Internal Traffic filter, key events) is handled separately.

Before / After (改动前 / 改动后)

Before (live site, origin/main) After (this branch, sphinx-build docs)
Google Analytics No gtag on any page G-JKH4SXC7HC on every page (sphinx-immaterial's analytics theme option)
Maintainer visits — traffic_type=internal while the cpp-linter-internal flag is set (?internal=1 / ?internal=0 on any page of the origin), via docs/_templates/layout.html
sitemap.xml 404 18 URLs: all pages and API reference pages. search.html, genindex.html, py-modindex.html and _modules/* are excluded.
Docs dependencies sphinx-immaterial + sphinx-sitemap>=2.9.0 (lock updated with uv 0.13.1; the diff only adds sphinx-sitemap and its dependency sphinx-last-updated-by-git)

Evidence: built <head> of index.html

<script>
  try {
    var param = new URLSearchParams(location.search).get("internal");
    if (param === "1") localStorage.setItem("cpp-linter-internal", "1");
    if (param === "0") localStorage.removeItem("cpp-linter-internal");
    if (localStorage.getItem("cpp-linter-internal") === "1") {
      window.dataLayer = window.dataLayer || [];
      (function () { dataLayer.push(arguments); })("set", { traffic_type: "internal" });
    }
  } catch (e) {
    // localStorage is unavailable when storage is blocked; the visit is then not tagged.
  }
</script>
<script id="__analytics">function __md_analytics(){ /* … the theme's snippet, unchanged … */ var t=document.createElement("script");t.async=!0,t.src="https://www.googletagmanager.com/gtag/js?id=G-JKH4SXC7HC",document.getElementById("__analytics").insertAdjacentElement("afterEnd",t)}</script>

The flag script comes before id="__analytics" (character 1160 vs 1554 in the built index.html).

Evidence: sitemap.xml

https://cpp-linter.github.io/cpp-linter/API-Reference/cpp_linter.html
https://cpp-linter.github.io/cpp-linter/API-Reference/cpp_linter.clang_tools.html
… (12 API reference pages in total)
https://cpp-linter.github.io/cpp-linter/building_docs.html
https://cpp-linter.github.io/cpp-linter/cli_args.html
https://cpp-linter.github.io/cpp-linter/contributing.html
https://cpp-linter.github.io/cpp-linter/index.html
https://cpp-linter.github.io/cpp-linter/permissions.html
https://cpp-linter.github.io/cpp-linter/pr_review_caveats.html

The organization site's robots.txt (cpp-linter/cpp-linter.github.io#83) lists https://cpp-linter.github.io/cpp-linter/sitemap.xml.

Notes

  • The flag snippet runs in the theme's analytics block, before the theme's own snippet. That way gtag('set', {traffic_type: 'internal'}) is queued ahead of the first page_view. The same snippet was tested end to end with the real gtag.js and a fake measurement ID in feat: tag maintainer visits, track code copies and add social cards cpp-linter.github.io#83: tt=internal is sent after ?internal=1 and stops after ?internal=0.
  • None of these changes alters a page's layout, so there are no screenshots.
  • cspell.config.yml gains genindex and modindex (Sphinx page names in sitemap_excludes).

Summary by CodeRabbit

  • New Features
    • Documentation pages are now included in a sitemap, making them easier for search engines to discover.
    • Documentation analytics now distinguish visits marked as internal, helping separate them from other traffic.
  • Chores
    • Added documentation-related terms to spelling checks.

- Send page views to the Google Analytics property that
  cpp-linter.github.io already uses. These docs share its origin, so
  one property covers the whole site.
- Tag maintainer visits with traffic_type=internal when the
  cpp-linter-internal flag is set (?internal=1 on any page of the
  site), so the Internal Traffic data filter can drop them.
- Build sitemap.xml with sphinx-sitemap. These were the only cpp-linter
  docs without one; the organization site's robots.txt lists it.
@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fdad53c2-6265-47ee-91c7-8b542bf9bbf9
📥 Commits

Reviewing files that changed from the base of the PR and between ce85d58 and 586cc57.

⛔ Files ignored due to path filters: 1
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing: 4
  • cspell.config.yml
  • docs/_templates/layout.html
  • docs/conf.py
  • pyproject.toml

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

The documentation build now generates a sitemap with configured URL and exclusion settings, and configures Google Analytics. The layout template uses the internal query parameter to set an internal-traffic flag and tag analytics traffic. The spelling list includes genindex and modindex.

Changes

Documentation sitemap

Layer / File(s) Summary
Sitemap configuration
pyproject.toml, docs/conf.py, cspell.config.yml
The docs dependencies and Sphinx configuration add sitemap generation, set URL settings, and exclude selected pages. The spelling list adds genindex and modindex.

Documentation analytics

Layer / File(s) Summary
Analytics configuration and internal traffic flag
docs/conf.py, docs/_templates/layout.html
The theme configures a Google Analytics property. The layout template updates a local-storage flag based on the internal query parameter and queues traffic_type=internal when the flag is stored. Errors in the setup are caught, and the parent analytics block is still rendered.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 586cc

The documentation site adds sitemap generation and Google Analytics tracking, including internal-visit tagging. No actionable merge-blocking regression is established.

Pre-merge checks: Passed 5
Check name Status Explanation
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the two primary changes: adding Google Analytics and a sitemap to the documentation site.
Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1 files. (3 skipped: 3 unsupported.)

✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Oct 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (ce85d58) to head (586cc57).

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #237   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           26        26           
  Lines         2008      2008           
=========================================
  Hits          2008      2008           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shenxianpeng
shenxianpeng merged commit ee11d9e into main Oct 18, 2026
42 checks passed
@shenxianpeng
shenxianpeng deleted the chore/google-analytics branch October 18, 2026 20:51
shenxianpeng added a commit to cpp-linter/cpp-linter.github.io that referenced this pull request Oct 18, 2026
)

Follow-up to a review of this site's Google Analytics and Search Console
data. Over the last 90 days roughly 55% of users came from data-center
cities with 0 s engagement. Maintainer visits were about 21% of engaged
sessions and half of the `click_github` key events. Nothing recorded
that a reader copied a workflow or hook. Links shared in chats had no
preview card, and crawlers asking for `/robots.txt` landed on the 404
page.

This PR only changes the site. The Google Analytics side (turning the
Internal Traffic filter on, key events, custom dimensions) and Search
Console are handled separately.

## Before / After (改动前 / 改动后)

None of these changes alters a page's layout. The only new visual is the
social card, shown below.

### Behaviour

| | Before (live site, `origin/main`) | After (this branch) |
|---|---|---|
| Maintainer visits | Counted like any other visit | `?internal=1`
stores a flag in `localStorage`; from then on every event carries
`traffic_type=internal` (sent as `tt=internal`). `?internal=0` removes
it. The project docs under `/cpp-linter-action/` etc. read the same
flag. |
| Copying a code block | No event | `copy_code` event with `snippet`
(section id, or `cl-workflow` for the home page block) and `code_title`
(the file name above the block) |
| Open Graph / Twitter tags | 0 on every page | 13 on every page, with a
1200×630 card |
| 404 page `<title>` | `cpp-linter` (same as a real page) | `Page not
found - cpp-linter` |
| `/robots.txt` | 404 (served the 404 page, which was then counted as a
page view) | 200, `Allow: /` plus the sitemaps of this site and the four
project docs |
| Home page `<title>` | `C/C++ Linting - cpp-linter` | `C/C++ Linter
with clang-format and clang-tidy - cpp-linter` |

### Old URLs that Google Analytics recorded as 404s

`hooks/redirects.py` writes a small redirect page for each one after the
build. Each page has a meta refresh, `location.replace`, a canonical
link and `noindex`.

| Path | Before | After |
|---|---|---|
| `/docs/` | 404 | → `/getting-started/` |
| `/merch/` | 404 | → `/sponsor/#t-shirt-perk` |
| `/inputs-outputs/`, `/examples/`, `/permissions/`,
`/pr-review-caveats/` | 404 | → the same page under
`/cpp-linter-action/` |
| `/cli_args` | 404 | → `/cpp-linter/cli_args.html` |
| `/cpp_linter_rs/`, `/cpp_linter_rs/cli.html` | 404 | →
`/cpp-linter-rs/`, `/cpp-linter-rs/cli/` |

### Evidence: events sent by the real gtag.js

Built this branch with the measurement ID swapped for a fake one
(`G-TESTTEST00`), so nothing reached the real property. I served it
locally and recorded every `g/collect` request in a browser while going
through the pages in this order:

| # | Page opened (in order) | Event | `tt` (traffic_type) |
`ep.snippet` | `ep.code_title` |
|---|---|---|---|---|---|
| 1 | `/getting-started/` (no flag) | page_view | — | | |
| 2 | same page, copy button in "On every pull request" | copy_code | —
| `on-every-pull-request` | `.github/workflows/cpp-linter.yml` |
| 3 | `/getting-started/?internal=1` | page_view | `internal` | | |
| 4 | same page, copy button in "Before every commit" | copy_code |
`internal` | `before-every-commit` | `.pre-commit-config.yaml` |
| 5 | `/` (no parameter, flag still stored) | page_view | `internal` | |
|
| 6 | home page "Copy" button | copy_code | `internal` | `cl-workflow` |
`.github/workflows/cpp-linter.yml` |
| 7 | `/blog/?internal=0` | page_view | — | | |
| 8 | `/blog/` | page_view | — | | |

### Evidence: built `<head>` of the home page

Before (live):

```html
<title>C/C++ Linting - cpp-linter</title>
<!-- no og:* or twitter:* tags -->
```

After (`mkdocs build --strict`):

```html
<title>C/C++ Linter with clang-format and clang-tidy - cpp-linter</title>
<meta property="og:type" content="website">
<meta property="og:site_name" content="cpp-linter">
<meta property="og:title" content="C/C++ Linter with clang-format and clang-tidy">
<meta property="og:description" content="clang-format and clang-tidy for C and C++ pull requests, pre-commit hooks and CI, with the LLVM version you pin.">
<meta property="og:url" content="https://cpp-linter.github.io/">
<meta property="og:image" content="https://cpp-linter.github.io/static/og-image.png">
<meta name="twitter:card" content="summary_large_image">
```

### Social card

Before: no card. After:

![Social
card](https://raw.githubusercontent.com/cpp-linter/cpp-linter.github.io/pr-assets/feature-analytics-and-seo/og-card-after.png)

## Notes

- The flag snippet runs in the `analytics` block, before Material's own
snippet. That way `gtag('set', {traffic_type: 'internal'})` is queued
ahead of the first `page_view`, which rows 3 and 5 above confirm.
- To report on `snippet` and `code_title`, they need to be registered as
event-scoped custom dimensions in Google Analytics. That happens outside
this PR.
- `robots.txt` lists `/cpp-linter/sitemap.xml`, which is added by
cpp-linter/cpp-linter#237. Until that one is deployed, that URL returns
404 to crawlers, which is harmless.
- Companion PRs add the same Google Analytics property and the same flag
to the project docs: cpp-linter/cpp-linter-action#504,
cpp-linter/clang-tools-pip#229, cpp-linter/cpp-linter-rs#502 and
cpp-linter/cpp-linter#237.

## After merging

Open `https://cpp-linter.github.io/?internal=1` once on each device and
browser you use. To check it is working, wait about a day, open any GA4
report, add the comparison "Test data filter name = Internal Traffic"
(it exists while the filter is in Testing), and confirm your own visits
show up there. Only then should the filter be switched to Active.

This branch was successfully deployed

1 active deployment
test-coverage — 586cc578 Deployed Oct 18, 2026 by shenxianpeng via coverage-report #210
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants