{"id":5505,"date":"2023-07-27T12:16:19","date_gmt":"2023-07-27T17:16:19","guid":{"rendered":"https:\/\/wpengine.com\/builders\/?p=5505"},"modified":"2023-07-27T12:25:06","modified_gmt":"2023-07-27T17:25:06","slug":"headless-wordpress-post-previews-in-nuxt-3","status":"publish","type":"post","link":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/","title":{"rendered":"Headless WordPress Post Previews in Nuxt 3"},"content":{"rendered":"\n<p>In this short article, you&#8217;ll learn how to implement post and content previews in a Nuxt 3 application using the Faust.js WordPress plugin. All of the code for this article is located on the <code>feature\/previews<\/code> branch of the <a href=\"https:\/\/github.com\/JEverhart383\/nuxt-wordpress-starter\/tree\/feature\/previews\">Nuxt WordPress<\/a> starter I created for a more <a href=\"https:\/\/wpengine.com\/builders\/headless-wordpress-nuxt-3-vue-3\/\">foundational tutorial on Nuxt 3 and headless WordPress<\/a>. If you&#8217;re new to Nuxt 3 and headless WordPress, I&#8217;d recommend starting with that tutorial.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What is Faust.js and What Does It Do?<\/h2>\n\n\n\n<p>The <a href=\"https:\/\/faustjs.org\/\">Faust.js framework<\/a> is a JavaScript framework that makes building headless WordPress simple and easy. It&#8217;s based on Next.js but contains two parts, a collection of NPM packages that power the JavaScript frontend and a WordPress plugin that provides a number of helpful backend features.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-text-color has-background has-link-color wp-elements-1c64e8656e2adf26d6fee6632ed30e85 has-global-padding is-layout-constrained wp-container-core-group-is-layout-c825ac28 wp-block-group-is-layout-constrained\" style=\"border-radius:12px;background-color:#1a8bec;margin-bottom:var(--wp--preset--spacing--30);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--30);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--30)\">\n<div class=\"wp-block-group alignwide is-content-justification-left is-layout-flex wp-container-core-group-is-layout-8b72b8c1 wp-block-group-is-layout-flex\" style=\"padding-top:0;padding-right:0;padding-bottom:0;padding-left:0\">\n<div class=\"wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-b97f5b79 wp-block-group-is-layout-flex\">\n<div class=\"wp-block-outermost-icon-block\"><div class=\"icon-container\" style=\"width:52px\"><svg fill=\"none\" viewBox=\"0 0 51 52\" aria-label=\"Frost logo\"><path fill=\"#0076DC\" d=\"M51 26C51 11.917 39.583.5 25.5.5S0 11.917 0 26s11.417 25.5 25.5 25.5S51 40.083 51 26Z\"><\/path><path fill=\"#fff\" d=\"M18.693 43a8.4 8.4 0 0 1-3.701-.944 4.91 4.91 0 0 1-2.044-2.11 2.946 2.946 0 0 1 1.095-3.778 3.386 3.386 0 0 1 4.287.68c.36.423.691.87.991 1.336l.047.071c1.228-2.833 1.785-5.765 2.361-8.838.27-1.417.553-2.875.902-4.325l-2.658-.207a.733.733 0 0 1-.675-.723 1.916 1.916 0 0 1 .52-1.487 1.889 1.889 0 0 1 1.496-.387l.76.07a1.337 1.337 0 0 0 1.416-1.005c.118-.472.232-.944.35-1.45.401-1.99.998-3.937 1.78-5.811 2.001-4.382 5.622-6.01 10.202-4.594.9.28 1.714.787 2.36 1.473a3.41 3.41 0 0 1 .907 2.497 2.973 2.973 0 0 1-1.02 2.082 3.157 3.157 0 0 1-2.318.709 3.485 3.485 0 0 1-2.389-1.214 8.862 8.862 0 0 1-1.067-1.775c-.113-.222-.221-.472-.34-.666-.042-.075-.084-.15-.122-.23l-.302.032-2.116 9.797 2.673.17a.723.723 0 0 1 .67.604 1.78 1.78 0 0 1-.316 1.591 1.716 1.716 0 0 1-1.516.401l-.359-.037-.472-.047c-1.005-.076-1.36.24-1.59 1.416-.1.51-.2 1.02-.293 1.53-.444 2.303-.898 4.683-1.535 6.982a16.486 16.486 0 0 1-2.2 4.835C22.97 41.862 20.955 43 18.693 43Zm-2.946-6.383a2.262 2.262 0 0 0-1.204.34 2.002 2.002 0 0 0-.755 2.568 3.97 3.97 0 0 0 1.62 1.695c3.304 1.624 6.26.878 8.295-2.11a15.411 15.411 0 0 0 2.072-4.552c.628-2.266 1.082-4.627 1.516-6.912l.297-1.53c.227-1.156.709-2.317 2.587-2.176l.51.052.35.038c.368.038.632 0 .736-.113a.854.854 0 0 0 .095-.614l-2.743-.175a.732.732 0 0 1-.652-.878l2.285-10.703 1.653-.189.141.312c.076.165.152.325.236.472.085.146.241.472.36.694.252.564.57 1.098.944 1.59a2.55 2.55 0 0 0 1.742.879 2.229 2.229 0 0 0 1.624-.472 2.031 2.031 0 0 0 .698-1.417 2.484 2.484 0 0 0-.67-1.808 4.557 4.557 0 0 0-1.94-1.204c-4.127-1.28-7.262.133-9.065 4.084a30.694 30.694 0 0 0-1.723 5.637c-.114.473-.232.978-.35 1.464a2.266 2.266 0 0 1-2.412 1.714l-.76-.07a1.034 1.034 0 0 0-.775.145.888.888 0 0 0-.208.581l2.734.198a.728.728 0 0 1 .652.898c-.373 1.51-.666 3.045-.945 4.532-.6 3.12-1.213 6.35-2.606 9.442a.714.714 0 0 1-1.232.114l-.288-.425c-.277-.43-.583-.84-.916-1.228a2.536 2.536 0 0 0-1.903-.873Z\"><\/path><\/svg><\/div><\/div>\n\n\n\n<p class=\"has-x-large-font-size\" style=\"font-style:normal;font-weight:800;letter-spacing:-1px\">Faust.js<\/p>\n<\/div>\n\n\n\n<p class=\"has-small-font-size wp-container-content-c86e52ed\" style=\"line-height:1.5\">The JavaScript framework<br>specifically for WordPress.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-content-justification-right is-nowrap is-layout-flex wp-container-core-buttons-is-layout-b315634e wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button is-style-outline-base\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/wpeng.in\/faust-builders\/\" style=\"border-radius:99px;padding-top:10px;padding-right:24px;padding-bottom:10px;padding-left:24px\" target=\"_blank\" rel=\"noreferrer noopener\">Download \u2192<\/a><\/div>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<p>Even though Faust.js is tightly coupled with React and Next.js, developers can use the plugin and reimplement patterns from Faust.js in other frameworks. That is essentially what we&#8217;ll do in this tutorial by copying Faust&#8217;s method of content previews in a Nuxt 3 app.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Installing and Configuring the Faust WordPress Plugin<\/h2>\n\n\n\n<p>First, you will need to install and activate the <a href=\"https:\/\/wordpress.org\/plugins\/faustwp\/\">Faust.js WordPress plugin<\/a> from the plugin repository. This creates a menu option in <code>Settings &gt; Faust<\/code> that gives you access to the Faust settings menu.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"651\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-9.53.20-AM-1024x651.png\" alt=\"The settings screen for the FaustWP plugin in WP admin\" class=\"wp-image-5506\" srcset=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-9.53.20-AM-1024x651.png 1024w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-9.53.20-AM-300x191.png 300w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-9.53.20-AM-768x488.png 768w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-9.53.20-AM.png 1476w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>The <a href=\"https:\/\/faustjs.org\/reference\/wordpress-plugin-settings\">official documentation<\/a> outlines what all of these settings do, but for the purposes of this tutorial, you will need to set the <code>Front-end site URL<\/code> to <code>http:\/\/localhost:3000<\/code> and take note of the <code>Secret Key<\/code> field.<\/p>\n\n\n\n<p>The Faust WordPress plugin helps developers with some common tasks relevant to headless CMS usage by providing options for disabling theme settings and redirecting traffic to your front-end URL. But it also rewrites links inside of the WP Admin area to point to your decoupled front end. <\/p>\n\n\n\n<p>Once we have these basic settings configured, you can start implementing the preview features inside of your Nuxt 3 app.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Understanding the Faust.js Auth Flow<\/h2>\n\n\n\n<p>Since preview data is privileged in WordPress, meaning public users can&#8217;t access this data, we need to implement some mechanism to authenticate our Nuxt.js queries with WordPress to ensure that there is a logged-in user and that the user has permission to access the requested data.<\/p>\n\n\n\n<p>A typical authentication flow in Faust happens in five steps.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>TL;DR<\/strong>: Authentication in Faust.js can happen in five main steps:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>User initiates request to authenticate data.<\/li>\n\n\n\n<li>Faust.js facilitates the request for an authorization code from the FaustWP plugin. This code is a short-lived token used to request a refresh and access token.<\/li>\n\n\n\n<li>Faust.js facilitates a refresh and access token request from the FaustWP plugin using the authorization code.<\/li>\n\n\n\n<li>Faust.js stores the refresh token in a secure, HTTP-only cookie. The token refresh requests a new access token when the current one expires<\/li>\n\n\n\n<li>Faust.js stores the access token in memory that you can use in subsequent authenticated requests.<\/li>\n<\/ol>\n<cite>Faust.js Docs, <a href=\"https:\/\/faustjs.org\/guide\/how-to-handle-authentication\">How to Handle Authentication<\/a><\/cite><\/blockquote>\n\n\n\n<p>If you look at how the Faust.js docs describe the authentication flow, it looks a lot like <a href=\"https:\/\/www.digitalocean.com\/community\/tutorials\/an-introduction-to-oauth-2\">an OAuth2-type flow<\/a>. In the following steps, you&#8217;ll see how to implement these steps in Nuxt.js to request preview data. For this example, you will implement the Redirect Strategy outlined in the Faust.js docs, but with a few meaningful differences.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Implementing Faust Auth in Nuxt.js<\/h2>\n\n\n\n<p>As you get started with the following steps, it&#8217;s worth noting that since the Faust.js plugin is really just the provider of an authentication strategy (similar to OAuth), it&#8217;s up to you as the developer to figure out how best that strategy fits into your use case. The following steps outline how to authenticate previews initiated from the WP Admin area, but it&#8217;s possible to use this same auth strategy in more complex ways.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Storing the Faust Secret Key<\/h3>\n\n\n\n<p>The first thing you need to do is provide a copy of the <code>Faust Secret Key<\/code> to your application. To do this, create an <code>.env<\/code> file and add an entry for <code>FAUST_SECRET_KEY<\/code>. The value of this variable should be taken from your Faust plugin screen in WordPress. To make this environment available to Nuxt only on the server, you will need to add a <code>private<\/code> runtime config variable in your <code>nuxt.config.ts<\/code> file.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ https:\/\/nuxt.com\/docs\/api\/configuration\/nuxt-config<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> defineNuxtConfig({\n<\/span><\/span><span class='shcb-loc'><span>    modules: &#91;\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-string\">'@nuxt\/devtools'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-string\">'@nuxtjs\/tailwindcss'<\/span> \n<\/span><\/span><span class='shcb-loc'><span>    ],\n<\/span><\/span><span class='shcb-loc'><span>    runtimeConfig: {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">public<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>            wordpressUrl: <span class=\"hljs-string\">'https:\/\/acfheadless.wpengine.com'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>            frontendSiteUrl: <span class=\"hljs-string\">'http:\/\/localhost:3000'<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        },\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">private<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>          FAUST_SECRET_KEY: process.env.FAUST_SECRET_KEY\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>})\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Since the value of <code>FAUST_SECRET_KEY<\/code> is used to grant a user access to your site&#8217;s API, it&#8217;s very important that this value remains a secret. Nuxt offers the ability to create private runtime config values that are only exposed on the server, but I also considered creating my own API endpoints that apply the secret key to certain requests. That is actually more in line with how the Faust Next.js application functions by using an API handler.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Respond to Preview Requests in Nuxt<\/h3>\n\n\n\n<p>After your application has access to the <code>FAUST_SECRET_KEY<\/code> variable, you&#8217;ll need to configure your Nuxt site to respond to the preview routes that Faust.js generates as preview links.<\/p>\n\n\n\n<p>With your <code>Front-end site URL<\/code> value set, Faust will rewrite all of the preview links to a path that looks like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>http:<span class=\"hljs-comment\">\/\/localhost:3000\/?p=225&amp;preview=true&amp;previewPathname=%2F%3Fp%3D225&amp;typeName=Post<\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Basically, you&#8217;ll need to listen for this path and react to it, kicking off the authentication flow with the Faust plugin. To do this, you can create a small piece of <a href=\"https:\/\/nuxt.com\/docs\/guide\/directory-structure\/middleware\">global middleware<\/a> to react to the <code>preview=true<\/code> query param.<\/p>\n\n\n\n<p>All Nuxt middleware needs to be in the <code>middleware<\/code> directory, and you can create a file called <code>preview.global.ts<\/code> to run the middleware function globally. Inside that file you can create one short function that looks like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span>  defineNuxtRouteMiddleware(<span class=\"hljs-keyword\">async<\/span> (to, <span class=\"hljs-keyword\">from<\/span>) =&gt; {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (to.query.preview === <span class=\"hljs-string\">'true'<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">const<\/span> config = useRuntimeConfig();\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">const<\/span> previewId = to.query.p;\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">return<\/span> navigateTo(<span class=\"hljs-string\">`<span class=\"hljs-subst\">${config.<span class=\"hljs-keyword\">public<\/span>.wordpressUrl}<\/span>\/generate?redirect_uri=<span class=\"hljs-subst\">${config.<span class=\"hljs-keyword\">public<\/span>.frontendSiteUrl}<\/span>\/preview?preview_id=<span class=\"hljs-subst\">${previewId}<\/span>`<\/span>, {external: <span class=\"hljs-literal\">true<\/span>})\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  })\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This function listens every time you load or change a route, but only runs code if the route contains the <code>preview=true<\/code> query parameter. Inside the conditional, the function loads the <code>config<\/code> values and extracts the <code>previewId<\/code> of the content item in WordPress.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Getting the Authorization Code<\/h3>\n\n\n\n<p>Using the <code>navigateTo<\/code> function, you kick off the first leg of the authentication flow by redirecting to the <code>\/generate<\/code> endpoint that the Faust plugin creates on your WordPress site. This endpoint also requires a <code>redirect_url<\/code> query param so that Faust can send the user back to the Nuxt application after the flow. In this example, that part looks like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>redirect_uri=${config.<span class=\"hljs-keyword\">public<\/span>.frontendSiteUrl}\/preview?preview_id=${previewId}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This endpoint checks if there is a logged-in user in WordPress and passes you to the WordPress login screen if you&#8217;re not logged in. In either case, after successfully authenticating, you should be redirected back to <code>http:\/\/localhost:3000\/preview<\/code> with two query parameters in the URL. The <code>code<\/code> query param contains an authorization code you&#8217;ll exchange for an access token in the next step, and <code>previewId<\/code> contains the database id for the content item that genereated the preview request. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Generating an Access Token<\/h3>\n\n\n\n<p>In the next step of this flow, the <code>preview.vue<\/code> template will do most of the heavy lifting. If your auth flow has been successful, you should wind up at this route with both <code>code<\/code> and <code>preview_id<\/code> query parameters in the URL. The entire script block for this route looks like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>&lt;script setup&gt;\n<\/span><\/span><span class='shcb-loc'><span> <span class=\"hljs-keyword\">import<\/span> {getTokensFromCode, getTokensFromRefreshToken} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/faust'<\/span>;\n<\/span><\/span><span class='shcb-loc'><span> <span class=\"hljs-keyword\">const<\/span> route = useRoute();\n<\/span><\/span><span class='shcb-loc'><span> <span class=\"hljs-keyword\">const<\/span> config = useRuntimeConfig();\n<\/span><\/span><span class='shcb-loc'><span> <span class=\"hljs-keyword\">const<\/span> {code, preview_id: previewId} = route.query;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span> <span class=\"hljs-keyword\">let<\/span> tokens;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span> <span class=\"hljs-keyword\">const<\/span> cookie = useCookie(<span class=\"hljs-string\">`<span class=\"hljs-subst\">${config.<span class=\"hljs-keyword\">public<\/span>.frontendSiteUrl}<\/span>-rt`<\/span>, {httpOnly: <span class=\"hljs-literal\">true<\/span>, maxAge: <span class=\"hljs-number\">300<\/span>});\n<\/span><\/span><span class='shcb-loc'><span> <span class=\"hljs-keyword\">if<\/span> (cookie.value){\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> refreshToken = cookie.value;\n<\/span><\/span><span class='shcb-loc'><span>    tokens = <span class=\"hljs-keyword\">await<\/span> getTokensFromRefreshToken(refreshToken);\n<\/span><\/span><span class='shcb-loc'><span>    cookie.value = tokens.refreshToken\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span> } <span class=\"hljs-keyword\">else<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    tokens = <span class=\"hljs-keyword\">await<\/span> getTokensFromCode(code);\n<\/span><\/span><span class='shcb-loc'><span>    cookie.value = tokens.refreshToken;\n<\/span><\/span><span class='shcb-loc'><span> }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/  use post here so we don't cache any null responses based on invalid credential<\/span>\n<\/span><\/span><span class='shcb-loc'><span> <span class=\"hljs-keyword\">const<\/span> {data, pending, refresh, error} = <span class=\"hljs-keyword\">await<\/span> useFetch(<span class=\"hljs-string\">`<span class=\"hljs-subst\">${config.<span class=\"hljs-keyword\">public<\/span>.wordpressUrl}<\/span>\/graphql`<\/span>, {\n<\/span><\/span><span class='shcb-loc'><span>    method: <span class=\"hljs-string\">'post'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>    body: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n<\/span><\/span><span class='shcb-loc'><span>        query: <span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        query DraftQuery($id: ID!) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">            post(id: $id, idType: DATABASE_ID, asPreview: true) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">                id<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">                title<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">                content<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">                date<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">            }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        `<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        variables: {\n<\/span><\/span><span class='shcb-loc'><span>            id: previewId\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>    }),\n<\/span><\/span><span class='shcb-loc'><span>    headers: {\n<\/span><\/span><span class='shcb-loc'><span>        Authorization: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${tokens.accessToken}<\/span>`<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    },\n<\/span><\/span><span class='shcb-loc'><span>    transform(data){\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">return<\/span> data.data.post\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span> })\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span> \n<\/span><\/span><span class='shcb-loc'><span> &lt;<span class=\"hljs-regexp\">\/script&gt;<\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the first few lines, you import a few utilities from the <code>faust<\/code> directory and load runtime config values and the Nuxt router. You can extract the values for <code>code<\/code> and <code>preview_id<\/code> from the current route&#8217;s query params, and then declare a variable called <code>tokens<\/code> to store your access and refresh tokens.<\/p>\n\n\n\n<p>The next section of code is where those tokens are actually generated. Using the <a href=\"https:\/\/nuxt.com\/docs\/api\/composables\/use-cookie#usecookie\">useCookie composable<\/a> from Nuxt, you can create a cookie to store the value of  <code>refreshToken<\/code> as a way of extending your authenticated session. If the cookie already has a value, then you can use the value of the refresh token to request a new access token, overwriting the value of the refresh token stored in the cookie with a new value. <\/p>\n\n\n\n<p>If this cookie hasn&#8217;t been set yet, then you use the <code>code<\/code> passed from the previous step to request an access token. Using the newly generated <code>refreshToken<\/code> value, you can initialize the cookie.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Fetching the Access Token in Detail<\/h3>\n\n\n\n<p>Now that you have an understanding of how the preview route handles the exchange of a <code>code<\/code> or <code>refreshToken<\/code> for an access token, let&#8217;s explode that interaction and examine the <code>getTokenFromCode<\/code> and <code>getTokenFromRefreshToken<\/code> utilities. <\/p>\n\n\n\n<p>The code in <code>faust\/index.ts<\/code> looks like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">type<\/span> Tokens = {   \n<\/span><\/span><span class='shcb-loc'><span>    accessToken: <span class=\"hljs-built_in\">string<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    refreshToken: <span class=\"hljs-built_in\">string<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    accessTokenExpiration: <span class=\"hljs-built_in\">number<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    refreshTokenExpiration: <span class=\"hljs-built_in\">number<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getTokensFromCode<\/span>(<span class=\"hljs-params\">code:<span class=\"hljs-built_in\">string<\/span><\/span>): <span class=\"hljs-title\">Promise<\/span>&lt;<span class=\"hljs-title\">Tokens<\/span>&gt;<\/span>{\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> config = useRuntimeConfig();\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> apiClientSecret = config.private.FAUST_SECRET_KEY;\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (!apiClientSecret) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-string\">'The apiClientSecret must be specified to use the auth middleware'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        );\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>  \n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">let<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">`https:\/\/acfheadless.wpengine.com\/?rest_route=\/faustwp\/v1\/authorize`<\/span>, {\n<\/span><\/span><span class='shcb-loc'><span>        headers: {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-string\">'Content-Type'<\/span>: <span class=\"hljs-string\">'application\/json'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-string\">'x-faustwp-secret'<\/span>: apiClientSecret,\n<\/span><\/span><span class='shcb-loc'><span>        },\n<\/span><\/span><span class='shcb-loc'><span>        method: <span class=\"hljs-string\">'POST'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        body: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n<\/span><\/span><span class='shcb-loc'><span>          code\n<\/span><\/span><span class='shcb-loc'><span>        }),\n<\/span><\/span><span class='shcb-loc'><span>      });\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> tokens: Tokens = <span class=\"hljs-keyword\">await<\/span> response.json();\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> tokens;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getTokensFromRefreshToken<\/span>(<span class=\"hljs-params\">refreshToken:<span class=\"hljs-built_in\">string<\/span><\/span>): <span class=\"hljs-title\">Promise<\/span>&lt;<span class=\"hljs-title\">Tokens<\/span>&gt;<\/span>{\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> config = useRuntimeConfig();\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> apiClientSecret = config.private.FAUST_SECRET_KEY;\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (!apiClientSecret) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-string\">'The apiClientSecret must be specified to use the auth middleware'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        );\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>  \n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">let<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">`https:\/\/acfheadless.wpengine.com\/?rest_route=\/faustwp\/v1\/authorize`<\/span>, {\n<\/span><\/span><span class='shcb-loc'><span>        headers: {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-string\">'Content-Type'<\/span>: <span class=\"hljs-string\">'application\/json'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-string\">'x-faustwp-secret'<\/span>: apiClientSecret,\n<\/span><\/span><span class='shcb-loc'><span>        },\n<\/span><\/span><span class='shcb-loc'><span>        method: <span class=\"hljs-string\">'POST'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        body: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n<\/span><\/span><span class='shcb-loc'><span>            refreshToken\n<\/span><\/span><span class='shcb-loc'><span>        }),\n<\/span><\/span><span class='shcb-loc'><span>      });\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> tokens: Tokens = <span class=\"hljs-keyword\">await<\/span> response.json();\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> tokens;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Both of these functions are almost identical in that they generate POST requests to the <code>\/?rest_route=\/faustwp\/v1\/authorize<\/code> endpoint on your WordPress site that Faust creates. This endpoint is what ultimately grants your application access tokens to request privileged data. <\/p>\n\n\n\n<p>In addition to sending the <code>code<\/code> or <code>refreshToken<\/code> in the body of your request, you need to pass the <code>FAUST_SECRET_KEY<\/code> in a &#8216;<code>x-faustwp-secret'<\/code> HTTP header. On the backend, Faust will use both of those values to determine whether the user and application should be granted tokens. The response from this endpoint is shaped like the <code>Tokens<\/code> type contained at the top of this file.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Requesting Preview Data from WPGraphQL<\/h3>\n\n\n\n<p>Now that you have an actual access token, you can use that token to make an authenticated request to WPGraphQL:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/  use post here so we don't cache any null responses based on invalid credential<\/span>\n<\/span><\/span><span class='shcb-loc'><span> <span class=\"hljs-keyword\">const<\/span> {data, pending, refresh, error} = <span class=\"hljs-keyword\">await<\/span> useFetch(<span class=\"hljs-string\">`<span class=\"hljs-subst\">${config.<span class=\"hljs-keyword\">public<\/span>.wordpressUrl}<\/span>\/graphql`<\/span>, {\n<\/span><\/span><span class='shcb-loc'><span>    method: <span class=\"hljs-string\">'post'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>    body: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n<\/span><\/span><span class='shcb-loc'><span>        query: <span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        query DraftQuery($id: ID!) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">            post(id: $id, idType: DATABASE_ID, asPreview: true) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">                id<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">                title<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">                content<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">                date<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">            }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        `<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        variables: {\n<\/span><\/span><span class='shcb-loc'><span>            id: previewId\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>    }),\n<\/span><\/span><span class='shcb-loc'><span>    headers: {\n<\/span><\/span><span class='shcb-loc'><span>        Authorization: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${tokens.accessToken}<\/span>`<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    },\n<\/span><\/span><span class='shcb-loc'><span>    transform(data){\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">return<\/span> data.data.post\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span> })\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the example above, you can see that this query uses the <code>asPreview<\/code> argument set to <code>true<\/code> when fetching data for this post using the database ID. However, to authenticate this request, you need to add an <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Authorization\">Authorization header<\/a> to your HTTP request using the Bearer authentication scheme: <code>Bearer YourSecureTokenHere<\/code>. This auth scheme is used primarily with token-based auth mechanisms. <\/p>\n\n\n\n<p>If you send this same request without the <code>Authorization<\/code> header, the value of <code>post<\/code> will be <code>null<\/code>instead of throwing an unauthorized error. This is important to note because it is a behavior of GraphQL. Since our query documents can contain multiple queries across resources, some of which may be public and some private, WPGraphQL does not reject queries outright if the endpoint is public. Instead, it will not resolve data for which the current user does not have permission. <\/p>\n\n\n\n<p>In this case, without the <code>Authorization<\/code> header to vouch for your identity, WPGraphQL treats your query like a public user and doesn&#8217;t resolve data for the preview since public users can&#8217;t view preview data. <\/p>\n\n\n\n<p>If everything works as it should, your post&#8217;s preview data should render inside of the <code>preview.vue<\/code> template.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"586\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-12.44.31-PM-1024x586.png\" alt=\"A preview post called 'new test preview'\" class=\"wp-image-5515\" srcset=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-12.44.31-PM-1024x586.png 1024w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-12.44.31-PM-300x172.png 300w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-12.44.31-PM-768x439.png 768w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-12.44.31-PM.png 1351w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>Although this post dives into the weeds of authentication, it was made to intentionally be as simple as possible. There are many use cases this short tutorial does not address, like previewing different templates or layouts based on content type, but hopefully, you can use this auth strategy to satisfy any more complex needs you have in your own work. <\/p>\n\n\n\n<p>Even if you use other frameworks like Nuxt.js, the Faust Next.js codebase is a great place to learn about patterns that can be applied to other frameworks.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-text-color has-background has-link-color wp-elements-1c64e8656e2adf26d6fee6632ed30e85 has-global-padding is-layout-constrained wp-container-core-group-is-layout-c825ac28 wp-block-group-is-layout-constrained\" style=\"border-radius:12px;background-color:#1a8bec;margin-bottom:var(--wp--preset--spacing--30);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--30);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--30)\">\n<div class=\"wp-block-group alignwide is-content-justification-left is-layout-flex wp-container-core-group-is-layout-8b72b8c1 wp-block-group-is-layout-flex\" style=\"padding-top:0;padding-right:0;padding-bottom:0;padding-left:0\">\n<div class=\"wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-b97f5b79 wp-block-group-is-layout-flex\">\n<div class=\"wp-block-outermost-icon-block\"><div class=\"icon-container\" style=\"width:52px\"><svg fill=\"none\" viewBox=\"0 0 51 52\" aria-label=\"Frost logo\"><path fill=\"#0076DC\" d=\"M51 26C51 11.917 39.583.5 25.5.5S0 11.917 0 26s11.417 25.5 25.5 25.5S51 40.083 51 26Z\"><\/path><path fill=\"#fff\" d=\"M18.693 43a8.4 8.4 0 0 1-3.701-.944 4.91 4.91 0 0 1-2.044-2.11 2.946 2.946 0 0 1 1.095-3.778 3.386 3.386 0 0 1 4.287.68c.36.423.691.87.991 1.336l.047.071c1.228-2.833 1.785-5.765 2.361-8.838.27-1.417.553-2.875.902-4.325l-2.658-.207a.733.733 0 0 1-.675-.723 1.916 1.916 0 0 1 .52-1.487 1.889 1.889 0 0 1 1.496-.387l.76.07a1.337 1.337 0 0 0 1.416-1.005c.118-.472.232-.944.35-1.45.401-1.99.998-3.937 1.78-5.811 2.001-4.382 5.622-6.01 10.202-4.594.9.28 1.714.787 2.36 1.473a3.41 3.41 0 0 1 .907 2.497 2.973 2.973 0 0 1-1.02 2.082 3.157 3.157 0 0 1-2.318.709 3.485 3.485 0 0 1-2.389-1.214 8.862 8.862 0 0 1-1.067-1.775c-.113-.222-.221-.472-.34-.666-.042-.075-.084-.15-.122-.23l-.302.032-2.116 9.797 2.673.17a.723.723 0 0 1 .67.604 1.78 1.78 0 0 1-.316 1.591 1.716 1.716 0 0 1-1.516.401l-.359-.037-.472-.047c-1.005-.076-1.36.24-1.59 1.416-.1.51-.2 1.02-.293 1.53-.444 2.303-.898 4.683-1.535 6.982a16.486 16.486 0 0 1-2.2 4.835C22.97 41.862 20.955 43 18.693 43Zm-2.946-6.383a2.262 2.262 0 0 0-1.204.34 2.002 2.002 0 0 0-.755 2.568 3.97 3.97 0 0 0 1.62 1.695c3.304 1.624 6.26.878 8.295-2.11a15.411 15.411 0 0 0 2.072-4.552c.628-2.266 1.082-4.627 1.516-6.912l.297-1.53c.227-1.156.709-2.317 2.587-2.176l.51.052.35.038c.368.038.632 0 .736-.113a.854.854 0 0 0 .095-.614l-2.743-.175a.732.732 0 0 1-.652-.878l2.285-10.703 1.653-.189.141.312c.076.165.152.325.236.472.085.146.241.472.36.694.252.564.57 1.098.944 1.59a2.55 2.55 0 0 0 1.742.879 2.229 2.229 0 0 0 1.624-.472 2.031 2.031 0 0 0 .698-1.417 2.484 2.484 0 0 0-.67-1.808 4.557 4.557 0 0 0-1.94-1.204c-4.127-1.28-7.262.133-9.065 4.084a30.694 30.694 0 0 0-1.723 5.637c-.114.473-.232.978-.35 1.464a2.266 2.266 0 0 1-2.412 1.714l-.76-.07a1.034 1.034 0 0 0-.775.145.888.888 0 0 0-.208.581l2.734.198a.728.728 0 0 1 .652.898c-.373 1.51-.666 3.045-.945 4.532-.6 3.12-1.213 6.35-2.606 9.442a.714.714 0 0 1-1.232.114l-.288-.425c-.277-.43-.583-.84-.916-1.228a2.536 2.536 0 0 0-1.903-.873Z\"><\/path><\/svg><\/div><\/div>\n\n\n\n<p class=\"has-x-large-font-size\" style=\"font-style:normal;font-weight:800;letter-spacing:-1px\">Faust.js<\/p>\n<\/div>\n\n\n\n<p class=\"has-small-font-size wp-container-content-c86e52ed\" style=\"line-height:1.5\">The JavaScript framework<br>specifically for WordPress.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-content-justification-right is-nowrap is-layout-flex wp-container-core-buttons-is-layout-b315634e wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button is-style-outline-base\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/wpeng.in\/faust-builders\/\" style=\"border-radius:99px;padding-top:10px;padding-right:24px;padding-bottom:10px;padding-left:24px\" target=\"_blank\" rel=\"noreferrer noopener\">Download \u2192<\/a><\/div>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<p>If you are looking for a place to host your next headless WordPress project, be sure to get a free sandbox account on our Atlas Platform. The platform combines Node.js hosting, WordPress hosting, and a CDN layer into one convenient dashboard to manage your headless sites.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-heliotrope-background-color has-text-color has-background has-link-color wp-elements-60580c77f35127ab4c58efd3ae854863 has-global-padding is-layout-constrained wp-container-core-group-is-layout-9b866ece wp-block-group-is-layout-constrained\" style=\"border-radius:12px;margin-bottom:var(--wp--preset--spacing--30);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--30);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--30)\">\n<div class=\"wp-block-group alignwide is-content-justification-left is-layout-flex wp-container-core-group-is-layout-4bb02320 wp-block-group-is-layout-flex\" style=\"padding-top:0;padding-right:0;padding-bottom:0;padding-left:0\">\n<p style=\"font-size:26px;font-style:normal;font-weight:700;letter-spacing:-1px;line-height:1\">Headless<br>Platform<\/p>\n\n\n\n<p class=\"has-small-font-size wp-container-content-9cfa9a5a\" style=\"line-height:1.5\">The all-in-one platform for <br>radically fast headless sites.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-content-justification-right is-nowrap is-layout-flex wp-container-core-buttons-is-layout-b315634e wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button is-style-outline-base\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/wpeng.in\/headless-platform\/\" style=\"border-radius:99px;padding-top:10px;padding-right:24px;padding-bottom:10px;padding-left:24px\" target=\"_blank\" rel=\"noreferrer noopener\">Try for free \u2192<\/a><\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>In this short article, you&#8217;ll learn how to implement post and content previews in a Nuxt 3 application using the Faust.js WordPress plugin. All of the code for this article [&hellip;]<\/p>\n","protected":false},"author":19,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_EventAllDay":false,"_EventTimezone":"","_EventStartDate":"","_EventEndDate":"","_EventStartDateUTC":"","_EventEndDateUTC":"","_EventShowMap":false,"_EventShowMapLink":false,"_EventURL":"","_EventCost":"","_EventCostDescription":"","_EventCurrencySymbol":"","_EventCurrencyCode":"","_EventCurrencyPosition":"","_EventDateTimeSeparator":"","_EventTimeRangeSeparator":"","_EventOrganizerID":[],"_EventVenueID":[],"_OrganizerEmail":"","_OrganizerPhone":"","_OrganizerWebsite":"","_VenueAddress":"","_VenueCity":"","_VenueCountry":"","_VenueProvince":"","_VenueState":"","_VenueZip":"","_VenuePhone":"","_VenueURL":"","_VenueStateProvince":"","_VenueLat":"","_VenueLng":"","_VenueShowMap":false,"_VenueShowMapLink":false,"footnotes":""},"categories":[23],"tags":[25,27,26],"class_list":["post-5505","post","type-post","status-publish","format-standard","hentry","category-headless","tag-faust-js","tag-nuxt-js","tag-wpgraphql"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Headless WordPress Post Previews in Nuxt 3 - Builders<\/title>\n<meta name=\"description\" content=\"In this post, you&#039;ll learn how to implement post previews from headless WordPress in a Nuxt 3 application.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Headless WordPress Post Previews in Nuxt 3\" \/>\n<meta property=\"og:description\" content=\"In this post, you&#039;ll learn how to implement post previews from headless WordPress in a Nuxt 3 application.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/\" \/>\n<meta property=\"og:site_name\" content=\"Builders\" \/>\n<meta property=\"article:published_time\" content=\"2023-07-27T17:16:19+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-07-27T17:25:06+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/headless-wordpress-post-previews-nuxt3.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Jeff Everhart\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:title\" content=\"Headless WordPress Post Previews in Nuxt 3\" \/>\n<meta name=\"twitter:description\" content=\"In this post, you&#039;ll learn how to implement post previews from headless WordPress in a Nuxt 3 application.\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/headless-wordpress-post-previews-nuxt3.jpg\" \/>\n<meta name=\"twitter:creator\" content=\"@wpebuilders\" \/>\n<meta name=\"twitter:site\" content=\"@wpebuilders\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jeff Everhart\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"8 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-post-previews-in-nuxt-3\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-post-previews-in-nuxt-3\\\/\"},\"author\":{\"name\":\"Jeff Everhart\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/person\\\/b5a7f380738d25f57b00a5aaacf3db52\"},\"headline\":\"Headless WordPress Post Previews in Nuxt 3\",\"datePublished\":\"2023-07-27T17:16:19+00:00\",\"dateModified\":\"2023-07-27T17:25:06+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-post-previews-in-nuxt-3\\\/\"},\"wordCount\":1782,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-post-previews-in-nuxt-3\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2023\\\/07\\\/Screenshot-2023-07-27-at-9.53.20-AM-1024x651.png\",\"keywords\":[\"Faust.js\",\"Nuxt.js\",\"WPGraphQL\"],\"articleSection\":[\"Headless\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-post-previews-in-nuxt-3\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-post-previews-in-nuxt-3\\\/\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-post-previews-in-nuxt-3\\\/\",\"name\":\"Headless WordPress Post Previews in Nuxt 3 - Builders\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-post-previews-in-nuxt-3\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-post-previews-in-nuxt-3\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2023\\\/07\\\/Screenshot-2023-07-27-at-9.53.20-AM-1024x651.png\",\"datePublished\":\"2023-07-27T17:16:19+00:00\",\"dateModified\":\"2023-07-27T17:25:06+00:00\",\"description\":\"In this post, you'll learn how to implement post previews from headless WordPress in a Nuxt 3 application.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-post-previews-in-nuxt-3\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-post-previews-in-nuxt-3\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-post-previews-in-nuxt-3\\\/#primaryimage\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2023\\\/07\\\/Screenshot-2023-07-27-at-9.53.20-AM.png\",\"contentUrl\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2023\\\/07\\\/Screenshot-2023-07-27-at-9.53.20-AM.png\",\"width\":1476,\"height\":938},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-post-previews-in-nuxt-3\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Headless WordPress Post Previews in Nuxt 3\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#website\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\",\"name\":\"Builders\",\"description\":\"Reimagining the way we build with WordPress.\",\"publisher\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\",\"name\":\"WP Engine\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/WP-Engine-Horizontal@2x.png\",\"contentUrl\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/WP-Engine-Horizontal@2x.png\",\"width\":348,\"height\":68,\"caption\":\"WP Engine\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/x.com\\\/wpebuilders\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UCh1WuL54XFb9ZI6m6goFv1g\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/person\\\/b5a7f380738d25f57b00a5aaacf3db52\",\"name\":\"Jeff Everhart\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/237d84b2a63acb8c427ba3246f9c5fc1b436aac578a5df421927c23b4fde45f5?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/237d84b2a63acb8c427ba3246f9c5fc1b436aac578a5df421927c23b4fde45f5?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/237d84b2a63acb8c427ba3246f9c5fc1b436aac578a5df421927c23b4fde45f5?s=96&d=mm&r=g\",\"caption\":\"Jeff Everhart\"},\"description\":\"Jeff is a Senior Developer Advocate at WP Engine, focusing on educational resources that blend JavaScript and WordPress to support the Atlas headless WordPress platform and ecosystem.\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/author\\\/jeff-everhartwpengine-com\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Headless WordPress Post Previews in Nuxt 3 - Builders","description":"In this post, you'll learn how to implement post previews from headless WordPress in a Nuxt 3 application.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/","og_locale":"en_US","og_type":"article","og_title":"Headless WordPress Post Previews in Nuxt 3","og_description":"In this post, you'll learn how to implement post previews from headless WordPress in a Nuxt 3 application.","og_url":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/","og_site_name":"Builders","article_published_time":"2023-07-27T17:16:19+00:00","article_modified_time":"2023-07-27T17:25:06+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/headless-wordpress-post-previews-nuxt3.jpg","type":"image\/jpeg"}],"author":"Jeff Everhart","twitter_card":"summary_large_image","twitter_title":"Headless WordPress Post Previews in Nuxt 3","twitter_description":"In this post, you'll learn how to implement post previews from headless WordPress in a Nuxt 3 application.","twitter_image":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/headless-wordpress-post-previews-nuxt3.jpg","twitter_creator":"@wpebuilders","twitter_site":"@wpebuilders","twitter_misc":{"Written by":"Jeff Everhart","Est. reading time":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/#article","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/"},"author":{"name":"Jeff Everhart","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/person\/b5a7f380738d25f57b00a5aaacf3db52"},"headline":"Headless WordPress Post Previews in Nuxt 3","datePublished":"2023-07-27T17:16:19+00:00","dateModified":"2023-07-27T17:25:06+00:00","mainEntityOfPage":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/"},"wordCount":1782,"commentCount":0,"publisher":{"@id":"https:\/\/wpengine.com\/builders\/#organization"},"image":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/#primaryimage"},"thumbnailUrl":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-9.53.20-AM-1024x651.png","keywords":["Faust.js","Nuxt.js","WPGraphQL"],"articleSection":["Headless"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/","url":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/","name":"Headless WordPress Post Previews in Nuxt 3 - Builders","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/#website"},"primaryImageOfPage":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/#primaryimage"},"image":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/#primaryimage"},"thumbnailUrl":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-9.53.20-AM-1024x651.png","datePublished":"2023-07-27T17:16:19+00:00","dateModified":"2023-07-27T17:25:06+00:00","description":"In this post, you'll learn how to implement post previews from headless WordPress in a Nuxt 3 application.","breadcrumb":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/#primaryimage","url":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-9.53.20-AM.png","contentUrl":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2023\/07\/Screenshot-2023-07-27-at-9.53.20-AM.png","width":1476,"height":938},{"@type":"BreadcrumbList","@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-post-previews-in-nuxt-3\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/wpengine.com\/builders\/"},{"@type":"ListItem","position":2,"name":"Headless WordPress Post Previews in Nuxt 3"}]},{"@type":"WebSite","@id":"https:\/\/wpengine.com\/builders\/#website","url":"https:\/\/wpengine.com\/builders\/","name":"Builders","description":"Reimagining the way we build with WordPress.","publisher":{"@id":"https:\/\/wpengine.com\/builders\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/wpengine.com\/builders\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/wpengine.com\/builders\/#organization","name":"WP Engine","url":"https:\/\/wpengine.com\/builders\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/logo\/image\/","url":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/05\/WP-Engine-Horizontal@2x.png","contentUrl":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/05\/WP-Engine-Horizontal@2x.png","width":348,"height":68,"caption":"WP Engine"},"image":{"@id":"https:\/\/wpengine.com\/builders\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/wpebuilders","https:\/\/www.youtube.com\/channel\/UCh1WuL54XFb9ZI6m6goFv1g"]},{"@type":"Person","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/person\/b5a7f380738d25f57b00a5aaacf3db52","name":"Jeff Everhart","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/237d84b2a63acb8c427ba3246f9c5fc1b436aac578a5df421927c23b4fde45f5?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/237d84b2a63acb8c427ba3246f9c5fc1b436aac578a5df421927c23b4fde45f5?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/237d84b2a63acb8c427ba3246f9c5fc1b436aac578a5df421927c23b4fde45f5?s=96&d=mm&r=g","caption":"Jeff Everhart"},"description":"Jeff is a Senior Developer Advocate at WP Engine, focusing on educational resources that blend JavaScript and WordPress to support the Atlas headless WordPress platform and ecosystem.","url":"https:\/\/wpengine.com\/builders\/author\/jeff-everhartwpengine-com\/"}]}},"_links":{"self":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts\/5505","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/users\/19"}],"replies":[{"embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/comments?post=5505"}],"version-history":[{"count":0,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts\/5505\/revisions"}],"wp:attachment":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/media?parent=5505"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/categories?post=5505"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/tags?post=5505"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}