GitHub Packages · MIT · Node ≥ 18

thirdparty-pulse

How much does each third-party script cost your pages? Estimated bytes, requests and main-thread time per page and per vendor, checked against budgets.

Third-party script weight and load-cost report for static sites: estimated bytes, requests and main-thread time per page and per vendor, with budgets, plus sync-script, duplicate and preconnect checks. SARIF, Action. Zero deps.

  1. 1 Point the @mrlouiswright scope at GitHub Packages in .npmrc

    @mrlouiswright:registry=https://npm.pkg.github.com
  2. 2 Install

    npm i -D @mrlouiswright/thirdparty-pulse
Installing from GitHub Packages needs a token

Published to GitHub Packages, not npmjs.com. GitHub Packages needs a GitHub token with read:packages to install, even for public packages: add //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} to .npmrc or run npm login --registry=https://npm.pkg.github.com.

~/project — zsh
$ npx thirdparty-pulse dist --base-url https://example.comthirdparty-pulse — https://example.com1 page(s), 1 with third-party content, 6 vendor(s)/origin(s), heaviest page ~465 KB of third-party script HIGH     index.html / loads ~465 KB of third-party script (budget 150 KB): intercom 200 KB, google-tag-manager 90 KB, meta-pixel 75 KB, hotjar 70 KB, cdn.example-widgets.net 30 KB [tp-budget-bytes]HIGH     index.html:5 Synchronous third-party script https://www.googletagmanager.com/gtm.js?id=GTM-ABC123 on / in <head> [tp-sync-script]MEDIUM   index.html /: estimated ~1163 ms of third-party main-thread work (budget 250 ms) [tp-budget-main-thread]MEDIUM   index.html:11 https://static.hotjar.com/c/hotjar-1.js is included 2 times on / [tp-duplicate-script]LOW      index.html www.googletagmanager.com is requested from <head> on / without a preconnect [tp-no-preconnect]LOW      index.html connect.facebook.net is requested from <head> on / without a preconnect [tp-no-preconnect]LOW      index.html static.hotjar.com is requested from <head> on / without a preconnect [tp-no-preconnect]LOW      index.html widget.intercom.io is requested from <head> on / without a preconnect [tp-no-preconnect]LOW      index.html cdn.example-widgets.net is requested from <head> on / without a preconnect [tp-no-preconnect]LOW      index.html:4 preconnect to fonts.unused-cdn.example on /, but nothing on the page loads from it [tp-preconnect-unused]LOW      index.html:8 Intercom (~200 KB) on / [tp-heavy-vendor]INFO     index.html:9 cdn.example-widgets.net script on / is not in the vendor table; assumed 30 KB [tp-unknown-weight] 2 high, 2 medium, 7 low, 1 info✔ Passing (fail-on: none).
Sample output from the thirdparty-pulse README.

Why it exists

Tag managers, chat widgets, pixels and embeds are usually the heaviest JavaScript on a static site, and nobody owns their weight. thirdparty-pulse reads your built HTML, finds every third-party script, inline vendor loader, iframe and asset, estimates its transfer size and main-thread cost from a table of common vendors (or the sizes you configure), and fails CI when a page goes over budget. It also catches the loading mistakes that make third parties expensive: synchronous scripts in “<head>”, the same script included twice, and missing or unused preconnects.

CLI, library and GitHub Action. Node.js 18+, zero runtime dependencies. Works on any static build output (Astro, Next.js “output: 'export'”, Eleventy, Hugo, Jekyll, Docusaurus, VitePress, plain HTML) and runs offline: point it at the folder your build writes. By Louis Wright.

Features

Budgets per page

third-party script KB (maxKb, default 150), requests (maxRequests, default 10) and estimated main-thread time (maxMainThreadMs, default 250 ms, at msPerKb 2.5 ms per compressed KB).

Budgets per vendor

vendorBudgets caps a single vendor (e.g. {"google-tag-manager": 80}).

Weights

a built-in table of ~45 vendors (analytics, tag managers, chat, video, maps, payments, CDNs) with typical compressed sizes; override or add hosts with weights (host, URL glob or vendor id). Unknown hosts get unknownKb (30) and an info finding.

Loading

synchronous third-party scripts (high in <head>, medium in <body>), duplicate includes, heavy vendors that have a facade (YouTube, Maps, chat), early origins without preconnect, and preconnects that nothing uses.

Report

the JSON report lists every vendor/origin with its weight, requests and the number of pages it is on.

Usage

# Example

 npx thirdparty-pulse dist

# Example

 npx thirdparty-pulse _site --max-kb 100 --max-requests 6

# Example

 npx thirdparty-pulse public -f json > third-party.json

CI / GitHub Action

permissions:
  contents: read
  security-events: write   # only needed for the SARIF upload
jobs:
  thirdparty-pulse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with: { persist-credentials: false }
      - run: npm ci && npm run build   # produce the static site first
      - uses: MrLouisWright/thirdparty-pulse@v1
        with:
          path: dist
          fail-on: high
          sarif: thirdparty-pulse.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: thirdparty-pulse.sarif }

Options & flags

FlagDescription
Site
--base-url <url>Public URL of the site, e.g. https://example.com (default: inferred from canonical/og:url)
--include <globs>Only check these pages (file or URL path globs, comma-separated)
--exclude <globs>Skip these pages
--clean-urlsPages are served without .html (about.html -> /about)
Budgets
--max-kb <n>Third-party script KB per page (default 150)
--max-requests <n>Third-party requests per page (default 10)
--max-main-thread <ms>Estimated third-party main-thread ms per page (default 250)
Common
-c, --config <file>JSON config (default: .thirdparty-pulse.json or "thirdpartyPulse" in package.json)
-f, --format <fmt>text (default) | json | sarif | markdown
--json <file>Also write a JSON report
--sarif <file>Also write SARIF 2.1.0
--markdown <file>Also write Markdown (e.g. for a job summary)
--fail-on <sev>critical | high | medium | low | info | none (default: high)
--list-rulesPrint rules and default severities
--no-fixesHide fix suggestions in text output
--no-colorDisable colours

Full reference, config file format and API in the README.

Support this project

Support open-source security tools

I build free, open-source tools that help developers ship safer websites. Every coffee helps keep them maintained, documented and free for everyone.

Buy me a coffee (opens Stripe in a new tab)

Secure checkout by Stripe

Portrait of Louis Wright

About the author

Louis Wright

Cyber security researcher and software engineer based in the UK.

More tools by Louis Wright

58 tools

Sentinels security scanners & CI gates · 20