Self-hosted critical CSS for WordPress - real headless Chrome, no SaaS.
751
Self-hosted critical CSS generator for WordPress. No metered quota. No SaaS. Your own container, your own control.
Full documentation, source, and issue tracker live on GitHub: github.com/solarssk/wp-critical-cssβ .
WP Rocket's Remove Unused CSS and QUIC.cloud's free tier both solve the same problem - shipping only the CSS a page actually needs above the fold - behind a paid plan or a metered quota. This image does the same job with a container you run yourself: a real headless Chrome renders each URL and extracts the above-the-fold CSS for mobile and desktop separately, and a small companion WordPress plugin inlines it and defers the rest.
save_post hook), or the daily sitemap sweep picks it up.<style id="wpcc-critical-css">; the theme's regular stylesheets defer via the standard media="print" swap.| Feature | What it does | |
|---|---|---|
| πΌοΈ | Real browser extraction | Headless Chrome, not a static CSS analyzer - separate mobile/desktop output. |
| β‘ | Regenerate on save | A save_post hook queues regeneration the moment a post or page changes. |
| πΊοΈ | Sitemap sweep | A daily cron job inside the container walks the sitemap so untouched pages still stay covered. |
| π | SSRF-hardened by default | Every fetch (page render, sitemap, sub-sitemaps, redirects) is blocked from reaching private/reserved addresses. |
| π | One shared secret | WPCC_SHARED_SECRET gates both directions, compared in constant time. |
| π¦ | Bounded queue | MAX_QUEUE_LENGTH-capped; a full queue returns 503 + Retry-After instead of silently dropping requests. |
| Layer | Technologies | |
|---|---|---|
| π’ | Service runtime | Node.js 24 (ESM), Express 5, node-cron 4 |
| π | Rendering | Puppeteer + criticalβ (via penthouse-esm) |
| π | WordPress plugin | PHP 7.4+, WordPress 6.0+ |
| π‘οΈ | CI/CD security | CodeQL, Semgrep, Trivy (CRITICAL gate + weekly re-scan), SonarCloud, gitleaks, SBOM + signed build provenance |
Add to your existing WordPress docker-compose.yml:
services:
critical-css-service:
image: solarssk/wp-critical-css:latest
restart: always
init: true
cap_drop: [ALL]
read_only: true
tmpfs:
- /tmp
- /home/pptruser/.cache/puppeteer/tmp
env_file:
- .env
networks:
- your_wordpress_network
.env needs (see .env.exampleβ ):
SHARED_SECRET - generate with openssl rand -hex 32WP_RECEIVER_URL - your WordPress REST endpointALLOWED_HOSTNAME - your site's hostname (only this hostname is ever rendered)SITE_SITEMAP_URL - your sitemap index URLAdd the same secret to wp-config.php:
define( 'WPCC_SHARED_SECRET', '<same value as SHARED_SECRET above>' );
Then install the companion WP Critical CSS plugin from a GitHub releaseβ - upload the zip via Plugins > Add New Plugin > Upload Plugin, no server file access needed. Full walkthrough: docs/DEPLOYMENT.mdβ .
solarssk/wp-critical-css:X.Y.Z # pinned release, e.g. 0.2.2
solarssk/wp-critical-css:X.Y # rolling minor line, e.g. 0.2
solarssk/wp-critical-css:latest
Built from ghcr.io/puppeteer/puppeteer, pinned by digest. Every image passes a Trivy CRITICAL-vulnerability gate before it's pushed, and the same digest is also published to ghcr.io/solarssk/wp-critical-css if you prefer GHCR. Single-architecture (linux/amd64) - not a multi-arch manifest.
Content type
Image
Digest
sha256:e21a94448β¦
Size
965.5 MB
Last updated
11 days ago
docker pull solarssk/wp-critical-css