Boring Docs

SEO, Analytics, Blog — Ship + Measure + Write

Metadata helpers, self-hosted beacon, markdown posts.

SEO, Analytics, Blog

SEO

libs/seo.js:getSEOTags({ title, description, keywords, openGraph, canonicalUrlRelative, extraTags }) — mounted in app/layout.js, overridable per page. Structured data via renderSiteSchemas() (Organization/WebSite/FAQ/Breadcrumb) and per-article JSON-LD. Sitemap: next-sitemap.config.js (SITE_URL, excludes images/icons). Keep ratings/prices truthful — fake aggregate values risk rich-result penalties.

Analytics (self-hosted, separate DB)

  • Write: track(event, properties, { userId, url }) from providers/analytics/index.js. Cookie session bsk_sid, 30-min idle, anon→authed promotion, 64KB property cap, never throws.
  • Read: providers/analytics/stats.js (overview, topPages/Events/Countries, rangeMetrics, dailySeries, funnels). Dashboard at /dashboard/analytics.
  • Beacon: POST /api/analytics/track (unauthenticated by design; anonymous counts). Validate lengths server-side; add in-app rate limits if abused.
  • Storage: analytics.sqlite next to app DB (WAL). Prune: npm run analytics:cleanup (scripts/analytics-cleanup.mjs, default 90 days, cron daily). Include it in volume snapshots.

Blog

libs/blog.js reads data/blog/<slug>.md (frontmatter title, description, date, tags), renders via gray-matter + marked once per mtime (in-memory cache). Routes: /blog, /blog/[articleId], author/category pages. Body rendered with dangerouslySetInnerHTML — safe only because files are repo-owned; sanitize before rendering if authorship ever opens up.