I built a Headless Multisite WordPress platform With GraphQL

Posted on:

Woof, could I stuff any more buzzwords in this title? But really, this was a really fun project, probably one of my personal favorite ones to-date. And if you're reading this, you're probably reading this content from the very platform that I built.

In fact, I did a presentation on this, as well.

The Problem

Over the last couple of years, I've been noodling around the idea of getting back to the basics with my website. I realized that I was using social media instead of publishing content on my site over the last several years, and that really bummed me out. In-addition to that, I currently run a few different WordPress blogs, all with content specific to things like travel, or WordPress development tutorials, and I felt that all of that content should be discover-able on my personal site. To make matters worse, I also occasionally guest publish content on other websites, as well, and I also feel that I should have that content on my site, too.

I also realized that in-order to truly stop using social media as the first place I go to publish, I had to either learn to stop posting micro content, or publish this content on my site instead, and distribute it out.

It became apparent to me that what I really wanted my personal website to be was a central hub - a single source of truth, and a holistic location to see all of my content online, even if it isn't published on my personal site. I want to absolutely everything I publish online to be filed away on my site, and intermingled with everything else. This includes content published on other blogs, GitHub announcements, micro posts (toots/tweets), blog posts, podcast interviews, guests posts...you name it. If it is content created by me, I want it on my site.

Defining The Scope

After spending a bit of time thinking about it, I decided that the best approach was an intermixing of PoSSE (publish on site, syndicate everywhere) and PESOS (publish everywhere, syndicate on site). When possible, content should be published using WordPress, and then shared on social media. All of that content should link back to the original post on my personal site.

I did a little digging, and determined that with the right combination of features, I was able to set up a way for me to publish social media content through my WordPress website and the experience is reasonably close to what I was doing using native apps like Mastodon or Twitter.

If the content belongs on a different platform, like a guest post or a podcast interview, a copy of the resulting content is be added to my personal site, including a copy of the content. The catch? It doesn't actually use this content for anything but searching purposes, and to have a backup in-case the content ever goes down. Instead, the idea is to have this content visible in the feed, but instead it will go directly to the original post.

Finally, as I was thinking through this site, more than anything I wanted this to feel as authentic as possible. I wanted my site to feel like "my personal space" again, not some place where I'm pushing myself like I'm a brand, or something. I wanted this to holistically represent me, to share the things I love, both in my professional life and otherwise. I wanted my site to feel like an appropriate place to share everything about my life that I'm comfortable with sharing to the public.

So with that, my goals with the platform were:

  • Make it easy to publish any type of content using my mobile device, including micro content
  • Create a design that supports a feed with a mix of different content types, from blog posts, to micro content, to video, and everything in-between.
  • Design a site where it feels appropriate to publish any content I would ever want to publish online.
  • Improve discoverability of all of my content, across all channels, from a single location.
  • To never feel like I'm "losing my content" by closing down a social profile again.
  • Provide a single place to publish content across all of my blogs at one time.
  • Design my website so that if I had no career whatsoever, and was never going to benefit from this site financially, that I would still love it and be proud to have it.
  • Reduce the number of WordPress installs I manage to a single installation.
  • The new site should showcase my most-outstanding WordPress development skills.

Designing The Site

