Rebuilding JumpOff.io With React, Netlify, GraphQL and WordPress
The Project
After years of using a traditional WordPress site for jumpoff.io, when it came time for a redesign of JumpOff’s own site, I decided not to just build a new theme. I wanted to provide a better browsing experience by incorporating more modern web development technologies while still using WordPress to manage my content. What I ended up with is a Netlify hosted React app fetching data from a WordPress install via GraphQL.
In this post I’ll explore my thought process, as well as some of the benefits, drawbacks and techniques I discovered along the way. While I am very happy with this approach for my site, it’s certainly not for all use cases, and I’ll discuss some potential problems with it at the end of the article.
Redesign Goals
Here were my primary goals in the website redesign:
- Faster load times
- Smoother page transitions
- Better code deployment process
- Keep my blog content in a WordPress install
- Cleaner, updated look
New Website Architecture Diagram
There’s a lot going on here, so a diagram is helpful to show how everything fits together:
Why Not Just Create a New WordPress Theme?
While a traditional WordPress setup has a lot of strengths (and I still think it’s a great choice for many situations), it has some major downsides as well. One of the biggest downsides is that the application code runs on the web server, so every time a user navigates to another link on the site, it requires an entirely new HTTP request to the server, that request is processed, and a whole new document is sent back to the browser. This results in a lot of data being sent back and forth, when often, we already have a lot of that data loaded in our browser. If we’re looking at one blog post and we navigate to another, do we really need to send a new header, footer, stylesheets, etc.? Why can’t we just update our route and request the content that is actually different?
Client Side JavaScript Apps to The Rescue
In recent years, client-side JavaScript applications (like React, Vue, Angular and Ember) have solved that problem. By allowing our application code to run in the browser itself, we can navigate to a new page without a full page refresh. If we need to get new content, we navigate our new route almost instantly, and make a request to an API for our new content if we need it. This means much faster page loads. It also means we can control how the page transitions. Instead of a jerky white screen popping up for a bit while our browser processes the request, we can fade, slide, show a loading animation, or even a star wipe!
The majority of the work for the rebuild went into the React Application, which is available on Github.
Headless WordPress as a CMS
So we have our client side application, but we still need somewhere to manage and store our site’s content. The addition of the WordPress REST API made this possible by exposing WordPress content to other applications. I’ve used the REST API for other projects, but recently, another option has become available.
WPGraphQL
A WordPress plugin called WPGraphQL provides WordPress sites with a graphql endpoint. This allows us to get data from our site using the GraphQL query language, meaning we can ask our site for exactly the data we need, as opposed to a traditional REST API endpoint where we get everything that comes back from that endpoint, and we may need to make multiple round trips to get all the data we need. Using WPGraphQL will also allow us to use Apollo GraphQL in our client application, which has many cool features we can take advantage of later.
Below is an example of how I query my WordPress site’s GraphQL endpoint inside a component for a blog post. Note that I can declare exactly the information I want to query, (id, title, slug, date, content, and featured image src url), instead of receiving a huge JSON object from the rest API that tons of information that I may or may not need.
import React, {Component} from 'react'; import './SinglePost.css'; import LoadingShape from '../LoadingShape/LoadingShape'; import NotFound from '../NotFound/NotFound'; import Post from '../Post/Post'; import { Query } from "react-apollo"; import gql from "graphql-tag"; const SINGLE_POST_QUERY = gql` query detailView($slug: String!){ postBy(slug: $slug) { id title slug date content(format: RENDERED) featuredImage { sourceUrl } } } `; class SinglePost extends Component { render(){ let slug = { slug: this.props.match.params.post_slug } return <section className="blog"> <div className="jo-row"> <div className="jo-content"> <div className="single-post"> <Query query={SINGLE_POST_QUERY} variables={slug}> {({ loading, error, data }) => { if (loading) return (<LoadingShape/>); if (error) return (<NotFound/>); if (data) return ( <Post post={data.postBy}/> ); }} </Query> </div> </div> </div> </section> } } export default SinglePost;
Extending WPGraphQL
Since my site uses custom post types for my portfolio projects, I need to extend WPGraphQL to allow queries to our custom post type. Luckily, WPGraphQL makes this very easy. On my WordPress install, I have a custom plugin that registers a custom post type for portfolio projects. All I had to do to make this custom post type available was add 3 parameters to the end of my register_post_type() call:
‘show_in_graphql’ => true,
‘graphql_single_name’ => ‘project’,
‘graphql_plural_name’ => ‘projects’
// Adding Custom Post Type : Project. add_action( 'init', 'jo_project_register' ); function jo_project_register() { $labels = array( 'name' => _x( 'Projects', 'Projects', 'jumpoff-api' ), 'singular_name' => _x( 'Project', 'Project', 'jumpoff-api' ), 'add_new' => _x( 'Add New Project', 'Project Listing', 'jumpoff-api' ), 'add_new_item' => esc_html__( 'Add New Project', 'jumpoff-api' ), 'edit_item' => esc_html__( 'Edit Project', 'jumpoff-api' ), 'new_item' => esc_html__( 'New Project Post Item', 'jumpoff-api' ), 'view_item' => esc_html__( 'View Project Item', 'jumpoff-api' ), 'search_items' => esc_html__( 'Search Project', 'jumpoff-api' ), 'not_found' => esc_html__( 'Nothing found', 'jumpoff-api' ), 'not_found_in_trash' => esc_html__( 'Nothing found in Trash', 'jumpoff-api' ), 'parent_item_colon' => '', ); $args = array( 'labels' => $labels, 'public' => true, 'menu_position' => 5, 'exclude_from_search' => true, 'show_ui' => true, 'capability_type' => 'post', 'show_in_nav_menus' => false, 'hierarchical' => false, 'rewrite' => array( 'with_front' => false ), 'query_var' => true, 'supports' => array( 'title', 'editor', 'author', 'excerpt', 'thumbnail', 'comments', 'page-attributes', 'custom-fields' ), 'has_archive' => false, 'menu_icon' => 'dashicons-media-spreadsheet', 'show_in_graphql' => true, 'graphql_single_name' => 'project', 'graphql_plural_name' => 'projects', ); register_post_type( 'project' , $args );
But since I’ve also used Advanced Custom Fields to create custom post meta for this project post type, I’ll need to add these fields to our WPGraphQL schema as well. This is done with the code below:
// Register Custom Fields With WPGraphQL add_action( 'graphql_register_types', 'jo_register_cpm_with_wpgraphql' ); function jo_register_cpm_with_wpgraphql() { if (function_exists('register_graphql_field')) { //Project Link register_graphql_field( 'Project', 'projectLink', [ 'type' => 'String', 'description' => 'Project Link', 'resolve' => function( $post ) { $project_link = get_post_meta( $post->ID, 'project_link', true ); return ! empty( $project_link ) ? $project_link : 'default url'; } ] ); //... (A whole bunch more custom fields omitted for brevity's sake) //Portfolio Image register_graphql_field( 'Project', 'portfolioImage', [ 'type' => 'String', 'description' => 'Portfolio Image', 'resolve' => function( $post ) { $image_id = get_post_meta( $post->ID, 'portfolio_image', true ); $portfolio_image = wp_get_attachment_image_src( $image_id, 'full' ); return ! empty( $portfolio_image[0] ) ? $portfolio_image[0] : null; } ] ); } }
So, by using WPGraphQL and my own custom plugin, I can quickly and efficiently get and send all the data I need back and forth from my client side app and my WordPress CMS. This allows me to continue to managing content through WordPress’ familiar backend, and utilize its user management tools, and some of its plugins, while getting to use Apollo GraphQL in my client side app. One of the great things about Apollo is that it paves the way for us to use “cache-and-network” fetching and offer offline support for the site. I haven’t implemented these yet, but they’re on the roadmap.
Going “Serverless” With Netlify
Originally, the JumpOff WordPress install (which has been moved to api.jumpoff.io) and our web application server (jumpoff.io) were both running on the same VPS. I had an Apache web server serving the WordPress site, and a proxy routing the web application requests to a NodeJS server running on the same VPS. The NodeJS server would serve up a server-side rendered React application, (which means upon a request, the node server would fully render and server the React app’s markup).
Along came Netlify, which offers really fast “serverless” hosting (which is probably more accurately called build-time rendering). The idea behind Netlify is that you can dispense with all the headache of managing a server, and use their service to just serve up static sites very quickly, as your (static) site won’t be running code on their server every time a request is made. This is useful for people using static site generators like Gatsby or Jekyll.
I’m not using a static site generator, but serving up a React application (without server-side rendering) is basically just serving up a static site, so I realized I could use Netlify to host jumpoff.io (for free), to reduce my server load, improve my site delivery time and making my deployment process much smoother (one push to my GitHub repo and my new code is deployed). This did require removing the server-side rendering for my app, but as I’ll discuss below, I think I can mitigate any potential SEO issues caused by this.
SEO
One of the big stumbling blocks I encountered in this approach maintaining good SEO practices. Admittedly, I’m not an expert in SEO, but I’ve tried to maintain reasonably good practices. Out of the box, WordPress does a great job with SEO. The content is easily crawlable by bots, and it generates sitemaps for you. When you switch to an approach like I’ve used, things become more complicated. This new approach means I’m leaving all that SEO goodness behind, but I believe I’ve addressed the major challenges with the techniques below. To start, I’m focusing primarily on Google. Mileage on other engines may vary.
Crawling Content
First off, there is some question as to whether your content in JavaScript app can be properly crawled by search engines. Since the browser is initially served what is basically an empty html shell, the concern is it won’t see the content that is subsequently generated by the JavaScript app. I’m not sure about all search engines, but based on my testing on Google Webmaster tools, it has been able to crawl my application.

