---
title: Website Analytics
tags: [website, analytics]
---

# Website Analytics

Tydora's documentation site and landing page are static sites hosted on GitHub Pages (`zuorn.github.io/Tydora`) — there is no backend or database, so server logs cannot be used for traffic statistics. This article explains how to add visitor tracking to the site using client-side analytics, measuring PV (page views) and UV (unique visitors).

## Why Track Visitors

- Understand which pages are most popular and identify trending documentation
- See where visitors come from and how they navigate, guiding content improvements
- Measure the impact of releases and announcements on traffic

## Choosing an Analytics Service

Client-side analytics loads a small tracking script on every page, which reports visit data to an analytics service. Common free options:

| Service | Cost | PV/UV | Privacy | Access in China | Maintenance |
|---------|------|-------|---------|-----------------|-------------|
| **Umami** (Cloud free tier / self-hosted) | Free tier | ✅ | No cookies, privacy-friendly | Moderate | Medium |
| Baidu Analytics | Free | ✅ | China-compliant | Good | Low |
| Cloudflare Web Analytics | Free | ✅ | No cookies | Moderate | None |
| Plausible | From $9/mo | ✅ | No cookies | Poor | Low |
| Google Analytics 4 | Free | ✅ | Requires cookie banner | Poor | Low |

This project uses **Umami** (Umami Cloud free tier): open source, no lock-in, cookie-free and privacy-friendly, with a tiny script that does not affect loading performance, and full ownership of the data.

## Setup Steps

### 1. Create a Website on Umami

1. Visit [umami.is](https://umami.is) and sign in with your GitHub account (the Umami Cloud free tier includes 10k events/month, enough for a personal documentation site)
2. Click **Add website**:
   - **Name**: any name, e.g. `Tydora`
   - **Domain**: enter `zuorn.github.io` — **do not include the `/Tydora` path** (see FAQ below)
3. After saving, open the website details and copy the **Website ID** (a UUID like `56c781b4-...`)

### 2. Save the Tracking Script

Create `website/analytics/snippet.html` and paste the script provided by Umami, adding an `id="t-analytics"` attribute (used by the injector for idempotent de-duplication):

```html
<!-- Tydora Analytics · Umami Cloud（https://umami.is） -->
<script id="t-analytics" defer src="https://cloud.umami.is/script.js" data-website-id="YOUR_WEBSITE_ID"></script>
```

> This file is the **single point of maintenance** for tracking: to switch services later (e.g. Baidu Analytics), just replace the contents of this one file — no other code changes are needed.

### 3. Write the Injection Script

The documentation site is generated by `markdown-publish` (a static site generator) and does not support built-in analytics injection, so we walk through all HTML files after the build and insert the snippet before `</head>`.

Create `scripts/inject-analytics.mjs`:

```js
/**
 * Injects the tracking snippet before </head> in every HTML file under website/site/**
 * Runs after copy-landing, so both landing pages and docs pages are covered
 * The snippet is maintained in website/analytics/snippet.html (switch services by editing this one file)
 * Idempotent: skips pages that already contain id="t-analytics", so repeated builds never double-inject
 */
import { readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
import { join, resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const siteDir = resolve(__dirname, "../website/site");
const SNIPPET_FILE = resolve(__dirname, "../website/analytics/snippet.html");

let snippet = "";
try {
  snippet = readFileSync(SNIPPET_FILE, "utf-8").trim();
} catch {
  console.log("⚠️ website/analytics/snippet.html not found, skipping injection");
  process.exit(0);
}

function walk(dir) {
  for (const entry of readdirSync(dir)) {
    const full = join(dir, entry);
    if (statSync(full).isDirectory()) walk(full);
    else if (full.endsWith(".html")) inject(full);
  }
}

function inject(file) {
  const html = readFileSync(file, "utf-8");
  if (!html.includes("</head>") || html.includes('id="t-analytics"')) return;
  const out = html.replace("</head>", `  ${snippet}\n</head>`);
  writeFileSync(file, out, "utf-8");
  console.log(`✅ analytics injected → ${file.replace(siteDir, "site")}`);
}

walk(siteDir);
```

The idempotency logic: pages that already contain `id="t-analytics"` are skipped, so repeated local builds never produce duplicate injections.

### 4. Hook into the Build Pipeline

Edit `package.json` to append the injection step to `postdocs:build`:

```json
"postdocs:build": "node scripts/copy-landing.mjs && node scripts/inject-analytics.mjs"
```

npm automatically runs `postdocs:build` after `docs:build` finishes. The build chain becomes:

```
docs:build（generates site/）
  → copy-landing（copies landing pages into the site root）
  → inject-analytics（injects tracking into all HTML）
```

### 5. Update the CI Trigger Paths

Edit `.github/workflows/deploy-docs.yml` to add `scripts/inject-analytics.mjs` to the push trigger paths, so changes to the injector trigger a redeploy:

```yaml
on:
  push:
    branches: [main]
    paths:
      - 'website/**'
      - 'scripts/copy-landing.mjs'
      - 'scripts/inject-analytics.mjs'
      - 'package.json'
      - '.github/workflows/deploy-docs.yml'
```

> `website/analytics/**` is already covered by `website/**`, so it does not need a separate entry.

## Verification

### Local Build

```bash
npm run docs:build
```

Output similar to the following means the injection succeeded:

```
✅ analytics injected → site/index.html
✅ analytics injected → site/en/index.html
...
```

Open `website/site/index.html` and confirm there is a `<script id="t-analytics" ...>` before `</head>`, and that rebuilding does not double-inject.

### After Deployment

1. Push the code and wait for GitHub Actions to finish (about 2-5 minutes); confirm the deployment status is Active
2. Open `https://zuorn.github.io/Tydora/` and press `F12` to open Developer Tools
3. Switch to the **Network** panel, refresh the page, and search for `umami` — seeing requests to `cloud.umami.is` (e.g. `/api/send`) means tracking is active
4. After a few visits, the Umami dashboard will show PV/UV, referrers, top pages, and more

## FAQ

### Q: Why can't I enter `zuorn.github.io/Tydora` in the Umami Domain field?

The Domain field only matches the host, not paths. Enter `zuorn.github.io` — the `/Tydora` part is ignored and does not affect tracking ownership:

- Umami only receives reports from pages that actually load the tracking script. Other GitHub Pages projects without the script never report, so data stays clean
- Page URLs in reports still include the full path (e.g. `/Tydora/en/...`), so you can filter by path

### Q: When does tracking start counting?

Only new visits after deployment are counted. Visitors who visited before the deploy and still have the old page cached will only be recorded after they refresh and load the new page; historical visits are not backfilled.

### Q: Why can't I see requests in the Network panel?

Check two things:

1. Whether the deployed page source contains `<script id="t-analytics" ...>` before `</head>`
2. Whether the Domain in the Umami dashboard is `zuorn.github.io` (without the path)

### Q: How do I switch analytics services?

Edit only `website/analytics/snippet.html` and replace it with the new provider's script — the injection mechanism requires no changes.

## Related Documents

- [[09-blog/Auto-Update-Configuration]] — Auto update configuration
- [[09-blog/Publish-to-Microsoft-Store]] — Publish to Microsoft Store
