There are 3 main challenges to building a distributable API-driven WordPress theme. These are:
- SEO and UX concerns surrounding client-side rendering
- Client-side routing that replicates WordPress’s internal routing rules (this article)
- Preloading resources to instantly fulfill user requests (coming soon)
This article will explore the second of these concerns – client-side routing.
When you’re making a traditional server-side theme, you don’t have to think about routing – WordPress is able to map the URL to the correct template file without the theme builder having to worry about how it does it. We’re not quite so lucky when building a client-rendered theme. We have to configure and manage routing behavior ourselves.
This typically takes the form of writing logic like this (pseudocode):
if currentRoute matches routingRule1 then display template1, if currentRoute matches routingRule2 then display template2, ...etc
where a routing rule is a regex pattern that matches a url structure like /dir1/dir2/:param, or /:param, or even just /. So in this way you could map all URLs with the pattern
/blog/:postSlug to your post template, or all URLs with the pattern
/:pageSlug to your page template, and
/ to your home template.
The problem with this is that these url structures are defined by WordPress in the Permalink Settings so you can’t just hardcode them like in the example above. And worse those settings aren’t accessible by default to the REST API.
Sure you could extend the API to expose those settings, but that doesn’t actually solve the problem, because URL patterns in WordPress don’t have to be unique between resource types. The user could define their posts to use /:postSlug, which would be the same structure as the /:pageSlug pattern that automatically applies to pages and then CPTs could even further complicate things. Distinguishing between the types now requires extra logic which defeats the whole purpose of mapping URL patterns to templates in the first place.
So what do we do?
Forget about patterns. We have to dynamically register individual routes after we get back the resource from the API according to its own individual link attribute. In this way each resource (post, page, or CPT) gets its own routing rule. Our routing logic now looks like this (pseudocode):
gets posts from the API, for each post get the post.type and post.link, map the link to the correct template according to its type
The routing rules that get generated from this logic no longer have parameters; they’re full static links. If you download two posts then two rules get generated. Instead of a blanket post pattern of /post/:postSlug, you have /post/example1 and /post/example2 that both get mapped to a post template.
Confused? Think I’m crazy? Let me know in the comments.