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.analysisTimestampis when Google ran Lighthouse. -
urlas you supplied it,strategy, andfinalUrl: a changed final URL means a redirect changed and scores may not compare. -
success,scores.performanceand the other threescores, pluslabMetricsto explain drops. -
fieldData.overallCategoryandlighthouseVersion, 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
}
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" }'
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'));
}
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.performanceis a lab estimate. Real-userfieldData, which the README treats as the Google Search signal, isnullfor 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)