UNPKG

skills-ws

Version:

87 agent skills for AI coding assistants — marketing, growth, web3, dev, design & operations. Built for OpenClaw, Claude Code, Cursor, and Codex.

108 lines (86 loc) • 4.27 kB
## Contents - 10. Monitoring & Dashboards - What to Track - GSC API Monitoring Script ## 10. Monitoring & Dashboards ### What to Track | Metric | Tool | Alert Threshold | |--------|------|-----------------| | Indexed pages | GSC → Indexing report / URL Inspection API | Drop >10% week-over-week | | Pages submitted vs. indexed ratio | GSC Indexing report | <70% of submitted URLs indexed | | Avg position by page type | GSC Search Analytics | Decline >5 positions | | Crawl stats / soft 404s | GSC Crawl Stats + server logs | >50% 4xx/soft-404 in crawl | | Thin / near-duplicate pages | Custom crawler | Quality-gate fail or similarity >0.8 (see §6) | | Broken internal links | Screaming Frog / custom | Any internal 404 | | Core Web Vitals (field) | CrUX / GSC | LCP >2.5s, INP >200ms, CLS >0.1 | | Organic traffic by template | GA4 + GSC | Drop >20% month-over-month | Note: **INP (Interaction to Next Paint) replaced FID as a Core Web Vital in March 2024** — track INP, not FID. ### GSC API Monitoring Script Run this weekly (cron). Two things matter for correctness: 1. **Use a rolling window, never hardcoded dates.** GSC Search Analytics data lags ~2–3 days, so query "the 28 days ending 3 days ago" and compare it to the immediately prior 28 days — so the alert is *trend*, not an absolute one-off. 2. **Search Analytics ≠ index status.** A page only appears here once it has had an *impression*. It's a proxy for "indexed and ranking somewhere." For true index status, use the **URL Inspection API** (`urlInspection.index.inspect`, quota ~2,000/day) on a sample, or read the Indexing report in the GSC UI. ```typescript // scripts/monitor-indexing.ts — run weekly via cron. tsx scripts/monitor-indexing.ts import { google } from 'googleapis'; const SITE_URL = process.env.GSC_SITE_URL ?? 'https://example.com'; const DAY = 86_400_000; // GSC data lags; offset the window end by `lagDays`. function rollingWindow(endOffsetDays: number, lengthDays: number) { const end = new Date(Date.now() - endOffsetDays * DAY); const start = new Date(+end - (lengthDays - 1) * DAY); const iso = (d: Date) => d.toISOString().slice(0, 10); // YYYY-MM-DD return { startDate: iso(start), endDate: iso(end) }; } async function queryPageCount( sc: ReturnType<typeof google.searchconsole>, window: { startDate: string; endDate: string }, pathRegex: string, ) { const res = await sc.searchanalytics.query({ siteUrl: SITE_URL, requestBody: { ...window, dimensions: ['page'], dimensionFilterGroups: [{ filters: [{ dimension: 'page', operator: 'includingRegex', expression: pathRegex }], }], rowLimit: 25000, // paginate with startRow if a template exceeds 25k URLs }, }); return res.data.rows?.length ?? 0; } async function checkIndexingHealth() { const auth = new google.auth.GoogleAuth({ keyFile: process.env.GSC_KEY_FILE ?? 'service-account.json', scopes: ['https://www.googleapis.com/auth/webmasters.readonly'], }); const sc = google.searchconsole({ version: 'v1', auth }); // Templates to watch, keyed by URL regex. const templates: Record<string, string> = { locations: '/plumbers/', comparisons: '/compare/', }; const current = rollingWindow(3, 28); // 28 days ending 3 days ago const prior = rollingWindow(31, 28); // the 28 days before that for (const [name, regex] of Object.entries(templates)) { const [nowCount, prevCount, expected] = await Promise.all([ queryPageCount(sc, current, regex), queryPageCount(sc, prior, regex), getExpectedPageCount(name), ]); const indexRatio = expected ? nowCount / expected : 0; const wowDelta = prevCount ? (nowCount - prevCount) / prevCount : 0; console.log( `[${name}] ranking-visible: ${nowCount}/${expected} (${(indexRatio * 100).toFixed(1)}%), ` + `period-over-period: ${(wowDelta * 100).toFixed(1)}%`, ); if (indexRatio < 0.7) console.error(` ⚠ Only ${(indexRatio * 100).toFixed(1)}% of ${name} pages visible in search.`); if (wowDelta < -0.1) console.error(` ⚠ ${name} dropped ${(wowDelta * -100).toFixed(1)}% vs prior period.`); } } checkIndexingHealth().catch((e) => { console.error(e); process.exit(1); }); ``` ---