TL;DR – There are real benefits to static sites but some downsides too. By configuring aggressive page caching rules for your WordPress site, you can achieve almost all the benefits of a static site without the downsides.
- Static vs. Headless WordPress
- Why People Love Static Sites
- The Revolution of Static Site Generators
- The Problems With Static Site Generators
- Static Site Generator Plugins for WordPress
- What is Nginx Page Caching?
- Cache All the Things
- Diving Into Nginx Page Caching
- Rules to Skip the Page Cache
- Aggressive Page Caching With Fewer Rules
- Increasing Cache Duration
- Preloading the Page Cache
- Personalized Dynamic Content
- Wrapping Up
There’s one chapter in the history of web publishing I’ve never quite understood — static site generators.
Before you flip out and start quoting all the benefits, let me start by saying that I do know why they exist, I’ve just never seen the point. Like many, I started building websites using good ol’ HTML, CSS, and a little bit of JavaScript. My first site was hosted with GeoCities, and looked a lot like this. I used things like <marquee>
tags for scrolling text animation, tables for layout, and styling was either done using the style
attribute, or inline CSS, with a few JavaScript alert
functions here and there.
Then, one day, a question from a friend took me down the rabbit hole of dynamically generating content from a database. I discovered ASP and then PHP and MySQL, and I’ve never looked back. Back then, static site generators just felt like too much extra work for very little additional benefit. While I knew about them, dynamically generated content using either a framework like CakePHP or CodeIgniter or a content management system like Drupal or WordPress was my preferred choice.
I know and respect many folks who have tried static site generators like Jekyll, Hugo, and Gatsby, but it’s just not something that ever seemed useful to me.
Static vs. Headless WordPress
Before we get into the details, it would probably be a good time to talk about headless WordPress. Headless WordPress is a term that has seen increased interest since early 2017, especially since WordPress 4.7 was released in late 2016, which marked the inclusion of the WP REST API. Making an API available to query the data from a WordPress database is what enables this headless functionality. A headless CMS uses a decoupled architecture to allow it to act as a backend service accessed via an API (or SDK). With a headless implementation, the CMS only acts in the capacity of content editing, and the frontend is served by another solution. This solution could be a single-page application or a static site generator.
Headless WordPress refers to not using the frontend of the WordPress website at all. The WordPress theme and any plugins that would affect the output on the frontend of the site really have no effect when using WordPress as a headless CMS. This is very different from using WordPress as a static site generator, which makes full use of the theme and plugins generating the frontend of the site, and saving the generated pages as HTML files.
Gatsby is an example of a headless WordPress solution, where the WP GraphQL plugin enables the GraphQL API, which is then accessed by Gatsby to generate the static site pages.
Why People Love Static Sites
Whether it’s a headless solution or a more traditional static generator plugin, there are a few reasons why static sites are popular. We covered these recently in our Gatsby article, but here’s a quick overview.
- Security is the biggest argument for using static sites. With a static site, all you have publicly accessible is HTML, CSS, JavaScript, images, etc. Static files. No PHP and no database. The possible attacks are greatly reduced.
- Static sites are fast and can handle a ton of traffic because there’s no backend (no PHP or MySQL) to be processed at every page request.
- Static sites won’t ever break unless the web server that serves the pages (usually Apache or Nginx) stops working.
- Static sites are often less expensive to host. You don’t have to pay for more CPU power to serve static HTML files.
The Revolution of Static Site Generators
In the early days of web publishing, building and maintaining a static site was somewhat tedious. You generally kept a local copy of all the site’s HTML pages, and if you needed to make a change, you’d edit the local page, and then upload it to the web server. Many web agencies were born during this time, as businesses needed someone technologically savvy to build and maintain their sites.
With the rise of personal weblogs or blogs in the late 1990s/early 2000s, more and more non-technical users were looking for ways to publish their content, without needing to learn HTML and CSS, or how to use FTP. Blogging tools like Blogger, Movable Type, Textpattern, b2, and WordPress were introduced, and they gave users a way to publish their content to the web, without knowing the underlying technologies.
Of these, Blogger and Movable Type were static site generators; they generated static HTML pages based on the user’s content. Blogger launched in 1999 and was one of the first to automate publishing a blog to a web server. The user created their content, and Blogger created and uploaded the HTML page to the server. Moveable Type came two years later in 2001, and took that process a step forward by introducing post titles, rich text editing, and categories, quickly becoming the most popular blogging platform.
Movable Type was built using Perl, which had to rebuild a page every time a change was made. This meant that if anyone left a comment on a post, the page had to be rebuilt before the comment was visible on the post. Blogging was a highly interactive medium, with folks regularly commenting on the posts of their friends. These issues led some to look for tools that would build the page content dynamically on the fly.
In late 2000 developers used PHP, Apache (web server), and MySQL (database) to build content management systems like TextPattern, b2, and WordPress. These systems powered both the frontend (the presentation layer) and the backend (the content layer) for content editing. The benefit of these tools was that the site owner no longer needed to rebuild the front end every time a comment was left or a change was made. In the baked vs fried debate, fried mostly won out.
Over time, developers started pushing the limits of the technology stack. As WordPress grew in popularity, folks started building more than just simple blogs with it. Sites were built with more content, more complex content, and more interactive functionality, and it took longer for Apache, MySQL, and PHP to generate and render the output. This was also hampered by the fact that most WordPress hosting solutions were using shared hosting, meaning many websites hosted on one server with a very generic Apache web server configuration.
As WordPress usage grew, it also became a bigger and bigger target for hackers. Old, neglected WordPress sites whose owners failed to update themes, plugins, and WordPress itself were an easy target, and WordPress gained the stigma of being insecure software.
Some folks also felt that they didn’t need any of the dynamic features of WordPress. They just wanted to publish simple HTML pages of their content, without the need to include comments, contact forms, or any other complex functionality. So, developers started looking at static site generators again. In 2008 Jekyll was released, and it started a web development trend back towards static websites. In 2017 Jekyll was the most popular static site generator, largely due to its adoption by GitHub.
The Problems With Static Site Generators
While a static site generator like Jekyll seems great for simple blogs or company websites, with one author, and where the content doesn’t change often, there’s a reason that dynamically generated sites built using WordPress are still a popular choice.
The main problem with static sites is that it’s harder to implement functionality that handles user interaction and data storage. Contact forms, ecommerce, membership sites, community forums, and online learning systems are all common problems you can solve with WordPress and the right plugins.
As soon as you need to implement a simple contact form on a static site, things get complicated and the advantage of a static site being simple evaporates. And as soon as you want someone else to edit your site, someone non-technical, you really begin to wish you had chosen WordPress in the beginning. Migrating from a static site generator to WordPress is no fun.
There are now CMS services built on top of static site generators, like Forestry.io that allow multiple editors to edit the content of a static generator site. However, if we take a step back, we’re pretty far away from the simplicity of a static site generator, and there is little to no benefit to using this over using WordPress and a static generator plugin. In fact, you would be much better off with WordPress because you can then take advantage of the massive plugin ecosystem.
Using a modern static site generator like Gatsby might appeal to those who are already proficient in React, but for those who are not, it can be daunting to get started. Not to mention that Gatsby is quite a complex system and far from a simple static site generator like Jekyll. The strength of Gatsby as I see it is compiling a site from multiple data sources. For example, you might want to generate a site with data from a proprietary CMS, WordPress for the blog, Shopify for the ecommerce, maybe an inventory management system, etc.
Finally, whatever static site generator you choose, you add an extra “build step” to the publishing of new content. Solutions like the Simply Static plugin we’ll discuss later require you to go to the plugin settings and regenerate the pages as a separate manual step. This adds friction when you need to make any changes to a site.
Static Site Generator Plugins for WordPress
One of the most important things you can do to improve the performance of a WordPress site is to enable page caching and avoid executing PHP and MySQL on every page load. Page caching is very close to a static site generator, in that the page content is converted to a static file. The key difference is when this conversion takes place.
In the early days of WordPress, plugins were developed to implement page caching, and the general approach looked something like this:
- On the first attempt to request the page content, the web server will determine that the web application (in this case WordPress) should serve the content for the requested URL. WordPress (using PHP) will query the database (MySQL), build the page content in HTML/CSS/JavaScript, and save it as a static file in a temporary location, known as the cache. That page content will then be served to the browser.
- On future attempts, the web server will still determine that the web application should serve the content for the requested URL. However, WordPress (using PHP) will determine that a static cache file exists, read the static file contents, and serve that content to the browser.
- Often the cache file for a given URL will be cleared when the page content is rebuilt, or after a given amount of time.
WP Super Cache and W3 Total Cache were some of the first WordPress solutions to implement page caching for WordPress in the form of static generation. They allow you to add URL rewrite rules to an Apache .htaccess
file or to your Nginx configuration (if you have access) so that the web server handles serving the static content without executing PHP and WordPress at all for better performance.
On the other hand, plugins like Simply Static and WP2Static offer a true static site generator for WordPress. These plugins give site owners a way to generate static HTML files of the content of a WordPress site, from the WordPress dashboard to a specific location. These static files can then either be uploaded to a web server, or the web server can be configured to automatically serve the static files from the location where the plugin creates them. Unlike a caching plugin, when the content for a URL changes, the site owner needs to regenerate either that file or all the static files.
Gatsby has become a popular way to generate static content from a WordPress site. Unlike Simply Static, Gatsby uses the WP GraphQL plugin to turn the WordPress site into a Gatsby-ready GraphQL data source. Using a React-based templating system, Gatsby queries the GraphQL data source and generates static content from the WordPress data, either via the command line or through a hosted service like Gatsby Cloud.
Over the past 5 years, I’ve seen a number of well-known WordPress folks try out a headless or static site solution of some kind for their personal blogs, they eventually all end up back with regular WordPress. I’ve never understood the hype around static site generators and I’ve never tried them. This is because I’ve been using something else for years, Nginx page caching.
What is Nginx Page Caching?
Unlike the plugin-based page caching we discussed earlier, where WordPress (and PHP) are involved in creating the static cache files when a page is requested, it’s also possible to implement page caching at the web server level, having the web server produce the static cache files.
Both Apache and Nginx give you the option to automatically create a static version of a page at the web server level. Nginx was written specifically to address the performance limitations of Apache and has overtaken Apache as the most widely used web server. One of the reasons for this is that in benchmarks, Nginx performs up to 2.5 times faster than Apache when serving static content. This is the main reason we chose Nginx over Apache when we wrote our Install WordPress on Ubuntu Guide, and when we built SpinupWP. With Nginx and PHP, page caching can be configured with the FastCGI module. This allows Nginx to serve a cached static version of a page without ever executing PHP or MySQL.
I used Apache + Varnish for several years before switching to Nginx + FastCGI Cache. By enabling page caching at the web server level, I’ve never needed to consider a caching plugin or a static site generator to serve static content. My web server has always handled the static version of my WordPress posts and pages.
Cache All the Things
Before we dive into the details of configuring Nginx page caching rules, it’s important to note that there are two other caching layers to consider when optimizing a WordPress site in addition to page caching: browser caching and object caching. We covered these in-depth in our WordPress Caching Guide, but here’s a brief summary:
- Browser Caching – This is where static assets like JavaScript, CSS, image files, and the HTML page itself are cached by the browser. Browser caching doesn’t necessarily help with application response time or throughput on a WordPress site, but it does help make your site feel more responsive because these static assets load much quicker from the browser cache as you navigate across pages of the same site.
- Object Caching – Object caching is useful when you are dealing with dynamic content, like ecommerce and membership sites. Object caching is where you cache data objects retrieved from the database and reduce future database queries. It’s a great way to speed up your WordPress site with very little technical setup.
Implementing these in addition to page caching will have a remarkable impact on your WordPress site’s performance.
Diving Into Nginx Page Caching
Web server caching is not new. The Apache mod_cache
and related caching modules have been around since Apache version 2.0, which was released in 2004. However, one of the main reasons WordPress page caching plugins exist, is that WordPress site owners have rarely been able to change the web server configuration where their sites are hosted. Shared hosting solutions rely on a generic server configuration, a “one size fits all approach”. So WordPress site owners had to rely on caching solutions at the web application level, not the web server level.
With the rise of VPS hosting, it’s becoming cheaper and easier to manage your own web server, giving developers direct access to web server configurations. This has opened up the possibility of implementing site-specific caching at the web server level.
The most straightforward way to implement Nginx page caching for a PHP-based web application like WordPress is to use the FastCGI module to automatically cache a static HTML version of any WordPress URLs. We benchmarked the performance of a WordPress site with no caching solution, a caching plugin, Varnish, and Nginx caching using FastCGI, and Nginx + FastCGI was the clear winner.
We have a detailed step-by-step guide on how to set up Nginx + FastCGI Cache on your own server so we’re not going to go into those details here. Following those instructions will get you a very good caching solution that will greatly speed up an uncached WordPress site. You’ve essentially turned your WordPress site into a static site, but instead of the static site generator rebuilding the static pages, the web server does so.
Rules to Skip the Page Cache
Of course, a WordPress site has dynamic parts that won’t work if they are page cached. For example, if you page cached the WordPress login form, you wouldn’t be able to login to the WordPress dashboard. This is why almost all page caching implementations have conditions to skip the cache. The following are the Nginx rules from our guide:
# Don't skip by default
set $skip_cache 0;
# Don’t cache POST requests
if ($request_method = POST) {
set $skip_cache 1;
}
# Don’t cache URLs with a query string
if ($query_string != "") {
set $skip_cache 1;
}
# Don't cache URIs containing the following segments
if ($request_uri ~* "/wp-admin/|/wp-json/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml|/cart/|/checkout/|/my-account/") {
set $skip_cache 1;
}
# Don't use the cache for logged in users or recent commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|edd_items_in_cart|woocommerce_items_in_cart") {
set $skip_cache 1;
}
These rules skip the cache in the following scenarios:
- On any POST requests. Usually, a POST request means some data needs to be processed, and the results are displayed on the screen, so responding with cached content wouldn’t be ideal.
- Any time a query string exists in the URL. Generally, WordPress site owners (or their developers) have enabled a Permalink Setting other than “Plain”, and so query strings in a URL usually mean some data needs to be fetched and displayed.
- For specific WordPress URLs. Things like
wp-admin
,wp-json
(the REST API root URL), and URLs for dynamic features likecart
,checkout
, ormy-account
shouldn’t be cached. - Whenever there is a logged-in user, or for recent commentators. This is achieved by checking for specific browser cookies that indicate the user type and skipping the cache accordingly.
For sites configured by SpinupWP, we have a Page Cache settings page, where customers can configure the cache duration, as well as add exclusions for specific URLs that aren’t already covered.
Aggressive Page Caching With Fewer Rules
You may have noticed that the caching rules above are specific to WordPress but are still pretty generic. They’re designed to work with any WordPress site. If we’re willing to write rules for a specific site, we can get a lot more aggressive with our page caching. Let’s examine the rules and see how we can tighten them up.
The POST Request Rule
# Don’t cache POST requests
if ($request_method = POST) {
set $skip_cache 1;
}
We can remove this first rule if our site doesn’t have any forms that are submitting POST requests to URLs on the frontend of our site. There’s already another rule that skips the cache for the WordPress dashboard and any requests to specific WordPress core files (more on that later). The cache will be skipped for any POST requests in the WordPress dashboard, the WP REST API, and specific WordPress core files. Many of the form plugins for WordPress submit POST requests to /wp-admin/admin-ajax.php
or the WP REST API, so these will still work after removing this rule. You should certainly test thoroughly as you modify these rules.
The Query String Rule
# Don’t cache URLs with a query string
if ($query_string != "") {
set $skip_cache 1;
}
This rule asserts that if the URL has a query string, assume it’s dynamic content that shouldn’t be cached. It is sometimes true that a query string indicates dynamic content, but not always. There are certainly some URLs with query strings that I still want cached. For example, I would certainly want my WordPress search results (e.g. https://hellfish.media/?s=wheels
) cached.
If you know your site well, you should know which query strings you should cache. It might also be a good idea to inspect your access logs and possibly identify some query strings you might not have known about.
Take the deliciousbrains.com site, where there’s the following custom caching rule that only skips the cache for URLs containing very specific query string parameters:
if ($request_uri ~* "(\?|&)(coupon|preview|download_file|licence_key)=") {
set $skip_cache 1;
}
But that’s a WooCommerce site. If you’re running a simple site that could be a static site, you probably only need to skip the cache for the preview
query string parameter.
By default, WordPress appends ?doing_wp_cron
to a URL to execute the WordPress cron as part of a request. So you wouldn’t want to cache requests with this query string. However, if you set up WordPress cron properly, you don’t need to worry about this query string.
Our query string rule becomes the following:
# Don't cache post previews
if ($request_uri ~* "(\?|&)preview=") {
set $skip_cache 1;
}
The WordPress Dashboard Rule
The next rule skips the cache for WordPress core files as well as some common ecommerce pages:
# Don't cache URIs containing the following segments
if ($request_uri ~* "/wp-admin/|/wp-json/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml|/cart/|/checkout/|/my-account/") {
set $skip_cache 1;
}
We definitely need to skip the cache for the WordPress dashboard, so we should keep /wp-admin/
, /wp-json/
, and wp-..php
. Although I find wp-..php
a bit too broad. We should make this more specifically just the wp-login.php
page.
You might think about removing /wp-json/
since you don’t use the WP REST API, but actually, you do. The WordPress dashboard makes heavy use of the WP REST API. You can certainly lock it down though, only allowing logged-in users access with a snippet of PHP. The following directive added to the Nginx server block will also deny any requests that don’t have the login cookie set:
location /wp-json/ {
if ($http_cookie !~* "wordpress_logged_in") {
return 403;
}
try_files $uri $uri/ /index.php?$args;
}
We can discard the ecommerce URLs unless you’re using them. I would also remove the sitemap(_index)?.xml
and /feed/
as I think we should certainly cache the site map and RSS feeds.
We can remove index.php
as that would only apply if you don’t configure pretty permalinks, which you definitely should.
The /xmlrpc.php
part is another easy one to remove. XML-RPC is an ancient API that is rarely used these days. We now have the WP REST API, which is a better option. In SpinupWP, you have the option to disable XML-RPC entirely, which adds the following to the site’s Nginx server
block:
location ~* /xmlrpc\.php$ {
deny all;
access_log off;
}
In the above screenshot, you may also notice the option to prevent PHP files in the uploads folder from being executed. I highly recommend adding the following to your Nginx server block to enable that:
# Deny access to any files with a .php extension in the uploads directory
location ~* /uploads/.*\.php$ {
deny all;
access_log off;
}
Our rule should now be trimmed back to look like this:
# Don't cache the WordPress dashboard
if ($request_uri ~* "wp-(login|admin|json)") {
set $skip_cache 1;
}
In some cases, skipping the cache for the entire WP REST API might not be practical. You might have a public read-only endpoint that gets a lot of requests and would benefit greatly from page caching and avoiding PHP and MySQL. In this case, you could add an exception rule that sets the $skip_cache
variable back to 0:
# Cache the Events API endpoint
if ($request_uri ~* "/wp-json/eventorama/v1/events") {
set $skip_cache 0;
}
The Cookies Rule
Finally, the last rule skips the cache if certain cookies are found in the request:
# Don't use the cache for logged in users or recent commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|edd_items_in_cart|woocommerce_items_in_cart") {
set $skip_cache 1;
}
We can remove the edd_items_in_cart
and woocommerce_items_in_cart
bits if we’re not running Easy Digital Downloads or WooCommerce. WordPress core doesn’t use the wordpress_no_cache
cookie and I can’t find anything else that does either, so let’s remove that.
The wp-postpass
cookie has to do with password-protected pages in WordPress, so you could remove it if you don’t plan to use that feature. Let’s remove wordpress_[a-f0-9]+
as well, as it seems to just be a catchall and has no specific purpose.
Skipping the cache for the comment_author
cookie allows the author of a comment to see their comment and its status (e.g., awaiting moderation) after submission. Skipping the cache for all pages after a comment is submitted is overkill though. A better solution here is to use a plugin like wpDiscuz to handle comment submissions asynchronously.
You might want to continue skipping the cache if the wordpress_logged_in
cookie is set, if the only users for your site are a few editors updating the site. It makes sense to skip the cache for those users so that the WordPress admin bar appears at the top of the pages on the frontend of the site.
Our rule would now look like this:
# Don't use the cache for logged in users
if ($http_cookie ~* "wordpress_logged_in") {
set $skip_cache 1;
}
If you’re running a site with a ton of logged-in users like an ecommerce or membership site, you don’t want to skip the cache for every page just because a user is logged in. You need a different strategy. On the deliciousbrains.com site, a custom dbi_admin
cookie is set to 1 when one of the team members logs into WordPress:
/**
* Set cookie for admin users on login.
*
* @param string $user_login
* @param WP_User $user
*/
function dbi_admin_cookie( $user_login, $user ) {
if ( empty( $user->roles ) || ! in_array( 'administrator', $user->roles ) ) {
return;
}
$expire = time() + YEAR_IN_SECONDS;
setcookie( 'dbi_admin', 1, $expire, COOKIEPATH, COOKIE_DOMAIN, false, false );
}
add_action( 'wp_login', 'dbi_admin_cookie', 10, 2 );
Then the following rule skips the cache only for those administrator users:
# Admin users
if ($cookie_dbi_admin = 1) {
set $skip_cache 1;
}
If you’re wondering where the $cookie_dbi_admin
variable is set, Nginx sets up all cookies as Nginx variables automatically.
We also use this cookie to skip loading Google Analytics and other snippets of Javascript to avoid polluting our data with our use of the site.
If you’re running a simple WordPress site with mostly static pages, your entire skip cache block can be greatly simplified down to the following:
# Don't skip by default
set $skip_cache 0;
# Don't cache the WordPress dashboard
if ($request_uri ~* "wp-(login|admin|json)") {
set $skip_cache 1;
}
# Don't cache post previews
if ($request_uri ~* "(\?|&)preview=") {
set $skip_cache 1;
}
# Don't use the cache for logged in users
if ($http_cookie ~* "wordpress_logged_in") {
set $skip_cache 1;
}
Increasing Cache Duration
Now that we’ve covered our recommended exclusions, you can see how you can more aggressively cache your WordPress pages.
The most obvious change you could make is to increase your page cache duration. In the Install WordPress on Ubuntu guide, we start by setting the duration to 60 minutes, but that could be increased to a day, or a week, or even a month if you really wanted to.
For example, on my personal blog I used to blog once a month, so setting my cache duration to one month would be fine. Even if I realized that one of my blog posts or pages needed an update, SpinupWP’s caching plugin will clear the cache any time a post is added or updated. If you’re not using SpinupWP, there’s an Nginx cache plugin that automatically purges the cache when content changes as well.
As described in the Varnish vs Nginx FastCGI Cache article, at SpinupWP we have set a longer cache duration of 7 days. We’ve then configured the site to purge the entire cache whenever content is updated. We’ve found this solution more reliable, versus trying to determine exactly which web page or pages should be purged from the cache, which can get complicated pretty quickly due to post archives and pagination.
Preloading the Page Cache
Whether you’re using the Nginx Cache plugin or the SpinupWP plugin, you’re clearing the entire page cache any time you save changes to your WordPress site’s content. This is another one-size-fits-all solution.
On your site, it might be fine to only clear the cache for the one page you change, but on the next person’s site, they might have a sidebar across the whole site containing that page’s title. To make sure saved changes are reflected everywhere they’re supposed to be, page cache plugins clear the cache for the whole site.
The problem is that now every page is going to be slow for the next visitor that requests it. And we’re talking 10x slower. 300ms vs 30ms.
Ideally, we could determine exactly what pages need to be purged every time site changes are saved and only clear those pages from the cache. We could then preload those pages into the cache so that the next request is served from the cache nice and fast.
Unfortunately, it can be difficult to determine exactly what pages need to be purged when WordPress content is updated and I have yet to come across a good solution.
The best solutions I’ve seen purge the entire cache and then crawl the whole WordPress site, preloading the cache. WP Rocket and WP Super Cache both offer this functionality, but only WP Rocket is compatible with our Nginx caching setup here. It will preload the cache without interfering with the Nginx cache setup. We plan to implement a cache preloading feature into the SpinupWP plugin in the future so that third-party plugins aren’t necessary.
Personalized Dynamic Content
There is one scenario where implementing this type of Nginx caching doesn’t deliver a great user experience, and that’s whenever there’s personalized dynamic content on a page that you might otherwise prefer to cache. Worse yet, whenever caching is implemented on pages with personalized content, you run the risk of one user’s personal data being cached, and displayed to another user. Consider a dynamically generated shopping cart at the top of all product pages on an ecommerce website. You definitely don’t want that data cached as it would mean every shopper seeing the same cached cart.
Fortunately, static cache files are nothing more than the rendered HTML, CSS, and JavaScript that WordPress outputs for these pages. Using custom JavaScript and an asynchronous request, it’s possible to fetch the relevant information on a per-user basis and update only the sections of the cached page content that need to be personalized. We have a handy guide on how we solved this problem for an ecommerce site, but the theory is the same for any content of this type:
- Cache a generic version of the entire page containing no personal data.
- Use JavaScript and an asynchronous request to update the page with personalized content on a per-user basis.
Wrapping Up
Personally, if I really wanted a fully static site today with no dynamic parts, I would do the following:
- Set up WordPress in a subdirectory on a subdomain (e.g.,
https://cms.hellfish.media/wp/
). - Protect this subdirectory with HTTP Basic Authentication.
- Use the Simply Static plugin to generate the static site, saving it to the web root (i.e.
https://cms.hellfish.media/
). - Configure the main domain (i.e.
https://hellfish.media
) with a CDN pulling from the web root.
This would give me all the benefits of a static site (speed, security, etc.) but I could easily give someone else edit access if needed. If I need dynamic parts in the future, it would be quick and easy to move the WordPress site out of the subdirectory to the web root and turn it into a typical WordPress site. Then I’d perhaps add aggressive page caching so that it performs just as well as the static site did.
However, the beauty of using the available caching tools at the server level means that you effectively turn your WordPress site into a static site, with all the benefits but none of the pain of manually rebuilding the cache. We run the SpinupWP site on WordPress using Nginx caching, and we rarely ever experience downtime, speed, or concurrency-related issues, even during Black Friday.
Our team spends a lot of time adding or updating content on both sites, from documentation updates to blog posts. Being able to manage it all in WordPress, and knowing that it will always be delivered blazingly fast to our customers, gives them appreciation for the time and effort that goes into getting it just right.
Not all web server caching solutions are equal. When Jonathan joined us and moved his personal blog from his old VPS hosting control panel to SpinupWP, he noticed an 11% speed increase, without making any specific changes. If you’re on the fence about trying out Nginx page caching for your WordPress site, I recommend you try it out.
What caching solutions are you using for your WordPress sites? Are you managing it yourself, or does your hosting provider handle it for you? Do you prefer to use static site generators, or have you taken a look into headless WordPress? Let us know in the comments.