Composable Architecture Powered by WordPress

It’s inevitable that – as a developer – I continue to change my mind and feel the urge to tinker again and again. Like a pendulum swinging back and forth I’ve noticed that my tendencies for choosing technologies also oscillates between complexity and simplicity. Always on the hunt for that happy medium where the developer, authoring, and user experiences are all as good as they can be. Inevitably, however, choices made to improve one of these three can become a hindrance on the others. Here’s my journey towards the next evolution of content generation for all my sites, leveraging composable architecture powered by WordPress.

A Focus on Simplicity

A couple years ago I switched this personal site of mine from an over-engineered monstrosity of custom code to a minimalist Eleventy build. It was a liberating experience being able to scrap so much and still have a functional site that was more performant and simple to maintain. After a couple years tinkering with it I came to this conclusion about the three experiences noted above:

  • Developer Experience – greatly improved due to less code, no databases, and a more straightforward organization
  • User Experience – greatly improved due to a redeveloped frontend for better accessibility, and 11ty being a static site generator it’s way more performant
  • Authoring Experience – more cumbersome with markdown files powering page content (including blog posts), no longer having an administration section to manage content from anywhere

Simplicity at What Cost?

As I alluded to in the Authoring Experience note above, the only problem I came across was that I had to be at my computer with an IDE open in order to draft new blog posts. “No problem” I thought, “I’m always at my computer anyway” and off I went.

Well, since that switch I’ve posted all of three new blog posts – just about one per year. I finally admitted to myself that it was due to the barrier of having to start a new markdown file, by the time I sat at my computer the post idea was either gone completely or I lost some of the more compelling aspects of it.

I started dreaming of a happy middle ground, one where I could still have the performance and simplicity of a static site but with the ease of an admin interface for adding content.

Growing Ambitions

Once I had it in my head that I wanted to make some changes for this site, of course I couldn’t help also considering the challenges I was having with content over at my accessibility auditing app Be Inclusive. I had switched to using Twill over there just over a year ago for similar reasons as I had for wanting a change on this site now. I need an easier authoring experience if I have any hope at keeping up with content creation, but from the start it felt like a bolted on solution that didn’t quite fit.

That got me wondering about content authoring for multiple sites, is there a way I could make something work long-term that’d be versatile enough for my shifting priorities across projects?

Enter Composable Architecture

That led to me turning to the concept of composable architecture, while my needs are relatively narrow focused (mostly blog and article-based content) it’s still the best term I can think of. It can be referred to as microservices architecture, modular architecture, and headless architecture. Us technologists aren’t that great at naming things…

Basically, I’m looking to offload the authoring of content to another service entirely and pulling that data as needed into any applications I want. This type of content architecture is very popular nowadays and for good reason, it’s fairly compelling to be able to split the code apart from the content and to have the latter available for multiple kinds of applications. Larger companies have used it effectively to share content across websites, native applications, social, and marketing (often paired with terms like omnichannel or digital transformation).

Choosing WordPress

After some fiddling with SaaS composable architecture content providers and trying a few options out, I settled on ol’ reliable WordPress for my content authoring needs. Here’s a few reasons why:

  • As a web developer, I’m fortunate enough to be able to do a bit of work upfront in order to self host and not incur fees for an out-of-the-box content provider. Many were great, I just couldn’t justify the cost.
  • WordPress comes pre-packaged with the ability to set up a multisite installation, so I can have content for this personal site, the Be Inclusive app, and any other sites I need to add into the future.
  • WordPress comes with a RESTful API already baked in too, so I can run API calls to gather the content I need. If my needs get to be more custom, I can also create my own RESTful API endpoints for full control.
  • With custom post types, I can also compartmentalize different types of content with a simple plugin. Leaving me with a versatile and extensible framework to grow into.

Getting Started

Setting this up was actually much simpler than I was anticipating, here’s a quick rundown:

  • I wanted to position the content somewhere reasonable for all my content needs, for me that ended up being as a separately hosted subdomain.
  • I installed WordPress at the subdomain, and then configured multisite using the instructions in this Create a Network WP support page.
  • I started a new “site” for both this personal site, and for the Be Inclusive app, from the Network Admin Sites Screen.
  • I disabled all themes, since this is going to be used solely as a content hub where sites will be pulling content from the API. As far as I know, you can’t have _no theme_ so I created a simplified one used mainly for redirecting traffic. See details and source code below under the “Redirect Theme” headline.
  • I added a few plugins that defined custom post types I need for my site content (besides standard blog posts). See details and source code below under the “Custom Post Types” headline.

