BuddyX

13 min read · 2,678 words

How To Convert A WordPress Site To A Static HTML Website

How To Convert A WordPress Site To A Static HTML Website

There’s a particular kind of relief in visiting a static site during a traffic spike and watching it not fall over. No database queries stacking up, no PHP process pool getting exhausted, just files being handed over by the web server as fast as the network allows. That’s the appeal behind converting a WordPress site to static HTML, and it’s a legitimate move for a specific category of site, just not for every site, and not without understanding what you give up along the way.

This isn’t a theoretical exercise. Plenty of brochure sites, documentation hubs, and content archives run WordPress purely as a content-management convenience and serve the public a fully static output. WordPress becomes the editing tool, not the thing visitors actually touch. Here’s how that works, which tools do it well, and where it falls apart.

What “Converting to Static HTML” Actually Means

A normal WordPress page is generated on the fly: a visitor requests a URL, WordPress boots PHP, queries MySQL for the post content, runs it through the theme’s template hierarchy, and hands back assembled HTML. Every single visit repeats that process unless a caching layer intervenes.

Static conversion flips this. Instead of generating HTML per request, you crawl the entire site once (or on a schedule), capture the fully rendered HTML output of every page, and save it as literal .html files alongside copies of your CSS, JavaScript, and images. From that point forward, a web server just reads and returns those files. No PHP execution, no database query, on a normal page view.

Crucially, this is different from a caching plugin like WP Rocket or W3 Total Cache, which speeds up the dynamic site but still keeps PHP and MySQL running behind the cache layer, ready to regenerate on a cache miss or an admin action. A true static conversion removes the dynamic layer from the serving path entirely for public visitors, though the original WordPress install still exists somewhere as your editing environment.

Why People Actually Do This

Security surface shrinks dramatically. A static HTML file can’t be exploited through a SQL injection vulnerability because there’s no database in the request path. It can’t be hit with a PHP object injection attack because there’s no PHP executing per request. The overwhelming majority of WordPress compromises target the dynamic layer, vulnerable plugins, weak admin credentials leading to file uploads, outdated core installs, and none of that surface exists on the public-facing static output.

Speed becomes close to the theoretical maximum. Serving a static file is about as fast as a web server can go. No template rendering, no database round trip, nothing waiting on I/O beyond reading a file from disk (or, more likely on a CDN, from edge cache). Time to first byte on a well-configured static site regularly comes in under 50ms.

Hosting gets radically cheaper and simpler. Static files can be served from something as minimal as an S3 bucket with CloudFront in front of it, GitHub Pages, Netlify, or Cloudflare Pages, all of which have generous free tiers because static file serving is cheap for the provider to offer. You stop paying for PHP-FPM workers, managed database instances, and object caching layers sized for traffic spikes.

Traffic spikes stop being a capacity planning exercise. A post that suddenly gets shared widely and spikes to ten times normal traffic is a non-event for a static site behind a CDN. The same spike against a dynamic WordPress install without aggressive caching can take the database down.

Where It Falls Apart

None of this is free. Static conversion trades away everything that depends on server-side processing at request time:

  • Comments need an external service (Disqus, a serverless form handler, or a third-party comment API) since there’s no PHP to process a comment submission against the database.
  • Search needs a client-side JavaScript search index (like Lunr.js or Algolia) rather than WordPress’s built-in query-based search, since there’s no server to run a database search against.
  • Contact forms need a form-processing service (Formspree, Netlify Forms, a serverless function) instead of a plugin like Contact Form 7 that posts back to PHP.
  • Membership and login areas can’t work at all on pure static output, because authentication and per-user content inherently require server-side logic evaluating who’s asking.
  • WooCommerce and any e-commerce functionality is fundamentally incompatible with static conversion for the same reason, carts, checkout, and inventory all require live server state.
  • Anything personalized, related posts based on browsing history, geo-targeted content, A/B tests served server-side, needs to move to client-side JavaScript or gets dropped entirely.

The sites where this trade makes sense are the ones where none of the above matters much: documentation sites, marketing brochure sites, portfolios, archives of published content that isn’t accepting new comments, and blogs willing to swap native comments for Disqus or a similar bolt-on.

Choosing a Static Site Generator Plugin

Three tools dominate this space, and they take meaningfully different approaches:

Simply Static is the most widely used option and crawls your live site the same way a search engine bot would, following links, capturing rendered HTML, and rewriting internal URLs to point to relative paths or your new static domain. It handles most standard WordPress themes well out of the box, and its Pro version adds direct deployment to Netlify, Amazon S3, and SFTP, which removes a manual upload step from the workflow.

