Key Takeaways
Headless WordPress leverages ACF and WPGraphQL for modern web experiences. ACF models custom data beyond posts/pages, while WP GraphQL provides a powerful API for querying content types efficiently.
Faust framework enhances headless WordPress development. Faust simplifies building sites, supporting content editors, and enabling post previews and authentication seamlessly.
Local development tools like Local and WP Migrate streamline workflow. Easily set up and migrate WordPress resources locally for efficient development processes.
Headless WordPress hosting on WP Engine integrates WordPress backend with Node.js for seamless performance. The all-in-one solution simplifies managing headless sites with a unified dashboard.
Everyone has their favorite tools when it comes to WordPress development, and the idea of building a headless site without those tools can be daunting. Luckily, two favorite tools of WordPress developersāACF and WPGraphQLācan help you dip your toes into headless development.
In this session, WP Engine Senior Developer Advocate Jeff Everhart shows off the ins-and-outs of the infrastructure by building a demo site in just minutes using Headless WordPress!
Session Slides:
Transcript:
JEFF EVERHART: Whatās up, everyone? My name is Jeff Everhart, a senior developer advocate here at WP Engine. Thank you so much for joining my talk today on building your first headless WordPress project with ACF and WP GraphQL. Before jumping into building, I always like to give some context on what headless WordPress is. It seems like headless is becoming less of a niche term and more mainstream, so Iāll try to keep the explanation short.
Headless WordPress at its core is about using the WordPress CMS through an API to power some other type of application. If we look at the diagram in this slide, when a user requests site.com/page, instead of having WordPress handle that request, a JavaScript application responds to that route. It looks at the path, determine what data it needs from WordPress or other sources, makes those APIs calls, and generates a response to the user.
Itās important to note that whatās in that Node.js runtime slot could really be any number of different types of applications or clients. Weāve seen various examples of mobile apps or websites built with all of the most popular frameworks use this pattern with great success. Now that you have a good idea of what headless WordPress is, letās walk through the tech stack that weāre going to use for todayās project.
At our back most layer, weāll use WP GraphQL as our API. WP GraphQL turns your WordPress backend into a powerful GraphQL API. It enforces all of the same role and capability security checks that native WordPress and the core REST API do. WP GraphQL is a great choice for developing headless applications because using its declarative query language, we can query for different content types all across that content craft. So getting posts, pages with their categories all in the same request.
Now, letās see how we can structure and model some more complex data using the WordPress CMS. Many devs approaching headless projects need to model and store data that is different, right? Extends beyond WordPressā typical posts or pages. ACF or advanced custom fields is a great tool for modeling those custom fields. In an upcoming release, it will become even more powerful by allowing you to register post types and taxonomies directly in the UI in the free version of this plugin.
It makes all of the data available over the REST API easily but will need to install a WP GraphQL extension to add some of that field data to our GraphQL schema. Personally, Iām really excited to see this plugin evolve as it becomes more powerful for headless developers.
Now, that we can model complex data and query it through WP GraphQL, we need some way to create those modern web experiences for our users while also supporting the content editors that already know and love WordPress. Thatās where Faust comes in. Faust is a two-part framework consisting of a WordPress plugin and a Next.js based JavaScript framework. They work together to make building headless WordPress sites easy and intuitive.
It adds support for things like post previews and authentication out of the box, and you can lean into a re-imagined WP template hierarchy experience in modern JavaScript. Faust.js aims to be a very extensible platform and ships with a plug-in system and an admin bar that you can use to customize and create experiences that support your particular use case.
Last but not least, weāll need some ways to work with these tools locally and in production. Using local and WP migrate, Iāve pulled all of the WordPress resources youāre going to need for this project into a single zip file that you can drag and drop into local to get started. Once weāre done building our site, we can push our work to the Headless WordPress platform. Headless WordPress WP Engineās all in one headless hosting solution that combines your WordPress backend and a Node.js container all tuned to work together available through one seamless dashboard.
So now that you have a good understanding of all the tools that weāll be using in todayās presentation, letās jump right in and start building. Open the URL in this slide to access the demo code in the browser. Iāve tried to make this experience as easy as I can to follow along with, but weāll be covering a lot of ground in the next 25 minutes, so feel free to just watch now and refer back to this GitHub repository and the recording of this session later to keep working after the conference. If the rest of decode is a car show, then this session is on building engines. Pun totally intended.
Now that we have the GitHub repository open, letās get started. The first thing I recommend you do is make a fork of this repository in your own account. If you look through whatās contained in this repository, youāll see a bunch of sample code as well as some step by step instructions in the readme for everything that weāll do in this presentation. In fact, weāll do a lot of copying and pasting directly from this repository into your project. To clone this down locally, letās go ahead and run the Git clone command inside of your terminal.
Thatāll take just a second to download, and then once weāve downloaded the project, letās go ahead and change our directory into that project directory. From there, Iām going to run a command to open this project in VS Code, but you should feel free to use whatever code editor you want. Since VS Code has an integrated terminal already, I can clean this up and then just go ahead and close out iTerm. And now, we can transition to getting our WordPress local site situated. To do that, weāre going to open this project in Finder and then locate the headless WP demo zip file that Iāve prepared for you.
Weāll drag and drop that zip file directly into the local development environment, and local will start the process of unpacking and bootstrapping your demo WordPress site for you. We can pretty much stick with the defaults, and then local will take just a few minutes to create you a new WordPress site. Now, this is a great feature of WP migrate pro that allows me to export any site as a zip and drag and drop that directly into local so that I can take a production site to my local machine very quickly, regardless of the platform.
Once thatās finished installing, you may want to go ahead and trust this SSL certificate, and then weāll go ahead and open this project up in WP Admin. From there, weāll go ahead and just minimize local, since itās already running, and we donāt really need to do anything else with it. So from there, weāll open up our repository, where youāll find an admin username and password that Iāve already created for this demo site. You should be able to use those credentials to go ahead and log in to the WP Admin dashboard. From there, weāll take just a second to get situated with this local WordPress install.
The first thing that weāll do is take a look at the plugins that are installed with this WordPress site. We see things like advanced custom fields for data modeling. We have fouls to enable some of our headless capabilities, WP GraphQL to act as an API for our site, and then WP GraphQL for ACF extension to surface some of the ACF field groups. Now, letās take a look at the posts as well. If you look at all of the different demo posts that Iāve pre-populated inside of this project, we can see we have a number of different posts with a bunch of different categories.
If we take a look at the content inside of each post, weāll see that weāve got a bunch of body content, some images, featured images, and some ACF field groups that weāve already created and pre-populated. So letās take a look at the field groups in more detail. If we open up the ACF menu, youāll see we have two field groups createdā food resources and sweet mixtape. Letās take a look at food resources first.
This is just a really basic field group with two individual fieldsā text and URL. For each one of these things, I have the Show in GraphQL option checked so that they appear in GraphQL. Then I also have checked that option at the field group level. In addition, I have some conditional logic to determine when to render these postsā so if the post category is equal to food. And letās check out what sweet mixtape looks like.
Weāll see that this looks pretty similar to our food resources, and that we have a couple of different fields. Each one has the Show in GraphQL option checked, as well as having that checked at the field group level in addition. We can see that there are a couple of different settings that the WP GraphQL extension gives us that we may not need. In addition, we can see that weāre conditionally displaying this based on post category as well.
So now, letās take a look at WP GraphQL and what that gives us in terms of tools. If we open up the GraphQL menu, weāll get dropped inside of the graphical IDE. Now, this is an interactive development environment that allows you to create queries using WP GraphQL. We can specify that we want, say, the first 10 posts with categories and include our additional ACF fields on here as well. When we click the Run button, we get back live data from our WordPress site that matches the data in that query.
If we want, we can open up the Query Composer window and visually compose aspects of our query. So this is a really handy tool if youāre not sure what particular fields or data might be on a particular WordPress object. You can use the Query Composer to explore and run those queries in real time. Now, letās move to configuring Faust by opening up the Faust settings menu. As I said in the intro talk, Faust is really a two-part framework that consists of a WordPress plugin and your front-end code base.
So if we come back into the WordPress plug-in settings menu, we can see that weāve set this option for front-end site URL, which is going to point to the address of our front-end site. Right below that in the secret key option, weāre going to go ahead and click regenerate to generate a unique key that you can use for your demo project. From there, letās actually hop back into the JavaScript code base, and weāre going to run this command to copy a sample environment variable file into one we can use with our site.
Once weāve run that, command open up the .env.localfile, and weāre going to make a couple of changes. The first thing is that will replace the next public WordPress URL option with the location of your local site. So ACF.WPEngine.local. And then weāll also take that secret key value and use that for our Faust secret key. Make sure we want to uncomment that as well, and then that last environment variable is really just useful for testing and local development so that you can smooth out any SSL issues you may have while connecting to the local development environment.
From there, weāre going to want to run NPM Install to install all of our projectās dependencies, and then once thatās done, we can kick this off and run NPM Run Dev to start our project. Thatāll take just a second to compile, but once it does, we can open localhost:3000 in the browser, and we should see our Faust site in action.
Once that renders, you can see that Faust is already handling some magic for us. If we look at that menu in the top right hand, we can see that itās already pulling in pages and content from our menus that weāve defined in our WordPress backend, and if we hop back into our WordPress Admin, we can look at how this connection works in a little bit more detail.
Because the Faust plugin knows the address of our front-end URL, it rewrites a lot of the links, such as those preview links so that when you open them in the browser, they open in the front-end site URL, so that youāre getting a real live preview of what your content is going to look like on the front-end.
Now, letās take a deeper dive into the structure of our Faust JavaScript project. If youāre familiar with working in Next.js, youāve probably used the pageās directory to create page components that implement your routes. You can still do this in Faust, but it builds on this concept by providing a catchall route called the WordPress node that allows you to take any URI that WordPress manages and resolve that to a particular piece of content.
We then get some additional data about that content item and pass that down through our components so that we can resolve that one piece of individual content to a particular template in the WP templates folder. This is very similar to the template hierarchy concept and traditional WordPress theming, and lots of the naming conventions. Match for example, frontpage.js is the front page of our website, while page.js is responsible for rendering things of the page content type. And single.js is there to render single posts.
Now, letās start and make our frontpage.js just a little bit more dynamic. OK . So to get started, weāll go ahead and open up our front page.js file and give ourselves just a little bit more room to work. So if we look at the contents of this file, we can see there are three main pieces. Thereās the component itself that we render, a query property that gets attached to the component, and this gets run every time we render the component, and then some variables that we can pass in at the bottom.
And as you can see, there are two really main ways to use this. Either using the use query hook inside of the component, or it can get passed down as props into the component itself, and youāll see that in an example later. So letās hop back out to the repository and get started with step 2.1 to update the query for our frontpage.js. So weāll copy that, then head back into the graphical IDE and just play around there for a second. So this query that we have on our clipboard should get the first 10 posts and get a couple of pieces of data associated with each one of those posts.
And so if we click in there and run it, we see that all works great. And so weāll want to go ahead and add that to our component query property. So weāll find a good place to do that and just paste that in there, and do a little bit of reformatting. And so what that does is thatās adding this individual part of the query to that query itself. So we still need to make the results of that query available to the rest of our component, so weāll go ahead and add in this additional line that just extracts that post result into a variable that we can work with.
Now, the next step for making this homepage dynamic is actually creating a component to render those post excerpts. So weāre going to do that and create a couple of files in the components folder, and Iāll mention here thatā Iām going to go ahead and create a postexcerpt.js inside of a post excerpt folder, and Iām really just duplicating the structure of the existing components that are a part of this Faust.js Getting Started project. Youāre really free to do whatever you want here, and Iām just following the framework as itās already been laid out for me as a part of this Getting Started.
So once we have all three of those files, weāll start adding some stuff to them to render those posts. So the first thing that weāll do is put some content in our post excerpt component. And so weāll just copy and paste that from our repository, and we can see that weāre importing that module.css file. And we have a function called post excerpt that is our component that tastes one prop that is the post, then weāre rendering a section, having a link thatās going to go directly to that post URI, rendering the title. Then down there, weāve got category tiles that weāre rendering out as well, and then using dangerously set in our HTML to set the HTML content for the post excerpt.
Now, once all thatās great and saved, weāre going to save that out, but weāll also come in here and add this additional scoped style to our component to style those span tags for our categories, and then weāll just do some importing exporting inside of index.js file to take that from a default export to a named export, and weāll save all of those things. And then the last step to make this available for use in other things is to add one more export line to the index.js file of our components folder. And now that weāve done that, if we head back into the home page, frontpage.js, we should be able to just add an additional import to our existing import statement to use our post excerpt.
Now, weāre going to want to find a place to implement that, and if we come down and look inside of our main element, see that we have some stuff right below the heroā what weāll do is weāll just copy and paste some of the code in the repository and overwrite whatās inside of that main with the use of our post excerpt. And weāll just do a little bit of reformatting to smoosh some stuff around, but you can see there that what weāre doing is inside of that container, we are going to map through the array of posts that we got back as a result of our query and then return a post excerpt component where we pass in the post and give it a key.
And then if we go ahead and save all of that and render it in the browser and refresh, we should see that weāve got a great dynamic home page. Yeah, each one of those titles has a clickable link, as do the individual category tiles. And if we click through, we can see that through the benefit of the templates that already exist in Faust, all of our post stuff is already getting rendered, although weāre missing some of those resource groups that we created using ACF. So if we click through to a second post, we can see that one renders great as well as do all of the pre-made category links that areā weāre just using those category URIs to lean into Faust in rendering that category JS template.
Ok now that we have a dynamic home page, letās move on to getting those ACF fields to render on our single.js template. So go ahead and just clean up our code editor, and then we can open up the single.js file and take a look at whatās inside there. We know that at that top level, we have this component function that weāre exporting that actually takes props, and the component.query property that has a dynamic GraphQL query thatās reading some of those variables that we get back from the seed query.
And what weāll ultimately want to do in the end is display some of those different post resources at the bottom of our post content. So I head back into the repository and scroll down to step 3.1, where we update the single post query, and weāll take a look at that because it starts to pull in both the sweet mixtape and food resource field groups that we created in the earlier step. If I take that query and copy and paste it into graphical, Iāll go ahead and hard code a database ID for a post, and remove just a couple of those different things that we donāt need like As Preview and that other fragment.
If I go ahead and run that, weāll see that I get back some data that contains really what Iād expect. The content, I get back the author, and I get back, importantly, both of those field groups and their data. So Iāll go ahead and copy that query and head back into single.js. From there, Iāll really just replace that part of the query with what Iāve got from my clipboard. We can just go ahead and save that. You can reformat it if you want to, but this is whitespace agnostic, so I think that for the most part that looks just fine.
So from there, weāll want to do the same thing we did in the last step, where weāve made that a part of our query. Now, we want to make sure that we make those variables available inside of our component. So weāll add those to that structuring statement, and then additionally, since we have a couple of different categories for our posts, we want to create some Boolean stuff to help us determine whether or not we should display food resources or sweet mixtape. Because as you can see there, no matter what weāre getting back both, and if there is nothing to find for sweet mixtape fields, those come back as null. So Iāll want to do some conditional checking there, so Iāll add these two lines of code to our file.
And what this basically does is it creates us some Boolean variables that we can use down below to conditionally render templates. In this thing, weāre looking at the nodes for each categories and then basically filtering them to determine whether or not they have food in them or music, and then checking the length. Thereās probably a lot of ways that you could implement this type of Boolean variable, so feel free to change this if you have a cleaner way to do that, but for our purposes here, I think that thatās going to work just fine.
Now, the next step is that weāll actually want to, like in the previous step, create some additional components. So Iāll go ahead and inside of my components file go ahead and create a food resources component. So Iāll create a folder and then a food resources.js file inside of that, and along with that, Iāll create an index.js file as well as a CSS modules file. And now that SCSS modules file is really helpful because it allows us to scope our styles to that particular component. So it feels like funky syntax to do it, but in the end itās a really clean result because we donāt have to write a bunch of classes and stuff.
So Iāll start copying and pasting the component code from the repository directly into those files. We can see that weāve got a food resources function that takes two props recipe name and recipe link, then weāre rendering those out below our nice age with the burrito emoji. Weāll also copy and paste some SCC file styles into this particular folder, and then weāll make sure that we export that from the component folders index.js. And just like in the previous example, weāll want to go ahead and export this in the index.js of our components folder as well so that we can have a really nice group to import, like youāve seen directly from that components folder elsewhere in some of these different files.
So once weāve got that added, weāll turn our attention to the music resources component, and weāll do the same thing. Weāre going to go ahead and create that same file structure. So music resources folder and a musicresources.js file inside of that. Then weāll go ahead and create an index.js file to export it and then also our musicresources.module.scss file as well for those scope styles.
So once weāve got all that stuff, weāll do the same thing we did for our food resources and just copy and paste some of this code from the repository. We can see that this component looks almost identical. Weāve got music resources. This actually has three props instead of two, but we had three fields on this field group, but the styles.component convention is the same. And weāre just doing some slightly different rendering because we have different content.
So from there, weāll also add in our SCSS code and make sure to export that from the component folders index.js and then also import it into our folder over here, and I think I actually madeā letās really quickly rename this and just make sure that all of our naming looks good, so that our index.js and components finds our music resources and all that stuffās great. So now, weāll go ahead and just close out all of these extraneous tabs because we are ready to actually implement those components in our single.js file.
So to do that, weāll just add those two components to our existing import statement, which already automatically recognizes those, and weāll find a good place to do that. And so weāve got this content wrapper component down here. And so right now, weāre passing in the content, but that actually will also optionally accept children. So we can pass in the content but then also pass in some child components directly to that content wrapper, so that it gets displayed in that nice horizontal space that we already have and everything is in alignment.
So thatās what weāll do, and then weāll sort of copy and paste that code in there and reformat it and talk through whatās happening in this particular example. So right inside of that content wrapper component, weāre using those is food and is music Boolean variables to conditionally render, though, corresponding resources components. So if food is true and this post is in the category of food, weāll render that. And if itās music, weāll do the same thing. And there, you can see weāre passing in all of the different props that are needed to render that.
And if we come back out and refresh one of our single.js template pages, we can see that our food resources are rendering just the way that we would expect, and that link would work appropriately if we come back into WP Admin or our home page. And if we find one and the food category or music, we can open up Kinfolk Synth DIY and see what our music resources component looks like, and all that looks great. And if we find one that actually is in both categories, we can see that it actually, at the bottom, renders both of those components just the way that we would expect it to.
OK. So now that weāve got our site the way that we want it, letās move on to talking about how to deploy this site. Now, I have a section of the GitHub repository created for us, and actually, Iāve created an entirely separate deployed branch in the finish branch. So running Git check out finished, weāll get you that. And you can also just deploy that branch directly to Headless WordPress, which is WP Engineās headless WordPress hosting solution.
This gives you both a WordPress installation and a Node.js container, and you can sign up for a free sandbox account by just clicking this button on the Headless WordPress landing page. Take you to a really quick forum, and as you can see, the price is zero. You may still have to put in a credit card that we use just for fraud prevention purposes, but you can have a free account to play around with any of this to test it, out to learn headless to your heartās desire. So Iāll go ahead and open up the WP Engine dashboard to get started deploying this site to Headless WordPress.
The first thing Iām actually going to do is open up my list of sites, and Iām going to open up my production WordPress site. So actually, this ACF headless site that you see right here, where Iām going to open up the WP Admin in a new window, is the parent site for the zip file that you all are using locally. So I made a zip of that using WP export, and thatās actually what Iām going to use for my production deployment.
From there, Iāll click into the Headless WordPress tab and then click Create App. And Iām presented with this option. Iām going to choose Pull From Repo, and then click Continue. And if I havenāt authenticated with GitHub, this is where you would, but since I already am, I can just go ahead and select my repository. So weāll go ahead and select the repository weāre using for this project, click Continue, then Iām going to deploy my app in US Central.
From here, it allows me to choose a branch, and like I said, Iām going to use Finished. Thereās also options if youāre using a monorepo, which weāre not, and Iām going to leave the I Have My WordPress Installation checked, and Search For My ACF Headless Site. Now, here, Iām going to want to, actually, instead of using a .env file, Iām going to want to copy over two environment variables from my files project.
So the first one is next public WordPress URL. Those are the same environment variables that we set in our local project, and Iāll copy the link to my production WordPress install in there. Then Iāll add another environment variable, and this one is going to be for our Faust secret key. So Iāll go ahead and copy that from the Faust Settings menu, and pop that in there and set the key for that to Faust Secret Key.
And once thatās done, I can go ahead and click Create App, and Headless WordPress will begin the process of building and deploying my app. During the Headless WordPress build process, it will run NPM install, and your NPM build command for whatever framework that youāre using. And then once all of that code has been built, it will deploy that Node container for you to our network. And so once all of that spins for just a second, we should get a success message, and then we can click the URL thatās provided for us and actually look at our site live.
So weāll get our success message, and then we can go ahead and open up this URL in another tab. And there we can see that our site looks exactly like it did locally, and itās pulling in all of the correct data, all of the correct images, and even is sucking in all of our ACF content. A number of our posts look really great, and our very performant on Headless WordPressā Iām actually using some really large images here, so if you see some slowness, itās certainly due to my choices.
The Headless WordPress platform is chocked full of features that modern JavaScript developers will appreciate. Sadly, thereās not enough time in this presentation to run through them all in detail.
But Headless WordPress does a really good job of bringing together important details from both the frontend and backend parts of your headless ecosystem into one convenient dashboard, where you can look at individual build logs and the build output, examine deployments, environment variables that you used for a particular build, as well as have access to additional settings or features that you can enable, such as preview environments, where you can create an additional environment for every PR thatās created against your GitHub repo, or create environment webhooks to rebuild particular parts of your app or CDN as you make changes to WordPress.
All of these things become possible with the Headless WordPress platform, and it really lowers the barrier for getting started building with headless WordPress.
Wow. Congratulations like I said that was a lot to cover in 25 minutes. Please feel free to keep on practicing after the presentation, and hit me up if you have any questions getting started with the demo resources. If youāre interested in learning more about headless but need a space to do it, sign up for a free Headless WordPress Sandbox account. In addition to deploying your own code repositories, like weāve done today, you can also get started with some of our premade blueprints, which are one click Project stacks that can help you see what a headless project looks like when itās finished.
We have a basic scaffold example, a portfolio example with some custom post types, and a new ecommerce example that integrates with big commerce through some of their APIs. Our team and developer relations is really passionate about meeting you wherever you are on your journey towards learning headless. So check out our headless developerās roadmap for a step-by-step series of articles and videos that build on one another to help backfill some of the concepts weāve just glossed over today.
The headless ecosystem is young, but itās growing quickly. If you need help on a project, want to talk to some people using headless WordPress in production, or just want to hang out where the magic happens, join the roughly 1,000 other developers in our Discord focused on headless, where we chat, help each other with projects, and help get you moving. Thank you so much for coming to this talk. I look forward to collaborating with you on all of your future headless projects.