Redirect Theme

I didn’t want folks using this subdomain to see the content I was creating, so the theme I created was called “Redirect” and has three files – functions.php, index.php, and style.css. All three files get added to wp-content/themes/redirect. You then set that as the active theme and you’re all set!

functions.php

I needed to be able to support post thumbnails, that’s really all this one is used for currently.

<?php

if ( ! function_exists( 'redirect_theme_support' ) ) :

	/**
	 * Sets up theme defaults and registers support for various WordPress features.
	 *
	 * @since Twenty Twenty-Two 1.0
	 *
	 * @return void
	 */
	function redirect_theme_support() {
		// Add support for block styles.
		add_theme_support( 'post-thumbnails' );
	}

endif;

add_action( 'after_setup_theme', 'redirect_theme_support' );

index.php

This is where we perform the redirect sitewide, the only downside is that you can’t use the “Preview” link when authoring.

<?php

// We want to run this site as a headless implementation, redirect to the site instead
header("Location: https://yourdomain.com/");

style.css

This is only here to give the meta information we need to see in the WordPress admin

/*
   Theme Name: Redirect
   Description: Redirects the front end to another domain
*/

Custom Post Types

I needed a couple custom post types defined for FAQ and Help Article content separate from the blog posts. Here’s an example of the plugin for FAQs, it’s a single file that gets added to wp-content/plugins

<?php

/**
 * FAQs custom post type plugin
 *
 * @wordpress-plugin
 * Plugin Name:       Custom Post Type - FAQ
 * Description:       Defines a custom FAQ post type for use in the admin editor
 * Version:           1.0.0
 * Author:            Steve Woodson
 * Author URI:        https://stevenwoodson.com/
 */

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}


/**
 * Register the FAQ Custom Post Type.
 *
 * @see https://developer.wordpress.org/reference/functions/register_post_type
 */
function faq_post_type() {

	$labels = array(
		'name'           => _x( 'FAQs', 'Post Type General Name' ),
		'singular_name'  => _x( 'FAQ', 'Post Type Singular Name' ),
		'menu_name'      => __( 'FAQs' ),
		'name_admin_bar' => __( 'FAQs' ),
		'archives'       => __( 'FAQ Archives' ),
		'attributes'     => __( 'FAQ Attributes' ),
		'all_items'      => __( 'All FAQs' ),
		'add_new'        => __( 'Add New FAQ' ),
	);
	$args   = array(
		'label'               => __( 'FAQs' ),
		'description'         => __( 'A frequently asked question' ),
		'labels'              => $labels,
		'supports'            => array( 'title', 'editor' ),
		'hierarchical'        => false,
		'public'              => true,
		'publicly_queryable'  => true,
		'show_ui'             => true,
		'show_in_menu'        => true,
		'menu_position'       => 5,
		'exclude_from_search' => false,
		'searchable'          => true,
		'publicly_queryable'  => true,
		'rewrite'             => false,
		'capability_type'     => 'post',
		'show_in_rest'        => true,
		'rest_base'           => 'faqs',
		'has_archive'         => true,
		'taxonomies'          => ['category', 'post_tag']
	);
	register_post_type( 'faq', $args );
}
add_action( 'init', 'faq_post_type' );

Next Steps

So I’ve settled on a composable architecture setup, chose WordPress to facilitate it, and got it all set up as a content hub for all my growing content authoring needs. The next steps are:

  • Migrating any existing content into it. For me that was a mixture of copy/paste of content, uploading images to WordPress and replacing the pasted version with the uploaded version, and then making sure I reset the publish date to be the same as the original.
  • Pulling WordPress Posts into Laravel for Be Inclusive
  • Pulling WordPress Posts into Eleventy for this site
  • I’m also considering setting this up for Accessibility Solutions too, which is built using Nuxt

This post is getting longer than I planned! I’m going to split out how I approached getting content from WordPress into these sites as separate posts. Each site being built with different languages and frameworks I’m sure they’re all going to have their own surprises.

Have some thoughts or feedback about this blog post?

Get a conversation started on LinkedIn or Twitter