Skills wp-to-static
Convert a WordPress website to a static site and deploy to Cloudflare Pages. Mirrors the rendered HTML via SSH, extracts only referenced assets (shrinks 1.5GB+ to ~25MB), fixes URLs, self-hosts fonts, strips WordPress cruft, and deploys. Use when migrating a WordPress site to static hosting.
git clone https://github.com/openclaw/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/abhibavishi/wp-to-static" ~/.claude/skills/clawdbot-skills-wp-to-static && rm -rf "$T"
skills/abhibavishi/wp-to-static/SKILL.mdWordPress to Static Site (Cloudflare Pages)
Convert a WordPress website to a pixel-perfect static site and deploy it to Cloudflare Pages. Zero attack surface, zero hosting cost, instant load times.
Prerequisites
Before running this skill, the user MUST have:
- GitHub CLI authenticated: Run
to verify. If not logged in, rungh auth status
first.gh auth login - Cloudflare Wrangler authenticated: Run
to verify. If not logged in, runwrangler whoami
first.wrangler login - SSH key added to ssh-agent: The recommended way to handle SSH keys. Run:
eval "$(ssh-agent -s)" ssh-add ~/.ssh/your_wp_key - Server host key verified: The user should have connected to the server at least once and accepted the host key, so it exists in
.~/.ssh/known_hosts
Environment Variables
Required (stop and ask if any are missing):
— SSH hostname (e.g.,WP_SSH_HOST
)ssh.example.com
— SSH usernameWP_SSH_USER
— SSH port (e.g.,WP_SSH_PORT
)18765
— Path to SSH private key file (e.g.,WP_SSH_KEY
). Key must have~/.ssh/wp_key
permissions.chmod 600
— WordPress site URL (e.g.,WP_SITE_URL
)https://example.com
— Short project name (e.g.,WP_SITE_NAME
)mysite
Optional:
— Cloudflare account ID for Pages deploymentCF_ACCOUNT_ID
—GH_REPO_VISIBILITY
(default) orprivatepublic
Security Model
- SSH authentication uses
— keys are loaded into the agent before running, so no passphrase is passed via environment variables or command argumentsssh-agent - SSH host key verification is ENABLED (no
) — the server must already be inStrictHostKeyChecking=no~/.ssh/known_hosts - Credentials are NEVER logged, echoed, or displayed
- Credentials are NEVER committed to git
- GitHub repos are created as private by default
Step 0: Validate
- Check all required env vars are set. If any are missing, stop and tell the user.
- Verify required binaries exist:
,ssh
,ssh-agent
,rsync
,curl
,git
,gh
.wrangler - Verify
succeeds. If not, tell user to rungh auth status
.gh auth login - Verify
succeeds (ifwrangler whoami
is set). If not, tell user to runCF_ACCOUNT_ID
.wrangler login - Verify SSH key file exists and has correct permissions (
).chmod 600 - Stop if anything is missing.
Step 1: Test SSH Connection
Test the connection using the key from ssh-agent:
ssh -i $WP_SSH_KEY -p $WP_SSH_PORT $WP_SSH_USER@$WP_SSH_HOST "echo connected"
If the key requires a passphrase and ssh-agent is not loaded, tell the user:
Please add your SSH key to ssh-agent first: eval "$(ssh-agent -s)" ssh-add /path/to/your/key Then re-run /wp-to-static
If the host key is not recognized, tell the user to connect manually once first to verify and accept the host key:
Please connect to the server once manually to verify the host key: ssh -i $WP_SSH_KEY -p $WP_SSH_PORT $WP_SSH_USER@$WP_SSH_HOST Accept the host key, then re-run /wp-to-static
Do NOT use
StrictHostKeyChecking=no. Do NOT bypass host key verification.
Step 2: Locate WordPress Installation
SSH in and find the WordPress
public_html directory. Common locations:
~/www/DOMAIN/public_html/~/public_html/~/htdocs//var/www/html/
Confirm by finding
wp-config.php. Store path as WP_ROOT.
Step 3: Mirror with wget (ON THE SERVER)
Run
wget --mirror on the server (not locally):
cd /tmp && rm -rf static_mirror && mkdir -p static_mirror && cd static_mirror && \ wget --mirror --convert-links --adjust-extension --page-requisites --no-parent \ --restrict-file-names=windows -e robots=off --timeout=30 --tries=3 --wait=0.5 \ --user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \ $WP_SITE_URL/ 2>&1 | tail -30
If
wget is not available on the server, fall back to curl locally for rendered HTML.
Step 4: Rsync to Local
Create
./build/site (NEVER use the project root as temp dir).
Exclude server-side code and sensitive files. Only static assets (images, CSS, JS, fonts) are needed. PHP files, config files, and other server-side code must NEVER be downloaded.
RSYNC_EXCLUDE="--exclude='*.php' --exclude='wp-config*' --exclude='.htaccess' --exclude='*.sql' --exclude='*.log' --exclude='debug.log' --exclude='error_log' --exclude='.env' --exclude='*.bak' --exclude='*.backup'" rsync -avz $RSYNC_EXCLUDE server:/tmp/static_mirror/DOMAIN/ ./build/site/ rsync -avz $RSYNC_EXCLUDE server:$WP_ROOT/wp-content/uploads/ ./build/site/wp-content/uploads/ rsync -avz $RSYNC_EXCLUDE server:$WP_ROOT/wp-content/themes/ ./build/site/wp-content/themes/ rsync -avz $RSYNC_EXCLUDE server:$WP_ROOT/wp-content/plugins/ ./build/site/wp-content/plugins/ rsync -avz $RSYNC_EXCLUDE server:$WP_ROOT/wp-includes/ ./build/site/wp-includes/
After rsync, verify no PHP or config files were downloaded:
find ./build/site -name '*.php' -o -name 'wp-config*' -o -name '.htaccess' -o -name '.env' | head -20
If any are found, delete them before proceeding.
Step 5: Extract Only Referenced Assets
This is the key step. Parse all HTML and CSS files to find every referenced local file:
From HTML:
src=, href=, data-src=, data-srcset=, srcset=, inline background-image: url()
From CSS: All
url() references — resolve relative paths from CSS file location to site root.
Write the list to
./build/referenced-files.txt, then copy only those files to ./public/ preserving directory structure. This typically shrinks 1.5GB+ down to ~25MB.
Step 6: Fix Absolute URLs
In
index.html and ALL CSS files:
- Replace
→ empty string (relative paths)$WP_SITE_URL/ - Replace any staging/dev domain URLs → local paths
- Self-host Google Fonts:
- Download each
to.ttf./public/fonts/ - Update
to@font-face src:fonts/filename.ttf
- Download each
- Remove
for Google Fonts domains<link rel="preconnect">
CSS path resolution is critical. If CSS is at
wp-content/uploads/cache/file.css:
→wp-content/uploads/../../
→wp-content/themes/../../themes/
→wp-includes/../../../wp-includes/
Step 7: Strip WordPress Cruft
Remove:
(WordPress, WPBakery, Slider Revolution)<meta name="generator" ...>
,<link rel="EditURI"...>
(RSS, oEmbed)<link rel="alternate"...>
,<link rel="https://api.w.org/"...><link rel="shortlink"...><link rel="profile" href="gmpg.org/xfn/11">
for fonts.googleapis.com<link rel="dns-prefetch"...>- W3 Total Cache HTML comments
root references in inline JSONwp-json
Keep: Email addresses,
<link rel="canonical"> (update to /)
Step 8: Cloudflare Pages Config
Create
./public/_headers with aggressive caching for /fonts/*, /wp-content/*, /wp-includes/*.
Create
./public/_redirects redirecting /wp-admin/*, /wp-login.php, /xmlrpc.php, /feed/* → / (302).
Step 9: Verify Locally
- Start
frompython3 -m http.server./public/ - Test key assets return HTTP 200 (CSS, JS, logo, fonts, images)
- Tell user to open the URL and visually verify
- Wait for user confirmation before deploying
Step 10: Scrub Temporary Files and Deploy
Before any git operations, remove the
./build/ directory to ensure no server-side code, PHP files, or sensitive data can accidentally be committed:
rm -rf ./build
Verify only
./public/ remains and contains no PHP or config files:
find ./public -name '*.php' -o -name 'wp-config*' -o -name '.htaccess' -o -name '.env'
This must return empty. If not, delete those files before proceeding.
Then deploy:
, commit ONLYgit init
and./public/.gitignore
(for binary assets)git config http.postBuffer 524288000gh repo create $WP_SITE_NAME --private --source=. --pushCLOUDFLARE_ACCOUNT_ID=$CF_ACCOUNT_ID wrangler pages project create $WP_SITE_NAME --production-branch mainCLOUDFLARE_ACCOUNT_ID=$CF_ACCOUNT_ID wrangler pages deploy ./public --project-name $WP_SITE_NAME- Verify deployment, report live URL, remind about custom domain setup
Safety Rules
- NEVER display or log credentials (SSH keys, passphrases, tokens)
- NEVER commit credentials to git (.gitignore must exclude .env, *.key, *.pem)
- NEVER use
or bypass SSH host verificationStrictHostKeyChecking=no - NEVER pass passphrases as command-line arguments or environment variables at runtime
- NEVER delete the current working directory (breaks the shell CWD)
- NEVER force-push or use destructive git commands
- NEVER rsync PHP files, wp-config, .htaccess, .env, or SQL dumps from the server
- Use
for temp files,./build/
for output — only./public/
is committed./public/ - ALWAYS delete
BEFORE any git operations to prevent accidental commits of server-side files./build/ - Verify
contains no PHP or config files before committing./public/ - Stop and report on any failure — do NOT retry blindly