One of the largest draws of headless or decoupled WordPress architecture is that we can endlessly remix the WordPress CMS that content creators love with the latest and greatest of web technology. This presentation will demonstrate how quickly a developer can get up and running with headless WordPress development using the Remix full stack framework. Using open source tools like WPGraphQL and Atlas Content Modeler, you can transform your WordPress site into a powerful headless CMS that serves data via GraphQL. We’ll see how we can query for and use that data in a Remix app, and also discuss how the framework’s focus on web fundamentals can benefit both the developer and the visitors who use our sites.
Remixing WordPress: Building a Headless Site with Remix, WPGraphQL, and Web Fundamentals
React Summit 2022
What's up, react Summit? My name's Jeff Eberhardt, and I'm a Senior Developer Advocate at WP Engine. The title of my talk today is Remixing wordpress, where we're going to spend about 20 minutes building a headless site with remix, WP graphql, and a few other open source plugins. But before we get started, maybe we could talk broadly about what headless wordpress is. With the rise in jamstack architectures, we see a ton of SaaS offerings in the headless CMS space. But if you're looking for an open source alternative, a lot of people don't know that wordpress can also become a headless CMS. And with wordpress being the most popular content management system, which is powering roughly 40% of the web at the time of this recording, there are chances that your content teams are already well-versed in the wordpress platform, so there can be a lot of reasons to consider this as an option. So let's take a second and talk about what traditional wordpress gives us. Traditional wordpress is your basic monolithic software platform. The visitor maybe interacts with a CDN by typing in a URL into their browser bar, but ultimately all of those requests are served by wordpress core. And any of the developer experience that needs to happen, whether that's theming or plugin development happens directly with that wordpress core software, as do the publisher interactions. So when people are creating content, they're doing that as a part of the wordpress monolith. Now when we look at headless wordpress in comparison, that offers the developer a lot of flexibility. The experience for the visitor is still the same. They type this URL into a browser bar, but instead of their request being handled by wordpress core, it is actually handled by an application that we ultimately determine what that looks like. And so in the example today, we're going to use remix as this node.js runtime, but that could be any potential front-end framework, whether you're a fan of Nuxt or Next or you want to use SvelteKit. That is entirely up to you as the developer, and all of your effort is going to be into developing that platform. And all of that interacts with wordpress through either the REST or graphql APIs that we're going to explore today that are a part of wordpress core or added via plugins. But the ultimate benefit is that the experience for the publisher or the content creator doesn't change and they get to use wordpress sort of as is to create and publish content while we as the developers get to take the benefits happening in the front-end framework space and bring those to our users. So let's get started. We don't have a ton of time, so I'm going to hop right into building out our headless site. So the first thing that we'll need to do is create a new wordpress site. And I'm going to do that using the local development tools. I'll click create new site, give my site a name, select the preferred customization option, create an admin username and password, and then I'll let local install those services for me on my local machine. If you have a wordpress site already, feel free to use that as well and install the plugins there. Once that's done installing, we can open up the WP admin panel, log in, and then we'll need to install a few plugins. First we'll want to install the WP graphql plugin, which essentially turns our wordpress app into a WP or a graphql api. You can install and activate that plugin. And then we will also be installing a plugin called Atlas Content Modeler, which is a headless focused content modeling plugin that will allow us to create complex content types and make those available over graphql immediately. Now with our plugins installed, we can work on creating our first content model. To do that, we'll use Atlas Content Modeler to create a content model around projects. Do that by specifying a singular and plural name, determining the api visibility of our content model, whether it's public or private, choosing an icon for the post editing screen and creating a description for our users to see. When we click the create button, that will automatically generate the model and begin the process of adding it to our graphql api. With our model created, we can begin the process of adding fields to our model. We'll start with a text field that stores the name of the project. We'll use that as the entry title, make it required, and add it to our model. After that, we can add a date model that will store the due date for the project, make that required as well, and add a description for that model that will be displayed to our users. From there, we will add an additional text field to store the assignee's name for the project and this will be the DevRel Associate who is assigned to a particular piece of content. Make that required as well. And then we will add one last rich text field that will be the project description. So now that we've created our project content model, let's add some data. We'll open up the post editing screen and fill out all of the fields that we specified as we created the model. And then when we're done, we can publish that piece of content to make it available for use in our application. Now that we've seeded our project with some additional data, let's explore the features of WPGraphQL in the Graphical IDE. The query composer feature of the Graphical IDE allows us to visually explore our graphql schema and select individual content types and their nodes and properties, all while composing a graphql query that we can run dynamically and to return actual data from our wordpress site. So now that we understand the resources available to us in wordpress to model data and to serve that data over graphql, let's talk about how we're going to integrate remix into that equation. So I've got a pretty basic remix app set up right here with a few dependencies installed that we're going to need to communicate with our wordpress server. So one of those is the graphql package. I've also installed apollo Client and I've done some styling with Tailwind css. So where we can get started is in our index.js route. We can look here, we can see that we're importing two different components. We've got just a basic header and a basic project component that's going to list our projects out in cards on this page. So what I'll do over here is in my terminal, just click npm run dev to get this dev server running. And then I can come back over here and we'll just refresh on localhost 3000 and see that this test data array where I've got some projects that we'll eventually swap out with data from our wordpress site is rendering right there for us. So the next thing that we'll want to do is configure apollo Client to query our graphql endpoint from our wordpress site. To do that, we'll begin by creating an apollo.js file in the lib folder. And then we'll add some import statements to import those needed resources from that package. And then what we'll do is create a default export that is going to be our client. And we'll set that equal to a new apollo Client. Now we'll pass in some configuration parameters. And one of the ways we can get our graphql endpoint is to hop back into our wordpress site, open up the WP graphql settings page and copy and paste that into our project. And we'll also create a new in-memory cache that we can use to help speed up our application. And once we're done, we'll save those things out. So now that we've configured apollo Client, I'm going to create a new file to contain my data fetching code. And really this part is entirely up to you about how you want to structure or where you want this code to live. So I'm going to create a file called wordpress service. In there I'm going to import the client that we configured in the last step. And then also the GQL object from apollo Client. So then we'll create an export and export a function called get projects. And then from here, what we'll do is hop back into our wordpress WP graphql instance, copy the query that we were using, and then we'll create a new query, open up a new template literal with GQL and paste that query in there. Then what we'll do is create a new variable called response and we will essentially use the client to query and pass in that query to that configuration object. Then once we get that response back from apollo Client, we will dig down into the object that we get back so that we return the individual project nodes. It's always helpful if you're unsure of how to traverse a particular object, you can always hop back into WP graphql and visually look at what the data looks like that you're going to get back from that api call. Okay, so now that we've got this function stepped out to make our graphql query, let's pause for a second and just take stock of where we're at. It's worth mentioning that although I've created this wordpress service file with these export functions, how you handle this organizing this data fetching code is really entirely up to you. I know lots of react developers in particular are fans of co-location, so having the code that does their data fetching also inside of their components, there are ways that you can achieve that as well by having some of this code happen in the individual route files. So this is just a choice that I've made because I like this organizational pattern, but that doesn't mean that you couldn't make another choice and remix is unopinionated about this as well. As we transition into looking into how we use this function to load data into our route components, remix is actually opinionated and we'll talk about some of those differences. Next we'll take a look at this index.jsx file in our routes folder. The remix framework uses a file-based routing system, which is kind of similar to what you might get in next.js or Nuxt if you're coming from the view world. So there's a decent amount of overlap in how this framework handles routing. And we'll look at some other more complex examples in just a little bit. But so if we open up our index.jsx file, we can see that we're importing some components in here and then we're exporting this index component that returns all of these jsx elements that are going to render our application. But right now we have all of our data hard-coded in this projects variable. And so the next step that we're going to want to tackle is loading our data dynamically from wordpress. So to achieve that, we'll just throw a couple more import statements up here. And I'm just going to copy those from off screen real quick. We're going to import two different things. So we're going to import our get projects function from this wordpress service file. And then we're going to also import this use loader data hook from remix itself. So once we've done that, then we'll come down here and we're actually going to create another export. So this is going to be an async function named loader. And that is a remix convention. And what's going to happen is when remix goes to render your components, it's going to call whatever is inside of this loader function on the server. And it's going to go out to your service, whatever that may be, in our case, wordpress and graphql to get some data. And then we will be able to access the data that this loader function returns using the use loader data hook. So for us, all we need to do in this example is add a return statement, and then we're going to await the result of get projects. And so again, that's one of those reasons that I personally like to develop in this way. So I like having a service file with all of my different data calls out there so that my route components can be very clean. Alternatively, you could certainly do some of this inside of this loader function. That's totally up to you and how you want to handle it. And to be honest with you, I'm not sure remix really cares either way. But so we've got this loader function, we're doing some amount of work in there, and that's up to us to determine what it is that we want to do. And then what we'll do down here is instead of hard coding this array, we can come and we can just use the use loader data hook. So we'll set projects equal to the result of this use loader data function. And so if we save that out and refresh our page over here, we can see that boom, that worked just as we expected. When this index route gets called, we go to render that component. This loader function calls get projects on the server. We return that data and then use that data via this hook to populate these project components down here. So fantastic. We've got our index page working the way that we want. But I think it might be time to pause for just a second and discuss this opinionated choice that the remix framework makes. I've worked with a number of different next.js projects and other react projects where some of the data loading that we're doing happens entirely on the client. And so if we're used to using the apollo like use query hook, for example, and having those components make those calls out to our graphql server on the client, this is a bit of a paradigm shift if you will, right? Because each one of these loader functions is only getting called on the server. And I think the remix team made that choice for a really good reason. And they've enumerated those reasons in a lot of videos, so I won't belabor them and I encourage you to go check out some of the remix singles to get a good idea of those. But in some ways, it does help us as developers simplify the type of code that we're writing, right? If we look at a framework like next.js, for example, where maybe we're doing static site generation and then some things are getting loaded on the client, it has a tendency to get a little bit messy, right? Because we have all these different abstractions for these different use cases. I think remix sort of simplifies that. It says this is the one way that we're going to load data into components. And so it makes that a little bit easier. It also makes it a little bit easier to handle credentials, right? If I were making authenticated calls to a database, since all of this data fetching in the loader function is happening on the server, I don't have to worry about leaking my credentials into the client. So there are a couple of pluses to this that I definitely recommend you explore if you're new to remix and kind of compare and contrast with whatever your framework of choice is at the time. So now that we've got our index page looking the way that we want, each one of these is actually a link to an individual project. But right now, if we click on it, we get a 404 error. So what we'll do in the next few minutes is just dig one layer deeper and look at how we would handle this dynamic routing example with the remix framework and wordpress. Okay. So for our project to handle this dynamic route URL, we're going to need to make some changes to our routes folder. We can see that the URL that we're sending the application to is project slash the slug of the particular project. So inside of our routes folder, we will create a new folder called projects to match that part of the URL segment. And then inside of that projects folder, we're going to create a new file called $slug.jsx. And this is going to be the component that we export. So I'm just going to come over here real quick and copy this from off screen and paste that in just so we can take a look at it. And we'll go ahead and save that out. And then we'll sort of just talk through a couple of different things here. And we can see already in the terminal that we've got some work that we need to do. So let's hop back. I'm going to save this one more time. We'll hop back into our wordpress service and then get going from there. So we can see that our component is using this get project by slug function. So we'll hop back in there and we'll go ahead and we'll stub that out. And so this isn't going to return anything at the moment. Get project by slug. And then this is going to take the slug as a parameter. And so now that we've got this stubbed out, let's hop back into our wordpress instance. And this is where we'll use the graphical IDE to create our graphql query. So again, using the graphical IDE in the query composer, we can use those tools to help us visually create and format a new query to get our post by the slug. And if we open up the individual sort of project node inside of the query composer, we can use that and specify the type of ID that we want to look up the post by. So in this case, we'll use slug and we'll grab an ID from our wordpress site and pass that in. And then what we'll do is also add in all of the additional fields that we had on our previous query to make sure that all of the data is the same. We can just click those to add them to the query. And then once we're done, we can again, preview the result of that query by just running it in the IDE. And so once we have our graphql formatted the way we want and making sure that it's returning the data that we want, we can copy it and paste it back into our wordpress service file. We'll make a few changes inside of this GQL template literal to parameterize this query so that we can pass in the ID. Then after that, we'll actually make the query with our client object and pass that formatted query into it, as well as a variables property that takes an object where each property in that variables object, like ID, for example, corresponds to one of the variables that we parameterized in the tag template literal. Then we'll also just sort of traverse our response from graphql and return that to our loader function. And as always, if you're kind of confused and you need help figuring out what your data looks like, you can always use the graphql IDE to explore that. So it's worth talking really quickly about the loader function as it's implemented in the slug.jsx file. A lot of this should look very familiar to what we did in our index file, with the exception of this params object that is getting passed into the loader. This is how we access those dynamic URL components. So $slug becomes params.slug, and then we just pass it into our get project by slug function that we pull in from our wordpress service. And if we come over here and we refresh our page, you can see that everything is working as we expect. And now we have both an index page and dynamic URL segments that are looking up individual posts. Thank you so much for coming to my talk on remix and headless wordpress. If you're interested in learning more about the open source headless CMS alternative and some of your favorite front end frameworks, be sure to follow me on Twitter or check out some of the content at developers.wpengine.com. Thanks. Thanks.