Mobile page fetch test on Google Webmaster Tools.
Meta Tags
Another concern is generating the proper meta tags for pages. If a page is retrieved via JS from a GraphQL API, how will it have the proper meta tags? To address this problem, I used React Helmet. Utilizing this, you can generate the proper meta tags from inside your React Apollo <Query> component, like this:
return <article id={postID} className="jo-post"> <Helmet> <title>JumpOff - {title}</title> <meta name="title" content={title} /> <meta name="description" content={metaDescription} /> <meta name="url" content={postLink} /> { featuredImage && <meta name="image" content={featuredImage.sourceUrl} /> } </Helmet> { featuredImage && <img className="jo-featured-image" src={featuredImage.sourceUrl} alt={title}/> } <div className="jo-post-content-wrapper"> <h1 className="jo-post-title black-box-text" dangerouslySetInnerHTML={{ __html: title }}/> <div className="jo-post-content" dangerouslySetInnerHTML={{ __html: content }} /> </div> </article> }
This generates meta tags with whatever we want. In this case, I’m injecting data fetched from my GraphQL API.
Sitemaps
Now for sitemaps. This was a little tricky. I could generate a sitemap.xml file at jumpoff.io at deploy time, and Netlify would serve this sitemap just fine. But that sitemap would only know about the routes I’ve defined in the React app. So it would know about the main page (jumpoff.io), the about page, (jumpoff.io/about), the portfolio page, etc… But how would it know about a specific blog post like jumpoff.io/blog/name-of-post-fetched-from-wordpress-install-by-client-side-app? Well, it wouldn’t know about it, and that’s a problem.
To solve this issue, I’ve decided to serve my sitemap from my WordPress install at api.jumpoff.io. I simply use Netlify’s redirect rules to direct requests from jumpoff.io/sitemap.xml to api.jumpoff.io/sitemap.xml, then in my custom plugin on my WP install, I wrote code to detect changes in posts and generate a sitemap from all the blog posts and the custom portfolio posts that my React app queries, as well as the main routes defined in my React app.
This approach isn’t perfect. If I change or add a route in my React app, I’ll have to manually change the code in my plugin to reflect this. To solve this, in the future I may have the React app generate some sort of informational file about its route structure, and then have my WP plugin periodically do wp_remote_get() to get this information. Additionally, I’m not 100% sure that using a 301 redirect to serve a sitemap like this is OK, but in Webmaster Tools, Google seems to have read the sitemap and its URLs. This is something I’ll have to keep an eye on. If any SEO gurus out there have suggestions I’m all ears!
Wrap-Up
The new site is still a work in progress. This new architecture has some benefits, and some kinks that need to be ironed out, but ultimately I think there’s a lot of potential. There are more moving parts now. But once I got everything configured, the user experience, load times, developer maintenance, and versatility of the site have all been improved, and I’ve reduced the load on my VPS. Plus I can still manage all my content via the WordPress admin that I’ve always used.
Benefits
- Faster load times, especially after the first pageload when the app is delivered
- Smoother page transitions (now that I have more control over these, I plan to make some cool transition animations)
- Much better code deployment process
- Reduced the load on my VPS (much of the data transfer has been offloaded to Netlify’s free hosting)
- Potential to use “cache-and-network” strategies and offer offline support for the site
- Can utilize many of the npm packages out there to add functionality to the site
Potential Issues
- SEO Concerns (I’ll keep an eye on this)
- Loss of functionality from plugins (for instance, I use the Code Snippets CPT plugin for my code snippets, and I’ve lost the syntax highlighting styles they apply, so I’ll need to figure out a way to address this myself)
- Links within post content fetched from WordPress are not recognized as React Router Links, so they will trigger full page refreshes (I hope to figure out a way to get React Router to handle these links)
- 2 potentials points of hosting failure now, Netlify server and my VPS
- No JavaScript, no site (I’m OK with this for my site, but for some sites this is unacceptable)
- CORS scripting issues (I’ll write about how I resolved this later)
Who is This Technique For?
- Sites who want an app-like experience, offline support, to control transitions and prevent full page reloads by using client-side Javascript frameworks, while maintaining content via the WordPress interface
- People who would like to reduce load on their WordPress server by utilizing Netlify to serve the shell of their website
- Developers who are more comfortable templating in React than in PHP/WordPress, or who would like to learn React or GraphQL
- Sites want a decoupled stack and may eventually be looking to replace WordPress as their method for managing/storing data
Who is This Technique Not For?
- Sites that need to function for users who have disabled JavaScript
- Existing WordPress sites that rely heavily on themes or plugins that affect the frontend of their site
- Existing WordPress sites where SEO is crucial and they can’t rely on search engines to render their JavaScript apps
- Developers uncomfortable working with JavaScript, Node, React or Git
Thoughts?
This site is a work in progress and I’m continually learning more about this new way to build sites, so if you have any thoughts, ideas, or examples of sites you’ve built using similar techniques I’d love to hear about them.