Actually Static: When WordPress Stops Being the Enemy
Source: Dev.to
GitHub Copilot CLI Challenge – A WordPress‑to‑Static‑Site Workflow
This post is for developers who love writing in WordPress but want the speed and safety of static sites (Hugo / GitHub Pages).
The Journey
I spent years wrestling with WordPress performance and security:
- Optimising caching layers
- Hardening installations
- Fighting plugin bloat
All to keep public‑facing sites running acceptably.
Then I discovered static site generators—Hugo, Astro—fast, secure, elegant.
But months of tweaking themes, debugging build pipelines, and fighting deployment workflows taught me something: I’d just traded one set of problems for another.
The Paradox
| WordPress | Static Site Generators |
|---|---|
| Best writing environment ever built – mature UI, solid editor, focus on content | Best deployment target ever built – fast, secure, cheap to host, scalable by default |
“I spent quite a lot of time tweaking Hugo sites instead of writing, and I’m afraid I’ll do the same thing if I transfer to Astro.” – @juliecodestack
The real problem isn’t WordPress or Hugo; it’s the friction between writing and deploying.
Why Public‑Facing WordPress Is a Burden
- Requires decent hosting and heavy plugin dependencies (security, SEO, etc.)
- Maintenance overhead is high
- Performance is variable at best
I wanted a simpler solution: keep WordPress as a writing environment while completely removing its public presence.
Write normally. Publish automatically. No manual export, no scripts to run, no friction.
The Solution
A WordPress plugin that automatically syncs published posts to a GitHub repository, converts them to Markdown with Hugo‑compatible front‑matter, optimises images (WebP / AVIF), and triggers a GitHub Actions workflow for static‑site deployment.
Tech Stack
- WordPress 6.9+ (PHP 8.1+)
- GitHub API – atomic commits via the Trees API
- Action Scheduler – async processing
- Intervention Image – local image optimisation
- Hugo – static site generator
- GitHub Actions + GitHub Pages – CI/CD and hosting
Key Features
- Zero‑touch publishing – write in WordPress as usual.
- Automatic sync – on publish/update the plugin pushes a single commit to GitHub.
- Markdown conversion – each post becomes a Hugo‑compatible Markdown file with proper front‑matter.
- Image optimisation – originals are converted to WebP and AVIF before committing.
- CI deployment – a GitHub Actions workflow builds the site with Hugo and deploys it to GitHub Pages.
Result: Publishing in WordPress instantly puts a complete static version of the site online—no manual export, no extra steps.
Demo
- WordPress admin (login:
tester, password:Github~Challenge/2k26, Author rights) – see the result on the demo website. - In the site repository you can view the committed files and the workflow definition under
.github/workflows.
Challenges & Lessons Learned
| Issue | What Happened | Fix |
|---|---|---|
| Hugo Theme Version Hell | The theme required Hugo 0.146.0, but the local install was 0.139.0 and GitHub Actions defaulted to 0.128.0. | Pin the Hugo version explicitly in every environment; add debugging for TOML errors. |
| GitHub Pages URL Stuttering | Deployed site had broken internal links because baseURL didn’t match GitHub Pages expectations. | Hard‑code the production URL in the workflow; accept minor navigation quirks in local previews. |
| Image Pipeline Memory Limits | Processing 10+ images per post with AVIF exceeded PHP memory on shared hosting → fatal errors. | Increase memory_limit to 512 M and batch‑process images sequentially instead of in parallel. |
| Action Scheduler Race Conditions | Duplicate commits occurred when a post was saved quickly (autosave, manual save, quick edit). | Add debouncing, transient locks, and post‑meta flags to prevent redundant syncs. |
| PHP 8.1 Strictness | An explode() on a null value froze the whole sync pipeline. | Wrap risky calls in try‑catch‑finally to guarantee lock release and UI update. |
| Git Line‑Ending Hell (LF vs CRLF) | Linux runners rejected files edited on Windows due to line‑ending mismatches. | Enforce LF line endings globally via a .gitattributes file. |
Closing Thoughts
Static sites can now be deployed in seconds on virtually any infrastructure. Keeping WordPress as a frontend editor makes sense only when you have a robust, automated deployment pipeline—exactly what this plugin provides.
This article is part of my “Farewell to WordPress” series, where I explore ways to move away from WordPress as a public‑facing platform while preserving its superb writing experience.
Happy writing, happy deploying!
Config File – Zero Cross‑Platform Headaches
The Partial Save Trap
WordPress tabbed interfaces only submit visible fields. When updating the Front Matter template, the GitHub PAT field wasn’t sent, so the value was overwritten with an empty string and the token was unintentionally deleted.
Fix
Use array_merge() (or a similar merge strategy) to preserve existing values during partial updates.
None of this was in the initial specifications. All of it was mandatory to ship.
What “48 hours” Actually Meant
| Task | Manual (estimated) | With Copilot CLI | Actual Savings |
|---|---|---|---|
| Plugin boilerplate + WordPress standards | ~4 h | 20 min | 3 h 40 m |
| GitHub API integration (Trees, refs, commits) | ~6 h | 1.5 h | 4 h 30 m |
| Image optimisation pipeline | ~5 h | 2 h | 3 h |
| Async queue setup (Action Scheduler) | ~3 h | 45 min | 2 h 15 m |
| Admin UI + settings page | ~4 h | 1 h | 3 h |
| Hugo adapter (Markdown + front matter) | ~2 h | 30 min | 1 h 30 m |
| Debugging real‑world issues | ~8 h | ~8 h | 0 h (no AI help) |
| Total | ~32 h | ~14 h | ~18 h |
Copilot accelerated the structured implementation. It did nothing for architectural decisions, debugging environment‑specific failures, or understanding WordPress.org compliance requirements.
Image Optimisation – Why It Matters
| Scenario | Files Uploaded | Build Time | GitHub Actions Runner Minutes |
|---|---|---|---|
| Without local optimisation | 5 MB original JPEGs | 2‑3 min per post | High (large files stored in repo) |
| With local optimisation (current approach) | AVIF (50‑150 KB) + WebP (100‑300 KB) + original ≈ 500 KB total | 15‑30 s | Minimal (just copy files) |
Trade‑off: PHP memory limits & processing time on the WordPress side vs. the cost of GitHub Actions runner minutes. WordPress is idle 99 % of the time, so shifting the bottleneck to the free WordPress host is sensible.
What’s Currently Handled
- ✅ Posts & Pages – Sync automatically with proper Hugo front matter
- ✅ Deletions – Trashing a post/page in WordPress triggers file deletion in GitHub
- ✅ Updates – Editing content re‑syncs, overwriting existing files
- ✅ Categories & Tags – Converted to Hugo taxonomies in front matter
- ✅ Featured Images – Optimised and linked in front matter (
featured_imagefield) - ✅ Custom Fields – Basic fields map to front matter (extensible via adapter)
Current Limitations (MVP Scope)
- ⚠️ Draft handling – Drafts stay in WordPress, never sync (intentional)
- ⚠️ Revisions – Only published versions sync; revision history stays local
- ⚠️ Complex blocks – Gutenberg blocks → HTML → basic Markdown (no advanced block preservation)
- ⚠️ Shortcodes – Rendered to HTML before conversion (original shortcode lost)
- ⚠️ ACF / Meta Boxes – Only standard custom fields supported (ACF needs a custom adapter)
- ⚠️ Author pages – Not yet implemented (single‑author blogs work fine)
Deliberate Trade‑offs
-
Source of truth remains WordPress. The plugin does not sync bidirectionally; editing Markdown directly in GitHub will not flow back to WordPress. This keeps the system simple.
-
Theme changes & SSG migration – Thanks to the universal front‑matter template system:
-
Update the front‑matter template in plugin settings (no code changes).
-
Bulk re‑sync all posts via WP‑CLI:
wp jamstack sync --all -
(Optional) Clean up the old file structure in Git if directory paths changed.
-
-
The adapter pattern is already in place. Adding support for Jekyll, Eleventy, Astro, etc., only requires a new adapter class; the core sync engine stays untouched.
-
What isn’t automated yet: Migrating between SSGs with fundamentally different content structures (e.g., Hugo’s
content/posts/vs. Astro’ssrc/content/blog/). Bulk file‑move operations in Git are currently manual. -
Changing front‑matter conventions within the same SSG is now a simple settings change, not a refactoring project.
Repository Overview – atomic-jamstack-connector
- WordPress plugin – WordPress.org compliant
- GitHub API integration – Atomic commits
- Asynchronous sync management – Action Scheduler
- Hugo‑compatible Markdown generation
- GitHub Actions workflow – Deployment
Process in Detail
-
Writing – Use the standard WordPress editor; no changes to the writing experience.
-
Automatic Commit – The GitHub repository receives Markdown, optimized images, and front‑matter.
.github/workflows/ └─ hugo.yml ← GitHub Actions workflow content/ ← Generated Hugo content static/images/ ← Optimized assets (deployment status) ← Last run status -
Hugo Structure –
content/posts/is generated automatically with correct naming. -
Deployed Site – The static version is served via GitHub Pages, delivering optimal performance.
The whole thing forms a simple publishing chain: write in WordPress → publish → let the rest execute.
Key Features
1. Universal Front‑Matter Engine
Instead of hard‑coding for a single Hugo theme, we built a raw‑template system. Users define their own YAML (or TOML) with custom delimiters and placeholders such as {{id}}, {{title}}, {{image_avif}}.
This enables the plugin to adapt to any static‑site‑generator (SSG) convention:
- Hugo – YAML front matter
- Jekyll – custom taxonomy names
- Eleventy – bespoke data structures
You control the output format; the plugin merely fills in the blanks.
2. Asset Management by WordPress ID
Optimised images (WebP & AVIF) are stored in folders named after the WordPress ID, e.g., static/images/1460/.
- Rename your post slug for SEO – images never break.
- The ID is immutable, guaranteeing permanent file paths.
3. Native WordPress Integration
- First‑class citizen with its own sidebar menu and tabbed navigation.
- Role‑based security:
- Authors see only their own sync history.
- Admins manage critical settings (e.g., GitHub PAT).
Responsible Cleanup
Clean Uninstall – removes all plugin traces (options and post meta) on uninstall, leaving zero database pollution.
4. Atomic Commits via GitHub Trees API
// Collect all files (Markdown + images)
$all_files = [
'content/posts/2026-02-07-this-is-a-post.md' => $markdown_content,
'static/images/1447/featured.webp' => $webp_binary,
'static/images/1447/featured.avif' => $avif_binary,
'static/images/1447/wordpress-to-hugo-1024x587.webp' => $webp_binary,
'static/images/1447/wordpress-to-hugo-1024x587.avif' => $avif_binary,
];
// Single atomic commit
$git_api->create_atomic_commit(
$all_files,
'Publish: This is a Post'
);
This approach is transactional: either everything commits or nothing does. No partial states, cleaner history.
5. WordPress‑Native Compliance
| Requirement | Preferred API |
|---|---|
| HTTP requests | wp_remote_post() (instead of curl) |
| File handling | WP_Filesystem (instead of file_put_contents()) |
| Database queries | $wpdb prepared statements |
| Shell execution | Never (exec(), shell_exec(), Git CLI are prohibited) |
Following these guidelines makes the plugin suitable for publication in the official WordPress Plugin Repository.
Project Narrative
When I started this project I wasn’t looking for a tool to code for me; I wanted a way to accelerate execution of a project whose architecture was already clear.
Having already used GitHub Copilot CLI, Gemini CLI, and various LLMs on other projects, I knew these tools could produce code quickly—but without a precise framework they mainly generate code, not a coherent system.
Note: This isn’t the autocomplete in the editor, but a command‑line tool capable of generating complete files from structured prompts.
The Real First Step
- Write specifications – define scope and break the project into functional blocks.
- Identify non‑negotiable constraints – WordPress‑native only, no shell execution, reliable async processing, atomic GitHub commits, WordPress.org compliance.
- Organize development into successive stages.
This mirrors what a technical project manager does before handing implementation to a team. The difference here is that the “team” is a tool that can produce code very quickly—if the instructions are clear and precise.
My Workflow
| Phase | Action |
|---|---|
| Specification | Write functional & technical specs, prompts, refined instructions. |
| Generation | Let Copilot generate code. |
| Verification | Review output against constraints. |
| Correction | Adjust prompts, fix issues. |
| Iteration | Repeat until the desired result is achieved. |
The pattern that emerged was:
specification → generation → verification → correction → iteration.
Observations
- Copilot can structure an entire class in seconds, propose a coherent implementation, or refactor a block.
- It can also forget an essential hook, overwrite an existing method, or produce code that fails to comply with the initial constraints.
Real Examples of Issues
- Method replaced by an incomplete stub.
- Hook not registered → silent failures.
- File generated but not actually written to disk.
- Fatal error on activation (common in strict WordPress environments).
Each incident required returning to fundamentals: verify → understand → correct → reformulate.
Key Takeaways
- Using Copilot effectively is not “write one prompt and wait”.
- It’s a continuous piloting process where the quality of instructions directly determines the quality of output.
- The tool shines for structured tasks: class creation, file organization, repetitive function implementation, refactoring, documentation.
- Responsibility for architecture, technical choices, and overall coherence remains entirely human.
Project Outcome
- Built in less than two days – not because the tool replaced design work, but because once design was complete, execution accelerated dramatically.
- GitHub Copilot CLI proved most valuable as an accelerator for an already thought‑out and structured project, not a substitute for development.
What Worked Well
- Clear, detailed specifications.
- Iterative verification and correction.
What Required Constant Supervision
- Ensuring WordPress‑native compliance (no shell commands, proper APIs).
- Maintaining hooks and activation routines.
- Verifying that generated files are actually written to disk.
Final Thoughts
GitHub Copilot CLI didn’t replace development; it didn’t eliminate the need to think, architect, or decide. Used as an execution partner rather than an automatic generator, it allowed a clear idea to become a functional system quickly.
That’s where these tools truly make sense: they don’t change how we build, but they reduce the distance between imagination and production.
In this specific case they solved a real tension: write comfortably in WordPress while publishing to a high‑performance static site—no friction, no compromises, just a workflow that works.
Further Reading
If you’re interested in other approaches to moving away from WordPress, check out my “Farewell to WordPress” series, where I explore migrations to Hugo and Astro with different strategies and trade‑offs.