Skip to content

feat: tag maintainer visits, track code copies and add social cards - #83

Merged
shenxianpeng merged 2 commits into
mainfrom
feature/analytics-and-seo
Oct 18, 2026
Merged

shenxianpeng merged 2 commits into
mainfrom
feature/analytics-and-seo

Conversation

@shenxianpeng

Copy link
Copy Markdown
Member

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):

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

After (mkdocs build --strict):

<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

Notes

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.

- ?internal=1 stores a flag in localStorage and every Google Analytics
  event then carries traffic_type=internal, so the Internal Traffic data
  filter can drop maintainer visits. ?internal=0 removes it. The project
  docs on the same origin read the same flag.
- Copying a code block sends a copy_code event with the section it is
  in (snippet) and the file name above it (code_title).
- Open Graph and Twitter tags on every page, with a 1200x630 card.
- The 404 page is titled "Page not found" instead of "cpp-linter".
- robots.txt lists the sitemaps of this site and the project docs.
- Redirect pages for old URLs that Google Analytics recorded as 404s.
- The home page title names what the site is: a C/C++ linter with
  clang-format and clang-tidy.
@netlify

netlify Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for cpp-linter-github-io ready!

Name Link
🔨 Latest commit 2dc98cb
🔍 Latest deploy log https://app.netlify.com/projects/cpp-linter-github-io/deploys/6ad518efb1bc1ca2f9c98082
😎 Deploy Preview https://deploy-preview-83--cpp-linter-github-io.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@2bndy5

2bndy5 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

I made img-gen specifically for generating social card images when each doc is built. The text rendering is a little off still, so this will suffice.

shenxianpeng added a commit to cpp-linter/cpp-linter-rs that referenced this pull request Oct 18, 2026
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. 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) |
|---|---|---|
| Google Analytics | No gtag on any page | `G-JKH4SXC7HC` on every page
|
| Maintainer visits | — | `traffic_type=internal` while the
`cpp-linter-internal` flag is set (`?internal=1` / `?internal=0` on any
page of the origin) |
| 404 page `<title>` | `Cpp-Linter` (same as the home page) | `Page not
found - Cpp-Linter` |

The overrides live in `docs/overrides/`, next to `docs/docs/`, so they
are not published as pages.

### Evidence: built `<head>` of `index.html`

```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>
```

I did not run the full docs build locally, because it compiles the Rust
CLI generator. The theme changes were built with the same mkdocs 1.6.1 /
mkdocs-material 9.7.7 pins from `docs/pyproject.toml`, with a temporary
config that inherits `docs/mkdocs.yml` and leaves out the gen-files
plugins and the other nav pages. The `Build docs` job in this PR runs
the full build.

## 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
cpp-linter/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 `htmltitle` (a Jinja block name in
`docs/overrides/404.html`).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
  * Added a custom “404 - Not found” page to the documentation site.
* Configured Google Analytics for the documentation site, including
support for distinguishing internally tagged visits.
* Updated the documentation theme configuration to load custom
overrides.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@shenxianpeng
shenxianpeng merged commit 70900d2 into main Oct 18, 2026
7 checks passed
@shenxianpeng
shenxianpeng deleted the feature/analytics-and-seo branch October 18, 2026 21:23
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