The last few years have seen static-site generation and Jamstack concepts evolve from niche tools to mainstream development approaches.
The benefits are appealing:
- simpler deployment and static hosting
- better security; there are few back-end systems to exploit
- easy backup and document version control using Git
- a great development experience, and
- super-fast performance
Unfortunately, static-site generator (SSG) projects are rarely handed over to clients. SSGs such as Jekyll, Hugo, and Gatsby are designed for developers. Navigating version branches, updating Markdown documents, and running command-line build processes is frustrating for editors coming from the world of one-click publishing on a content management system.
This tutorial describes one way to keep everyone happy and motivated! …
- content editors can use WordPress to edit and preview posts
- developers can import that content into Eleventy to build a static site
Headless CMSs and Loosely Coupled APIs
Some concepts illustrated here are shrouded in obscure jargon and terminology. I’ll endeavor to avoid it, but it’s useful to understand the general approach.
Most content management systems (CMSs) provide:
- A content control panel to manage pages, posts, media, categories, tags, etc.
- Web page generation systems to insert content into templates. This typically occurs on demand when a user requests a page.
This has some drawbacks:
- Sites may be constrained to the abilities of the CMS and its plugins.
- Content is often stored in HTML, so re-use is difficult — for example, using the same content in a mobile app.
- The page rendering process can be slow. CMSs usually offer caching options to improve performance, but whole sites can disappear when the database fails.
- Switching to an alternative/better CMS isn’t easy.
To provide additional flexibility, a headless CMS has a content control panel but, instead of page templating, data can be accessed via an API. Any number of systems can then use the same content. For example:
- an SSG could fetch all content at build time and render a complete site
- another SSG could build a site in a different way — for example, with premium content
- a mobile app could fetch content on demand to show the latest updates
Headless CMS solutions include Sanity.io and Contentful. These are powerful, but require editors to learn a new content management system.
The WordPress REST API
Almost 40% of all sites use WordPress (including SitePoint.com). Most content editors will have encountered the CMS and many will be using it daily.
WordPress has provided a REST API since version 4.7 was released in 2016. The API allows developers to access and update any data stored in the CMS. For example, to fetch the ten most recent posts, you can send a request to:
Note: this REST URL will only work if pretty permalinks such as Post name are set in the WordPress Settings. If the site uses default URLs, the REST endpoint will be .
This returns JSON content containing an array of large objects for every post:
[ "id": 33, "date": "2020-12-31T13:03:21", "date_gmt": "2020-12-31T13:03:21", "guid": "rendered": "https://mysite/?p=33" , "modified": "2020-12-31T13:03:21", "modified_gmt": "2020-12-31T13:03:21", "slug": "my-post", "status": "publish", "type": "post", "link": "https://mysite/my-post/", "title": "rendered": "First post" , "content": "rendered": "<p>My first post. Nothing much to see here.</p>", "protected": false , "excerpt": "rendered": "<p>My first post</p>", "protected": false , "author": 1, "featured_media": 0, "comment_status": "closed", "ping_status": "", "sticky": false, "template": "", "format": "standard", "meta": , "categories": , "tags":  ]
WordPress returns ten posts by default. The HTTP header
x-wp-total returns the total number of posts and
x-wp-totalpages returns the total number of pages.
Note: no WordPress authentication is required to read public data because … it’s public! Authentication is only necessary when you attempt to add or modify content.
It’s therefore possible to use WordPress as a headless CMS and import page data into a static site generator such as Eleventy. Your editors can continue to use the tool they know regardless of the processes you use for site publication.
The sections below describe how to import WordPress posts into an Eleventy-generated site.
In an ideal world, your WordPress template and Eleventy theme would be similar so page previews render identically to the final site. This may be difficult: the WordPress REST API outputs HTML and that code can be significantly altered by plugins and themes. A carousel, shop product, or contact form could end up in your static site but fail to operate because it’s missing client-side assets or Ajax requests to server-side APIs.
My advice: the simpler your WordPress setup, the easier it will be to use it as a headless CMS. Unfortunately, those 57 essential plugins your client installed may pose a few challenges.
The demonstration code below presumes you have WordPress running on your PC at http://localhost:8001/. You can install Apache, PHP, MySQL and WordPress manually, use an all-in-one installer such as XAMPP, or even access a live server.
Alternatively, you can use Docker to manage the installation and configuration. Create a new directory, such as
wpheadless, containing a
version: '3' services: mysql: image: mysql:5 container_name: mysql environment: - MYSQL_DATABASE=wpdb - MYSQL_USER=wpuser - MYSQL_PASSWORD=wpsecret - MYSQL_ROOT_PASSWORD=mysecret volumes: - wpdata:/var/lib/mysql ports: - "3306:3306" networks: - wpnet restart: on-failure wordpress: image: wordpress container_name: wordpress depends_on: - mysql environment: - WORDPRESS_DB_HOST=mysql - WORDPRESS_DB_NAME=wpdb - WORDPRESS_DB_USER=wpuser - WORDPRESS_DB_PASSWORD=wpsecret volumes: - wpfiles:/var/www/html - ./wp-content:/var/www/html/wp-content ports: - "8001:80" networks: - wpnet restart: on-failure volumes: wpdata: wpfiles: networks: wpnet:
docker-compose up from your terminal to launch WordPress. This may take several minutes when first run since all dependencies must download and initialize.
wp-content subdirectory will be created on the host which contains installed themes and plugins. If you’re using Linux, macOS, or Windows WSL2, you may find this directory has been created by the
root user. You can run
sudo chmod 777 -R wp-content to grant read and write privileges to all users so both you and WordPress can manage the files.
chmod 777 is not ideal. A slightly more secure option is
sudo chown -R www-data:<yourgroup> wp-content followed by
sudo chmod 774 -R wp-content. This grants write permissions to Apache and anyone in your group.
Navigate to http://localhost:8001/ in your browser and follow the WordPress installation process:
Modify your site’s settings as necessary, remembering to set pretty permalinks such as Post name in Settings > Permalinks. Then add or import a few posts so you have data to test in Eleventy.
Keep WordPress running but, once you’re ready to shut everything down, run
docker-compose down from the project directory.
Eleventy is a popular Node.js static-site generator. The Getting Started with Eleventy tutorial describes a full setup, but the instructions below show the essential steps.
Ensure you have Node.js version 8.0 or above installed, then create a project directory and initialize the
mkdir wp11ty cd wp11ty npm init
Install Eleventy and the node-fetch Fetch-compatible library as development dependencies:
npm install @11ty/eleventy node-fetch --save-dev
Then create a new
.eleventy.js configuration file, which sets the source (
/content) and build (
// .eleventy.js configuration module.exports = config => return dir: input: 'content', output: `build` ; ;
How to Use WordPress as a Headless CMS for Eleventy