So, with my goals intact, I fired up Figma and started laying out my design. Throughout the design of this site, I found myself constantly thinking "If I were 14, and building a website for myself, what would I have on it"? This drove a lot of decisions in the initial design (some features aren't in yet, but they will be in future iterations).

Things like:

  1. A music player in the sidebar, that can play a pre-made playlist of songs I've been listening to a lot lately.
  2. Links to video gaming profiles, not just social profiles.
  3. A "time warp" button that transforms the entire site to look like my Xanga journal back in 2004.
  4. Roughly where in the country my family and I are currently parked.

In-addition to that, I also wanted this site to serve my practical needs, and wanted it to be able to display my content in any format, and make it look really nice regardless. I ended up settling on a "feed" that is largely inspired by what social platforms like Mastodon and Twitter use to show content on their site. If the post is a podcast interview, it should show the embed to play the episode. If it's a micropost, include all of the content from that post, and if it's a blog post, show the excerpt and title.

Finally, I wanted the site to feel very minimalist. I wanted my content to have plenty of white space, and use the screen it is given to the best of it's abilities.

Ultimately, the initial design concept came out looking like this:

The sidebar area can be expanded/collapsed by clicking on the chevron, and it will not open by default on smaller screens. This allows me to feature links to my site and have a sidebar in places where it makes sense, but also make it something that can be tucked away by the user if it's distracting from the content. Conversely, it can be opened up on mobile, allowing people to learn a bit about me should they choose to do so.

Building The Back-End

I decided early-on that this site was going to be built as a headless WordPress website using WPGraphQL, and NextJS. I really like React, and love WPGraphQL because it has solved a lot of my former complaints in my case for a nearly headless WordPress site. This created an interesting dynamic that I hadn't considered until I started thinking about the back-end of the site, however. Since the site is headless, that means that my WordPress install does not have to be associated with a single WordPress website, even though WordPress expects that to be the case.

I just needed a few key customizations.

The Blog Taxonomy

The first of these customizations was a custom WordPress taxonomy, called "blog". This basically allows me to store multiple different website URLs as a taxonomy term, and associate those different taxonomies with individual posts. By doing this, I have essentially made it possible to tell WordPress "hey, this post here? it should be published on Casual Weirdness". This prevents it from showing up on WPDev Academy (when I eventually migrate it as a part of this system) but still makes it possible for me to include this post in my query results on my personal site. I can also include which site the content belongs to in the REST response, so my personal site knows "hey, don't render this post, instead link to the post through Casual Weirdness."

This taxonomy needed a little tweak to the block editor to force it to use a dropdown instead of a checkbox field for the taxonomy, since WordPress assumes that you will always want to associated multiple taxonomy terms to a post, when in fact we only want to associate 1.

Screenshot of the blogs taxonomy in the WordPress block editor sidebar.  It reads "This post will be published on this blog, but will still be displayed on your personal site as a backlink."

I ended up associating this taxonomy with both pages and posts. This allows me to make sure the front-end knows what pages are associated with certain sites, and prevents some pages from loading on Casual Weirdness that are not intended for that site.

The Canonical URL Field

The next thing I needed was a way to instruct the app "hey, this post was originally published somewhere else, when referring to this post, link to this URL instead".

Most of this logic actually happens on the front-end, but I needed to have a way to add this information on the site itself. This was a perfect task for a little bit of WordPress postmeta. Which is exactly what I used. Not much more to say on this part.

Support For The Jetpack (WordPress) App

One big piece of this equation is to be able to publish everything using the Jetpack app. I was originally going to make different post types, such as "microcontent", "videos", "audio interviews", or other things like that, but these items wouldn't be use-able inside the Jetpack app. The app (particularly the editing experience) is actually very nice, and I don't want anyone to think it's bad, because it's not. It's just that it's very vanilla WordPress, and you absolutely cannot customize the app in any way whatsoever. Because of this, I had to consider the limitations of this app, and ensure that whatever I do worked within the confines of what the app is capable of doing.

Curiously enough, the Jetpack app does not support custom post types (much to mine, and several other's chagrin). It does, however, support an older, lesser-known feature that was quietly tucked away and disabled in WordPress by default, called Post Formats. For my use-case, this was acceptable. Instead of using custom post types, I could use different post formats based on what kind of content I am publishing. Posting a micro post? Use the "aside" format. Blog post? use the default format. Video? ...you get the idea.

So, with that, I decided to force-enable support for these post formats on my site so that I can use them both on the actual theme, as well as through the app (which oddly enough shows the formats even if they're not enabled on the site).

Building The Front-End

The front-end of this site is using NextJS, WPGraphQL, and a plugin that customizes WPGraphQL to be able to include a JSON output of the block data for a post instead of the raw content. This makes it possible to convert content written with the block editor into proper React components instead of the HTML output directly.

I started this site by using the NextJS starter by WebDevStudios, because they have done a fantastic job at getting a head start on the challenges that come with going fully-headless, and it saved me a ton of time using the starter. There are many things I ripped out and replaced for my own needs, but it was a great starting point, and there's a lot of the code that I ended up using exactly as it is.

The Feed

The biggest challenge for the front-end of this site was building out the actual feed itself. Adding the loop of content, and making it dynamically load a different content card based on the post format wasn't so bad, but I had to add pagination to the content as well. Since I'm a masochist, I decided that this site doesn't have a footer, and therefore is a great use-case for an infinite scrolling feed that automatically pushes the next page into the browser state so that search engines can understand the implied pagination from scroll, as their best practices suggest.

I built this functionality from scratch instead of using a library, and it ended up being a lot more technical than I thought it would be for some reason. The WPGraphQL setup to fetch the data was surprisingly simple, based on the provided methods to do so. The issue is that I had to store each individual page, make it possible to loop through each one, keep track of what page I'm on, and a load of other things. It definitely touched on a lot of the skills I've learned with React over the years, and was a fun challenge.

  • The feed automatically detects when you're close to the bottom of the page, and sends a request to get more content
  • More content after a certain marker is loaded
  • If there's more content to fetch, it prepares to fetch more when you scroll down the page
  • If there's nothing left, it says "That's all"

Retro Mode

I wanted to showcase just how long I've been tinkering with web technologies (I took my first web design class in 2003), and also have some fun with my site. I decided a great way to do this could be to make it so that my entire website can transform into something more akin to what my site would have looked like if I built it in 2003. I was big on high-contrast back then, neon greens, pitch black, and apparently Bruce Almighty. I felt this was a fun way to showcase my skills with CSS, and also paying homage to my own past. If you're on desktop, click on the clock in the top-right corner of the screen, and let the fun begin ????.

Conclusion

This was a big project that is a culmination of months of thinking, years of context, and several weeks to fully build. I'm very proud of the result, and am excited that I have built myself a platform to publish my authentic content that I also own. It has re-invigorated my interest in blogging, and I has made me see WordPress through a whole new lens, and I've thoroughly enjoyed it.

I didn't name off every single feature I made in this post, because that would make this blog post entirely too long, but I did at least touch on some of the key unique items.