DEV Community

Cover image for Weekly PageSpeed monitoring in Google Sheets, with an alert that ignores one-off dips
Joshua Smith
Joshua Smith

Posted on

Weekly PageSpeed monitoring in Google Sheets, with an alert that ignores one-off dips

A one-off PageSpeed check shows how a page scored today. A weekly one shows when something broke. You need a schedule, a sheet that only grows, and an alert rule that does not fire on noise.

Audit templates, not every URL

Pages built from one template share their code, so one representative page per template speaks for the rest: the home page, a listing page, a detail page, a form or contact page, and any landing page you pay traffic to. A handful per site is enough; auditing every URL multiplies cost and noise, not findings.

Pick fixed URLs ("latest post" changes every week) that Google can fetch publicly. Monitor mobile, which is what Google ranks on, and add desktop only where the audience is mostly desktop. Each device is its own series; never average them.

What each history row needs

The sheet is the history, so every row must describe itself:

  • fetchedAt: when the row was recorded. Failed rows have it too, so sort on it. analysisTimestamp is when Google ran Lighthouse.
  • url as you supplied it, strategy, and finalUrl: a changed final URL means a redirect changed and scores may not compare.
  • success, scores.performance and the other three scores, plus labMetrics to explain drops.
  • fieldData.overallCategory and lighthouseVersion, to check whether a step change matches a Lighthouse upgrade.

Run on the same weekday and hour with the same input.

An alert rule that survives noise

Lighthouse scores wobble: two runs of the same page can differ by a few points, and some weeks a page fails to load. Alerting on one reading trains you to ignore the alert. Instead:

  • Alert when a page's two most recent usable readings are both below its threshold.
  • Set the threshold from data: wait a few weeks, then sit below the normal range by more than the wobble.
  • A failed audit is no reading, not a 0 or a 100. Alert separately when two runs in a row return nothing for a page.

The price is delay: the alert comes a week after the first bad reading. Too slow? Run twice a week with the same rule.

Run the audits on a schedule

Best Damn PageSpeed Insights Audit calls Google's official PageSpeed Insights API. Save an input like this as a task in your Apify account and give it a weekly schedule, for example the cron expression 0 6 * * 1:

{
    "urls": [
        "https://example.com/",
        "https://example.com/category/shoes",
        "https://example.com/product/sample",
        "https://example.com/contact"
    ],
    "strategy": "mobile",
    "categories": ["performance", "accessibility", "best-practices", "seo"],
    "includeAuditDetails": false
}
Enter fullscreen mode Exit fullscreen mode

Keep includeAuditDetails off so rows stay flat; when an alert fires, re-run a flagged page once with it on to see its top opportunities. Paste your own free Google API key into apiKey for a dedicated 25,000 audits a day. The shared default key can run out and return free rate-limited records, which leave gaps.

From your own cron, one call returns a short list's rows:

curl -X POST "https://api.apify.com/v2/acts/josh99smith~pagespeed-insights-audit/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "urls": ["https://example.com/", "https://example.com/contact"], "strategy": "mobile" }'
Enter fullscreen mode Exit fullscreen mode

Append to a sheet and chart it

The Integrations tab lists Google Sheets, Make, Zapier and n8n. Whichever you use, trigger it when a run succeeds, add rows instead of replacing the sheet, name the tab history, and check that nested fields like scores.performance get their own columns.

For the chart, make a pivot table: fetchedAt grouped by day as rows, url as columns, average scores.performance as values, filtered to one strategy. Insert a line chart; failed audits leave gaps, not zeros. Add a moving-average trendline, as the Actor's README advises for trend reports.

The alert script

Open Extensions, then Apps Script. Paste this, set EMAIL, change COL to your header names (keep the order), and add a weekly time-driven trigger for weeklyCheck a few hours after the Apify schedule. It repeats weekly until the page recovers.

const LIMITS = { '*': 80 };  // placeholder; override: 'https://example.com/heavy (mobile)': 60
const EMAIL = 'you@example.com';
const COL = ['fetchedAt', 'url', 'strategy', 'success', 'scores.performance'];

function findAlerts(values) {
  const ix = COL.map(c => values[0].indexOf(c));
  if (ix.includes(-1)) throw new Error('Missing column');
  const [when, url, dev, success, score] = ix;
  const series = {}, alerts = [];
  for (const r of values.slice(1)) {
    const t = +new Date(r[when]);
    if (!r[url] || isNaN(t)) continue;
    const perf = r[score] === '' ? NaN : Number(r[score]);
    const ok = String(r[success]).toLowerCase() === 'true' && Number.isFinite(perf);
    const key = r[url] + ' (' + r[dev] + ')';
    (series[key] = series[key] || []).push({ t, perf, ok });
  }
  for (const key in series) {
    const rows = series[key].sort((a, b) => b.t - a.t);
    const limit = LIMITS[key] || LIMITS['*'];
    const good = rows.filter(r => r.ok).slice(0, 2);
    if (rows.length > 1 && !rows[0].ok && !rows[1].ok)
      alerts.push(key + ': no reading in the last two runs');
    else if (good.length === 2 && good.every(r => r.perf < limit))
      alerts.push(key + ': ' + good[1].perf + ', then ' + good[0].perf);
  }
  return alerts;
}

function weeklyCheck() {
  const sheet = SpreadsheetApp.getActive().getSheetByName('history');
  const alerts = findAlerts(sheet.getDataRange().getValues());
  if (alerts.length) MailApp.sendEmail(EMAIL, 'PageSpeed alert', alerts.join('\n'));
}
Enter fullscreen mode Exit fullscreen mode

A blank score is skipped, never read as 0.

Cost and limits

The price is $0.004 per successfully audited URL and strategy. Assuming 3 sites with 6 templates each (18 URLs) and every audit succeeds:

  • Mobile only: 18 audits, $0.072 a week, $3.74 over 52 weeks.
  • Mobile and desktop: 36 audits, $0.144 a week, $7.49 over 52 weeks.
  • Twice a week doubles either figure.

Failed audits are free, so these are ceilings. A run stops at the maximum cost you set, so set it just above the weekly figure. Your Apify plan itself is not included.

Limits:

  • scores.performance is a lab estimate. Real-user fieldData, which the README treats as the Google Search signal, is null for low-traffic pages.
  • Pages must be public. Staging, localhost and login pages fail with http-error: free, but a gap, not a good score.

If this saves you time, a review on the Store page helps other people find it, and a star on GitHub helps too.

Disclosure: I built this Actor. Source: github.com/josh99smith/pagespeed-insights-audit. PageSpeed Insights and Lighthouse are Google trademarks; the Actor is not affiliated with Google.

Top comments (0)