GitHub Packages · MIT · Node ≥ 18

render-pulse

Find the CSS and JavaScript that block first paint in your built site, with the defer, async, media or critical-CSS fix for each.

Find render-blocking resources in a built static site: head scripts without defer/async, heavy blocking CSS, @import chains, third-party blocking resources without preconnect, document.write, parser-blocking body scripts, print CSS without media, missing critical CSS. SARIF, GitHub 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/render-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 render-pulse dist --base-url https://example.comrender-pulse — https://example.com1 page(s) at https://example.com HIGH     index.html:2 /: <script src="/js/jquery.js"> (0.0 KB gzip) in <head> blocks rendering [render-blocking-script]HIGH     index.html:7 /: <script src="https://tags.example.org/t.js"> in <head> blocks rendering [render-blocking-script]MEDIUM   css/all.css css/all.css uses @import [render-css-import]MEDIUM   index.html /: render-blocking resource from https://fonts.example.org without <link rel="preconnect"> [render-third-party-blocking]MEDIUM   index.html /: render-blocking resource from https://tags.example.org without <link rel="preconnect"> [render-third-party-blocking]MEDIUM   index.html /: 56.2 KB gzip (195.4 KB raw) of render-blocking CSS (budget 50.0 KB) [render-blocking-css-heavy]
Sample output from the render-pulse README.

Why it exists

Nothing is painted until the browser has downloaded and parsed every render-blocking stylesheet and synchronous script in “<head>”. One forgotten “<script src>” without defer, a 200 kB framework stylesheet or an @import chain can add seconds to First Contentful Paint and LCP on mobile. render-pulse reads your static build output, works out which resources are actually on the critical path (respecting defer, async, “type=module”, media, disabled and alternate stylesheets) and reports each one with a concrete fix.

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

Scripts

classic <script src> in <head> without defer/async (high), synchronous external scripts in the middle of <body>, document.write(), scripts placed before stylesheets.

Stylesheets

gzip size of all render-blocking CSS per page against a budget (default 50 kB), too many blocking stylesheets, @import in CSS files or inline styles, print stylesheets without media="print", and a hint to inline critical CSS when blocking CSS exceeds the 14 kB first round trip.

Third parties

render-blocking resources from other origins without <link rel="preconnect">.

Knows the async patterns

media="print" onload="this.media='all'", module scripts, JSON-LD and template scripts are not reported.

Usage

# Example

 npx render-pulse dist

# Example

 npx render-pulse public --max-css-kb 30 --fail-on medium

# Example

 npx render-pulse _site --format sarif > render.sarif

CI / GitHub Action

permissions:
  contents: read
  security-events: write   # only needed for the SARIF upload
jobs:
  render-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/render-pulse@v1
        with:
          path: dist
          fail-on: high
          sarif: render-pulse.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: render-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)
Render
--max-css-kb <n>Render-blocking CSS budget per page, gzip KB (default 50)
Common
-c, --config <file>JSON config (default: .render-pulse.json or "renderPulse" 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