DEV Community

Boehner
Boehner

Posted on

How to Monitor Page Load Time and Core Web Vitals Across Your Entire Site (Node.js)

Stop guessing your site speed — monitor it systematically. One script, any number of pages, run it weekly.


The problem with Lighthouse

Lighthouse is great for debugging one page. But when you're managing a SaaS landing page, 20 blog posts, and an e-commerce catalog, "run Lighthouse manually" isn't a workflow — it's wishful thinking.

You need:

  • All your pages checked on a schedule
  • A flag when any page exceeds your latency threshold
  • Zero manual steps

Here's how to build that in about 30 lines of Node.js.


What the analyze endpoint returns

We'll use the SnapAPI analyze endpoint, which headlessly loads a page and returns structured performance + content data:

{
  "url": "https://example.com",
  "load_time_ms": 1842,
  "word_count": 1204,
  "technologies": ["React", "Vercel", "Google Analytics"],
  "buttons": ["Get started", "Sign in", "View demo"],
  "forms": [{ "id": "signup", "fields": 3 }],
  "headings": ["Why Example beats the competition", "Pricing"],
  "links": ["https://example.com/pricing", "https://example.com/about"],
  "cta": "Get started"
}
Enter fullscreen mode Exit fullscreen mode

The key fields for performance monitoring:

Field What it tells you
load_time_ms Full page load to network idle (real browser, not simulated)
word_count Ballpark for content sprawl — bloated pages load slower
technologies Detect heavy JS frameworks, tracking pixels, etc.
buttons / forms CTA presence — catch regression when a deploy breaks your hero CTA

The 30-line script

// monitor-pages.js
const SNAPAPI_KEY = process.env.SNAPAPI_KEY;
const THRESHOLD_MS = 3000; // flag anything over 3 seconds

const PAGES = [
  "https://yoursite.com",
  "https://yoursite.com/pricing",
  "https://yoursite.com/blog",
  "https://yoursite.com/features",
  // add as many as you like
];

async function analyzePage(url) {
  const res = await fetch(`https://snapapi.tech/analyze?url=${encodeURIComponent(url)}&apiKey=${SNAPAPI_KEY}`);
  if (!res.ok) throw new Error(`${url}${res.status}`);
  return res.json();
}

async function runMonitor() {
  const results = await Promise.all(PAGES.map(analyzePage));
  const slow = results.filter(r => r.load_time_ms > THRESHOLD_MS);

  console.log(`\n✅ Checked ${results.length} pages`);
  console.log(`⚠️  Slow pages (>${THRESHOLD_MS}ms): ${slow.length}`);

  if (slow.length > 0) {
    console.log("\nSlow pages:");
    slow.forEach(r => console.log(`  ${r.load_time_ms}ms — ${r.url}`));
    process.exit(1); // non-zero exit for CI/GitHub Actions
  }
}

runMonitor().catch(err => { console.error(err); process.exit(1); });
Enter fullscreen mode Exit fullscreen mode

Run it:

SNAPAPI_KEY=your_key node monitor-pages.js
Enter fullscreen mode Exit fullscreen mode

Output:

✅ Checked 4 pages
⚠️  Slow pages (>3000ms): 1

Slow pages:
  4231ms — https://yoursite.com/blog
Enter fullscreen mode Exit fullscreen mode

Adding a Slack alert

Replace the console.log block with a Slack webhook call when slow pages are found:

async function notifySlack(slowPages) {
  const lines = slowPages.map(r => `• *${r.load_time_ms}ms* — ${r.url}`).join("\n");
  await fetch(process.env.SLACK_WEBHOOK_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      text: `🐌 *Page speed alert:* ${slowPages.length} slow page(s)\n${lines}`
    })
  });
}
Enter fullscreen mode Exit fullscreen mode

Then call it inside runMonitor:

if (slow.length > 0) {
  await notifySlack(slow);
  process.exit(1);
}
Enter fullscreen mode Exit fullscreen mode

Automating it with GitHub Actions

Create .github/workflows/page-speed.yml:

name: Page Speed Monitor

on:
  schedule:
    - cron: '0 9 * * 1'  # Every Monday at 9am UTC
  workflow_dispatch:

jobs:
  monitor:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-node@v3
        with:
          node-version: '20'

      - name: Run page speed monitor
        env:
          SNAPAPI_KEY: ${{ secrets.SNAPAPI_KEY }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        run: node monitor-pages.js
Enter fullscreen mode Exit fullscreen mode

Add SNAPAPI_KEY and SLACK_WEBHOOK_URL to your repository secrets. Now every Monday morning, you'll get a Slack ping if any page exceeds your threshold — or silence if everything's fine.


Real-world use cases

SaaS landing pages
Your homepage, pricing page, and signup flow are conversion-critical. A 4-second load on /pricing is quietly killing conversions. Set the threshold at 2500ms and get alerted the moment a deploy introduces a regression.

E-commerce category pages
Category pages are high-traffic, often image-heavy, and tend to balloon over time as products are added. Running weekly analysis catches the creep before it compounds.

Blog post audits
Blog posts get updated, embedded widgets get added, images get re-uploaded without compression. A weekly scan across your 50 most-trafficked posts will surface the ones that have gone soft.

Agency client monitoring
If you manage multiple client sites, add all their URLs to a single list and send each client a weekly report. The technologies field also lets you confirm their CDN, analytics, and tag manager are still loaded correctly after deploys.


Compared to running Lighthouse manually

Lighthouse (manual) This script
Pages per run 1 Unlimited
Automation None GitHub Actions / cron
Slack alerts No Yes
Tech stack detection No Yes
CTA regression detection No Yes
Setup time 0 (built into Chrome) ~15 minutes
Weekly cost $0 ~$0.04 (for 20 pages)

Lighthouse is still useful for deep dives — waterfall charts, render-blocking resource diagnosis, etc. But for monitoring, the script above wins on every axis except initial familiarity.


Extending it: full content audit

The same endpoint surfaces word_count, headings, buttons, and forms. You can extend the monitor to also catch:

  • Missing CTA: if (!r.cta) flag(r.url, "no CTA detected")
  • Thin content: if (r.word_count < 300) flag(r.url, "thin page")
  • Broken forms: if (r.forms.length === 0 && url.includes("/signup")) flag(...)

One API call, one page load — you get performance, content, and structure all at once.


Get started

SnapAPI has a free tier — 100 calls/month, no credit card. The analyze endpoint is included. If you're monitoring more than a few pages weekly, the paid plan starts at $9/month for 1,000 calls.

Full docs: snapapi.tech/docs#analyze

The script above is self-contained — drop in your PAGES array, add your key, and you're monitoring in under 15 minutes.

Top comments (0)