Building WPDecoupled: SEO and Internet plumbing 🔧

Alex Moon on

There are many pieces to a website as you all know. Some are more visible, but often the most critical are the parts never seen by the human eye. They are sitemaps, RSS feeds, robots.txt, and other robot-friendly data. After launching the site I dove into doing this “plumbing” work. We will review what, why, and how I wired it all up.

SEO+

There is a staggering amount of SEO-related data you can and should put on a site. One of the great benefits of WordPress plugins is how straightforward they make this. I knew I needed to get SEO data rolling and fast. The first step was choosing a WP SEO plugin. The 3 big players I am aware of are Yoast, SEOPress, and Rank Math.

A quick decoupled history of each. Each of these plugins supports the WordPress REST API. None of them natively supports WPGraphQL, but all now have some support from the community in the form of 3rd party plugins. Yoast’s WPGraphQL plugin was the first on the scene and was written by Ash Hitchcock in the early days of WPGraphQL and has been fairly well maintained. Based on his early work I had a client using SEOPress and used Ash’s code to create a primitive WPGraphQL integration for SEOPress. This integration has largely gone untouched since then. Finally, the auspicious David Levine brought us an excellent Rank Math integration for WP GraphQL.

The only good options were Yoast or Rank Math. I had used Yoast before on my personal sites and knew its quirks. I also knew David did good work and was interested in checking out Rank Math. I went for RankMath and was not disappointed.

Why Rank Math?

My choice of choosing Rank Math was quickly confirmed by a great feature Yoast has yet to implement. In our GraphQL schema, we have types that correlate to our WordPress content: Post, Page, Tags, etc… In WordPress, our SEO plugins will handle SEO for all these items. In GraphQL, that means each of their corresponding types will get an seo the field that has all the available data. Okay, so in my post component on the front end, I could easily request this data, same for any other content types.

query wpPost($slug: String!) {
  post(id: $slug, idType: SLUG) {
    title
    content
    seo {
      ...
    }
  }
}

Okay, this is easy enough, but what about my other content types? I would need to write a functionally Identical query to handle each of them. That’s a lot of code duplication I don’t want to maintain. What about using a GraphQL Fragment? e.g.

...SeoData on Post {
  ...
}

Do you see the problem? Fragments are typed. This fragment can only be used on the Post type…so again…I would be left writing identical fragments across several GraphQL schema types. There is a better way. But Yoast’s WPGraphQL integration does not yet support this better way. I was very happy to see that the Rank Math integration did. Those more familiar with GraphQL might know I am talking about GraphQL Interfaces.

GraphQL Schema docs for NodeWithRankMathSeo Interface from GraphiQL

Interfaces tell GraphQL that all the types that implement this interface share the same fields. In Rank Math’s case, this union is called NodeWithRankMathSeo.

As you can see in the image to the right, this interface is implemented for all content types for which Rank Math handles SEO. This image is from WPGraphQL’s GraphiQL instance inside my WP. If you have custom post types and such, those should show up if Rank Math is configured to handle SEO for them.

Okay, so now we can correctly write that fragment to be used across multiple content templates in my JavaScript framework of choice. But there’s an easier way. We are generating identical SEO data on all pages that support it, this sounds like something that could be abstracted to our layout and out of type and content-specific templates. To do this we just need to be able to fetch any kind of content type based on its uri.

query wpSeo($uri: String!) {
  nodeByUri($uri) {
    ...on NodeWithRankMathSeo {
      seo {
        ...
      }
    }
  }
}

We have done it! Now, if the seo field isn’t null, we can render its contents into our document head for some great SEO.

How I implement SEO with Rank Math

Okay, now that we have the data flowing we can build an SEO component and request all the needed data. But what should we put in here? We’ve got the obvious title and description options, but we also have Twitter card and Open Graph metadata. There are also various structured data schemas to help Search engines better understand data. If I want per route robots meta I could include this as well. So much niche tech I need to research, understand, and implement. But maybe there is a better way.

We already have a plugin that knows what to render, and how to render that data. The Rank Math team are experts in this, let’s leverage their knowledge. Our WPGraphQL integration exposes a simple field called fullHead and another called jsonLd. I can build a component for rendering these into the head as HTML and my work is done.

I expected this job to take a day or so, I wrapped it in under an hour and it was implemented better than I could have dreamed.

NOTE: Beware of getting the correct URLs in this content. They are often pointing to your WordPress instance and not your Front-end site unless you have added specific filters.

Feeds

RSS feeds are not dead. They may be underutilized, but they live on. Though, RSS is not the only feed technology. Atom feeds are closely related, and there is a new feed spec based on JSON. I recently helped rebuild the WPGraphQL website and implemented feeds there using a great npm package called, feed. You provide data for each of your posts and it will spit out all 3 of the feed types. I set this up to render serverside with a small caching window.

After implementing this I realized there may be a better way. WP has built-in support for rendering a variety of feeds. For the whole site but also for specific post types, categories, tags, comments on specific posts…etc. This would take ages to build by hand but I could fairly easily set up my front-end to leverage WordPress to render these feeds. I have not done this yet, and there are some logistical issues to figure out, but I think it’s all quite achievable. I just need to work out the specifics. I will be sure to share when I get to it.

Sitemaps

Sitemaps are very important as we all know. I have some experience with them from OSS work I did in the Gatsby ecosystem, but I still struggle to build them. I started to go down that rabbit hole, but I know the Faust.js team had found a better solution, and utilized the sitemaps provided by SEO plugins. Once again, this would allow me to leverage the knowledge of a team (the SEO plugin builder) that knows sitemaps, and not have to maintain my own code.

SvelteKit made this a breeze with parameter matching. In short, I wrote a regex to match the sitemap files Rank Math was creating. SvelteKit would use that filename to fetch the file from WordPress and return it to the user. Again, this turned out surprisingly simple. I made sure the URLs were correct in the sitemaps and moved on.

Robots

I wrote a quick robots.txt and dropped it in my static folder. This works, but this could also be created from the work Rank Math is already doing. I will make this change one day, but this file was simple enough, and I did not think to do this till later.

Conclusion

I was able to leverage Rank Math to simplify the implementation work significantly. My feeds will one day benefit from the core WordPress feeds functionality, but for now, they hopefully work. All this work was surprisingly effortless because of how I could leverage Rank Math and David’s WPGraphQL integration. It makes me excited to see how easy SEO for Decoupled WordPress has become. I hope to see similar strides in the Block Editor and beyond going forward.

Alex Moon

About Alex Moon

Avid Open Source maintainer, a longtime developer, and vagabond.