Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/syhily/yufan.me/llms.txt

Use this file to discover all available pages before exploring further.

yufan.me ships with first-party analytics backed entirely by Postgres — no third-party tracking scripts, no data leaving your server. Every page visit is ingested, enriched, and stored so you own the full dataset and can query it directly if you need to.

How data is collected

Visits are recorded through a Hono resource endpoint that fires asynchronously on every page render, so analytics never block the response. The pipeline applies a layered bot filter before anything is written:
  1. Bot detection — the isbot library checks the User-Agent string against a registry of ~6 000 known bots. The UA parser (ua-parser-js) provides a second check, catching any crawler, spider, or fetcher that isbot misses.
  2. Prefetch suppression — requests with a Purpose: prefetch or Sec-Purpose: prefetch header are discarded before bot detection runs.
  3. Geo enrichment — if MAXMIND_DB_PATH is set, the visitor’s IP is looked up in the GeoLite2-City database for country, region, city, and coordinates.
Bot rows are excluded from all dashboards.

What is tracked

Each access_log row captures the following fields:
FieldDescription
pathRequest path (no query string)
refererRaw Referer header
referer_hostParsed hostname from the referrer
browser / browser_versionParsed from User-Agent via ua-parser-js
os / os_versionOperating system parsed from User-Agent
device / device_typeDevice model and type (desktop, mobile, tablet, …)
visitor_hashSHA-256 of IP + daily_salt, truncated to 32 hex chars — identifies unique visitors without storing raw IPs
ipRaw client IP (stored but not exposed in dashboard queries)
languagePrimary language tag from Accept-Language
country / region / cityGeo fields from MaxMind (all NULL if not configured)
latitude / longitude / timezoneGeo coordinates and timezone from MaxMind
tsVisit timestamp
IP addresses are stored in the raw column for operational purposes, but visitor identity in dashboards is derived from the daily-rotated visitor_hash, not the raw IP. The salt rotates every 24 hours, so the hash cannot be used to track individuals across days.

Dashboard sections

Navigate to Admin → Analytics to access the dashboards.

Overview

The overview tab shows aggregate metrics for a configurable time range:
  • Total visits and unique visitors (counted by visitor_hash)
  • Top pages by visit count
  • Top referrers by hostname
  • Browser breakdown — share of visits per browser
  • OS breakdown — share of visits per operating system

Realtime

The realtime tab shows:
  • Live visitor count — active visitors in the last few minutes
  • Recent page views — a live feed of the most recent requests

MaxMind geo-enrichment

Geo columns (country, region, city, latitude, longitude, timezone) are NULL by default. To enable them:
  1. Download the free GeoLite2-City database from MaxMind (requires a free account).
  2. Place the .mmdb file somewhere accessible to the running process.
  3. Set the environment variable:
MAXMIND_DB_PATH=/path/to/GeoLite2-City.mmdb
The lookup is performed in-process on every enriched event. No external network call is made at runtime — the entire database is read from the local file.

Excluding admin visits

By default, visits made while logged in as an admin are excluded from analytics. This prevents your own browsing from inflating visitor counts. To include admin visits — useful during local testing to verify the pipeline is working — set:
ANALYTICS_TRACK_ADMIN=true
Remove or unset this variable in production.
Analytics data is stored in the access_log table. If you have TimescaleDB installed, migration 20260514000003_access_log_timescale converts it to a hypertable, which improves time-series query performance at scale. yufan.me works correctly without TimescaleDB — the migration is entirely optional.

Build docs developers (and LLMs) love