WP2Static takes a similar crawl-based approach but with a more modular architecture, it separates the crawling engine from the deployment target through add-ons, so you can plug in different deployment methods (GitHub, Bunny CDN, local export) without switching the core crawling logic. It also has stronger support for rewriting URLs correctly when you’re changing domains as part of the migration, which trips up some other tools.

Static HTML Output / Really Static and similar smaller plugins fill specific niches, smaller sites, specific hosting targets, but tend to have less active development and fewer edge cases handled than the two above. Worth checking their last update date before committing, since a stale static-export plugin is a bigger risk than a stale content plugin: you find out it’s broken only when the export itself fails partway through.

The Actual Conversion Process, Step by Step

1. Back Up Everything First

Full database export and full file backup before touching anything. If a conversion attempt goes sideways, and crawl-based exporters occasionally choke on unusual theme markup or plugin-generated content, you want a clean rollback point.

2. Audit What You’re About to Lose

Go through the site and list every piece of dynamic functionality currently in use: comments, search, contact forms, any membership gating, any plugin that outputs something server-rendered per visitor (like a “you last visited on X date” notice). For each one, decide whether it gets replaced with a client-side or third-party equivalent, or dropped.

3. Install and Configure the Static Generator

Set the output directory, decide on absolute vs. relative URL handling (relative URLs are generally safer if you might change the deployment domain later), and configure which content types and taxonomy archives should be included or excluded from the crawl.

4. Run the Initial Export and Review Locally

Download the generated static files and open them in a local server (even a simple php -S localhost:8000 from within the export folder works for a quick check) before deploying anywhere public. Click through several pages, check that images load, that internal navigation links resolve correctly, and that nothing renders a raw PHP error or unprocessed shortcode.

5. Deploy and Test on a Staging Subdomain

Push the static output to a staging location first, a Netlify preview URL, a subdirectory on your existing host, an S3 bucket without a custom domain attached yet, and test thoroughly there before pointing your real domain at it. Check every form, every search box, every piece of previously-dynamic functionality you decided to keep in a client-side form.

6. Update DNS and Redirects

Once you’re confident, point DNS at the new static hosting target. If any URLs changed structure during the conversion (rare, but it happens with certain plugin-generated URL patterns), set up 301 redirects from the old structure to the new one to preserve search rankings.

7. Decide What Happens to the Original WordPress Install

Most people keep the original dynamic WordPress site running privately (password-protected, or on a subdomain not linked publicly) as the actual content-editing environment, re-running the static export whenever content changes, rather than deleting it. Some set up an automated pipeline where publishing a post in WordPress triggers a fresh static export and deployment automatically, which turns WordPress into a proper headless-ish CMS for a static front end.

Is This the Same as Going Headless?

Related, but not identical. A true headless setup uses WordPress purely as a content API (through the REST API or GraphQL via WPGraphQL) and a separate framework, commonly Next.js or Astro, builds and serves the front end, often with more sophisticated incremental rebuilding, dynamic islands of interactivity, and tighter build pipelines than a crawl-based static export offers. Crawl-based static export is simpler to set up and requires no separate front-end framework knowledge, but headless with a modern static site generator gives more control over build performance and partial rebuilds when only one page changes rather than re-crawling the entire site.

For a smaller site publishing occasionally, crawl-based static export through a plugin is the pragmatic choice. For a larger site publishing frequently where a full re-crawl becomes slow, or where you want richer front-end interactivity than plain HTML/CSS/JS easily provides, headless is worth the additional setup investment.

Handling the Functionality You Don’t Want to Lose

Before writing off static conversion because of the “you lose comments and search” objection, it’s worth knowing what the actual replacement setups look like, because most of them are more capable than people assume.

Comments

Disqus remains the most common drop-in, embedding a JavaScript widget that handles comment storage and moderation entirely outside your static hosting. The trade-off is that Disqus injects ads on free tiers and adds a third-party tracking script, which matters if privacy is a priority. A lighter alternative is a self-hosted comment system like Isso or Commento, which run as a small separate service you host yourself, giving comments back without reintroducing PHP into the page-serving path. Either way, comment counts and content live outside WordPress entirely once you go static, so any existing WordPress comments need to be exported and imported into whichever system you choose before launch, or you accept starting fresh.

Client-side search libraries like Lunr.js or Fuse.js build a search index at export time (a JSON file containing tokenized content from every page) and then search that index entirely in the visitor’s browser using JavaScript, no server round trip required. This works well up to a few thousand pages; past that, the index file gets large enough that a hosted search service like Algolia (generous free tier for smaller sites) becomes the more practical option, trading a small monthly cost for faster, more relevant results and no client-side index download.

