DEV Community

Boehner
Boehner

Posted on

How to Monitor Page Load Time Across Your Entire Site (30 Lines of Node.js)

You can't fix what you don't measure. Most teams check page speed occasionally — run Lighthouse on the homepage, feel good, move on. Meanwhile, three product pages and two blog posts are loading in 4+ seconds and quietly tanking conversion.

Here's a script that checks every page you care about, flags the slow ones, and can run automatically every week in GitHub Actions.

What the SnapAPI analyze endpoint returns

The /v1/analyze endpoint runs a real Chromium browser against any URL and returns structured data — including load_time_ms, which is the time from request start to networkidle (fully loaded, no pending requests).

// npm install snapapi-sdk
const { SnapAPI } = require('snapapi-sdk');
const client = new SnapAPI(process.env.SNAPAPI_KEY);

const data = await client.analyze('https://yoursite.com/pricing');
console.log(data.load_time_ms);   // e.g. 2341
console.log(data.word_count);     // e.g. 1847
console.log(data.technologies);   // ["React", "Vercel", "Cloudflare"]
console.log(data.primary_cta);    // "Start free trial"
Enter fullscreen mode Exit fullscreen mode

One call. Real browser. No Lighthouse CLI, no Puppeteer setup, no browser binary in CI.

The monitoring script

// page-performance-monitor.js
// Checks a list of pages and flags any with load_time > threshold.
// Usage: SNAPAPI_KEY=key node page-performance-monitor.js

const { SnapAPI } = require('snapapi-sdk');
const fs          = require('fs');

const client    = new SnapAPI(process.env.SNAPAPI_KEY);
const THRESHOLD = 3000; // ms — flag any page over 3 seconds

const PAGES = [
  { url: 'https://yoursite.com',          label: 'Homepage' },
  { url: 'https://yoursite.com/pricing',  label: 'Pricing' },
  { url: 'https://yoursite.com/features', label: 'Features' },
  { url: 'https://yoursite.com/blog',     label: 'Blog index' },
  // add more...
];

async function checkPage({ url, label }) {
  try {
    const data = await client.analyze(url, { screenshot: false });
    return {
      label, url,
      load_ms:     data.load_time_ms,
      word_count:  data.word_count,
      technologies: data.technologies,
      primary_cta: data.primary_cta,
      slow:        data.load_time_ms > THRESHOLD,
    };
  } catch (err) {
    return { label, url, error: err.message, slow: false };
  }
}

async function main() {
  console.log(`Checking ${PAGES.length} pages (threshold: ${THRESHOLD}ms)...\n`);

  const results = await Promise.all(PAGES.map(checkPage));
  const slow    = results.filter(r => r.slow);
  const errors  = results.filter(r => r.error);

  // Print results table
  console.log('Page'.padEnd(20), 'Load (ms)'.padEnd(12), 'Words'.padEnd(8), 'Status');
  console.log('-'.repeat(60));
  for (const r of results) {
    const status = r.error ? '⚠ ERROR' : r.slow ? '🔴 SLOW' : '✅ OK';
    const load   = r.error ? 'N/A' : String(r.load_ms);
    console.log(r.label.padEnd(20), load.padEnd(12), String(r.word_count || '').padEnd(8), status);
  }

  console.log(`\n${slow.length} slow page(s), ${errors.length} error(s)`);

  // Save JSON results for GitHub Actions / Slack
  const output = { checked_at: new Date().toISOString(), results, slow_count: slow.length };
  fs.writeFileSync('performance-results.json', JSON.stringify(output, null, 2));

  if (slow.length > 0) process.exit(1); // fail CI if slow pages found
}

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

Sample output:

Checking 4 pages (threshold: 3000ms)...

Page                 Load (ms)    Words    Status
------------------------------------------------------------
Homepage             1842         1204     ✅ OK
Pricing              2341         987      ✅ OK
Features             3891         2103     🔴 SLOW
Blog index           4102         3847     🔴 SLOW

2 slow page(s), 0 error(s)
Enter fullscreen mode Exit fullscreen mode

Add it to GitHub Actions

# .github/workflows/page-speed.yml
name: Weekly Page Speed Check

on:
  schedule:
    - cron: '0 9 * * 1'  # Every Monday at 9am
  workflow_dispatch:      # Also allows manual trigger

jobs:
  check-speed:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm install snapapi-sdk
      - name: Run page speed check
        run: node page-performance-monitor.js
        env:
          SNAPAPI_KEY: ${{ secrets.SNAPAPI_KEY }}
      - name: Post to Slack if slow pages found
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: '{"text": "⚠️ Slow pages detected in weekly speed check. Check the Actions log."}'
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Enter fullscreen mode Exit fullscreen mode

Now every Monday at 9am, the workflow runs. If any page exceeds 3 seconds, the step fails and Slack gets a message. Fix the page, push, see the green check.

What else the analyze endpoint gives you

load_time_ms is one field. The full response is useful for page health audits beyond just speed:

Field What it tells you
word_count Content depth — blog posts under 300 words rarely rank
technologies Tech debt signal — 5+ marketing pixels? Check your bundle
primary_cta Is the CTA present after a deploy? Assert it in CI
navigation Did a nav item disappear? Catch it before users do
forms Sign-up form still rendering on the pricing page?

You can write assertions for any of these alongside the speed check:

// Assert the pricing page CTA is still there after deploy
if (!data.primary_cta || !data.primary_cta.toLowerCase().includes('free')) {
  console.error('FAIL: Pricing page CTA missing or changed');
  process.exit(1);
}
Enter fullscreen mode Exit fullscreen mode

Compared to running Lighthouse manually

This script Manual Lighthouse
Setup npm install snapapi-sdk CLI install + Chrome config
Run all pages Promise.all() One at a time
CI integration 10 lines of YAML Custom Docker image
Structural data (CTA, nav) Included Not available
Screenshot Optional param Separate run
Cost Free tier: 100 pages/month Free

The main difference: this gives you real browser load time plus structural page health in one call. Lighthouse gives you more granular network waterfall data. Use both if you need the waterfall; use this if you need automated monitoring across many pages.

Free API key

100 calls/month, no credit card, active in 30 seconds.

snapapi.tech/start

Top comments (0)