Forms

Netlify Forms and Formspree are the two most common choices, both letting you keep a normal-looking HTML form tag with no visible change to the visitor, while the actual submission gets routed to their processing service instead of a WordPress PHP endpoint. Both include basic spam filtering; for anything handling sensitive data, checking their data retention and compliance posture before use matters more than for a typical contact form.

Dynamic Bits Inside Otherwise Static Pages

Not every dynamic element needs the whole site to stay dynamic. A “last updated” timestamp, a related-posts widget, or a newsletter signup count can often be implemented as a small client-side JavaScript snippet that calls a lightweight serverless function (Cloudflare Workers, Netlify Functions, AWS Lambda) rather than requiring the entire page to be server-rendered. This hybrid approach, mostly static, with small dynamic islands loaded asynchronously after the static HTML renders, captures most of the performance and security benefit of full static conversion while keeping a few genuinely dynamic touches.

Performance Numbers Worth Knowing

The gap between a well-cached dynamic WordPress site and a true static export is smaller than people expect for a single request, but it shows up hard under concurrent load. A cached dynamic page and a static file can both return in well under 100ms for one visitor. The difference appears when a thousand visitors hit the same page in the same minute: the static file serving path has effectively no bottleneck beyond network bandwidth, while even a well-cached dynamic site has some fraction of requests hitting cache misses, warming events after a cache purge, or admin-ajax calls from logged-in editors that still touch PHP and the database. Static conversion mostly buys you resilience under unpredictable load rather than raw speed at low, steady traffic, both approaches can be fast at low traffic, but only one of them stays fast when a post goes viral or gets hit by bot traffic.

Common Problems People Run Into

The export misses pages entirely. This almost always traces back to a page not being reachable through internal links from the homepage or sitemap the crawler is following. Orphaned pages, pages only accessible through a search box, or pages behind a login (which the crawler can’t authenticate through) get skipped. Fix by making sure every page you need is linked from somewhere the crawler will follow, or by manually adding specific URLs to the plugin’s include list.

Forms and interactive elements just disappear or show broken markup. Anything that depended on a WordPress shortcode being processed server-side (like a Contact Form 7 shortcode) renders as literal shortcode text in the static output unless you’ve replaced it with a form solution designed to work in static HTML before running the export.

Internal links point to the old dynamic URLs after deployment. Most exporters handle this automatically by rewriting internal <a href> tags during the crawl, but custom theme code that builds URLs dynamically in JavaScript (rather than standard WordPress template functions) sometimes gets missed and needs a manual pass.

Images look fine locally but break after deployment. Usually a path issue, the export used absolute URLs pointing to the original WordPress domain, and once you deploy to a different domain, those references go stale. Configuring the plugin for relative URLs, or explicitly setting the target deployment domain in its settings before running the export, avoids this.

A Realistic Bottom Line

Converting to static HTML is a genuinely good move for content-heavy, low-interactivity sites that want speed, security, and hosting cost benefits and don’t need native comments, search, forms, or e-commerce. It’s the wrong move for anything transactional, anything with logged-in areas, or anything where content changes so frequently that re-crawling the whole site on every edit becomes a bottleneck. Know which category your site falls into before committing the time to set this up.

Keeping the WordPress Editing Experience After Going Static

One detail that surprises people the first time they do this: nothing about the editorial workflow needs to change. Writers still log into wp-admin, still use the block editor, still schedule posts the same way they always did. The only new step is triggering a re-export after publishing, which can be automated with a simple webhook, WordPress fires a hook on publish_post, that hook calls a small script that runs the static export plugin’s CLI command (Simply Static and WP2Static both expose one), and the result gets pushed to the hosting target without anyone touching a terminal. Set this up once and the “static site” framing disappears entirely from the day-to-day experience; it just quietly becomes how the site gets served.

For teams nervous about this change, running both in parallel for a few weeks, dynamic WordPress on a staging URL, static export live on production, gives everyone time to confirm the export pipeline is catching every content change correctly before fully committing and decommissioning the publicly accessible dynamic version.


Interesting Reads:

Updating To PHP 8.1 Crashed My WordPress Site

How to disable a WooCommerce Store

Can You Push Specific Pages Within WordPress

Reading
13 min · 2,678 words
Published
Aug 23, 2024
Wbcom Team
BuddyX contributor

Writing about WordPress communities, BuddyPress, BuddyBoss, LMS plugins, and the business of paid communities.

Keep reading

More from the BuddyX blog

Browse all posts on community, WordPress, BuddyPress and the studio of plugins behind BuddyX.