{"id":31887,"date":"2025-05-19T11:16:17","date_gmt":"2025-05-19T16:16:17","guid":{"rendered":"https:\/\/wpengine.com\/builders\/?p=31887"},"modified":"2025-05-19T11:16:18","modified_gmt":"2025-05-19T16:16:18","slug":"post-previews-in-headless-wordpress-with-nuxt-3","status":"publish","type":"post","link":"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/","title":{"rendered":"Post Previews in Headless WordPress with Nuxt 3"},"content":{"rendered":"\n<p><a href=\"https:\/\/wordpress.org\/documentation\/article\/how-to-use-the-preview-function\/\">Post previews<\/a> in WordPress allow users to view content exactly as it will appear on the live site before it is published. This feature is essential for authors, editors, and administrators to review and approve posts, ensuring that formatting, images, and layouts are correct and meet the intended design and content standards.<\/p>\n\n\n\n<p>The default post preview functionality that WordPress core provides doesn\u2019t work for headless WordPress setups. Luckily, the Faust.js plugin makes it possible to add support for post previews with headless sites.<\/p>\n\n\n\n<p>This article will explain how I implemented post previews in a Nuxt 3 application using the Faust.js plugin. <\/p>\n\n\n\n<div class=\"wp-block-group has-polar-background-color has-background is-layout-flow wp-container-core-group-is-layout-7a03825d wp-block-group-is-layout-flow\" style=\"padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--40)\">\n<p class=\"has-large-font-size\"><strong>Table of Contents<\/strong><\/p>\n\n\n\n<ul id=\"Prerequisites\" class=\"wp-block-list\">\n<li><a href=\"#prerequisites\">Prerequisites<\/a><\/li>\n\n\n\n<li><a href=\"#faustjs-plugin\">The Faust.js Plugin<\/a><\/li>\n\n\n\n<li><a href=\"#translating-the-faust.js-auth-flow-to-nuxt\">Translating the Auth flow in Faust.js to Nuxt<\/a><\/li>\n\n\n\n<li><a href=\"#the-nuxt-config\">The Nuxt Config<\/a><\/li>\n\n\n\n<li><a href=\"#middleware\">Middleware<\/a><\/li>\n\n\n\n<li><a href=\"#server-api-route\">Server API Route<\/a><\/li>\n\n\n\n<li><a href=\"#faust-indexjs\">The Faust Index file<\/a><\/li>\n\n\n\n<li><a href=\"#rendering-the-preview-page\">Rendering the preview page<\/a><\/li>\n\n\n\n<li><a href=\"#conclusion\">Conclusion<\/a><\/li>\n<\/ul>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequisites\">Prerequisites<\/h2>\n\n\n\n<p>To benefit from this article, you should be familiar with the basics of working with the command line, <a href=\"https:\/\/wpengine.com\/builders\/headless-roadmap\/\">headless WordPress<\/a> development, <a href=\"https:\/\/vuejs.org\/\">Vue<\/a>, and <a href=\"https:\/\/nuxt.com\/\">Nuxt<\/a>.<br><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Steps for setting up:<\/strong><\/h3>\n\n\n\n<p>1. Set up a WordPress site and get it running.&nbsp; <a href=\"https:\/\/localwp.com\/\">Local by WP Engine<\/a> is a good dev environment to use.<br><\/p>\n\n\n\n<p>2.  Head over to your WP Admin and install and activate the <a href=\"https:\/\/www.wpgraphql.com\/\">WPGraphQL<\/a> and <a href=\"https:\/\/wordpress.org\/plugins\/faustwp\/\">Faust.js<\/a> WordPress plugins.<\/p>\n\n\n\n<p>3. Clone down <a href=\"https:\/\/github.com\/Fran-A-Dev\/nuxt3-headlesswp-post-previews\">the Nuxt repo<\/a> for this project by copying and pasting this command in your terminal:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" 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><span class=\"hljs-string\">`npx degit Fran-A-Dev\/nuxt3-headlesswp-post-previews#main my-project`<\/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\">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><br><\/p>\n\n\n\n<p>4. &nbsp;<code>CD<\/code> into your Nuxt project and open it up. Create a <strong>.env<\/strong> file in the root of the Nuxt project. Open that file in a text editor and paste in the following:<\/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>FAUST_SECRET_KEY=your-secret-key\n<\/span><\/span><span class='shcb-loc'><span>NUXT_PUBLIC_WORDPRESS_URL=https:<span class=\"hljs-comment\">\/\/your-wordpress-url<\/span>\n<\/span><\/span><span class='shcb-loc'><span>NUXT_PUBLIC_FRONTEND_SITE_URL=http:<span class=\"hljs-comment\">\/\/localhost:3000<\/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><\/p>\n\n\n\n<p>5. Go back to your WP Admin.  Navigate to <code>Settings &gt; Faust<\/code> and grab the secret key from the field it autopopulates in, as shown here.  Copy that key:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"551\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-16-at-10.16.34\u202fAM-1024x551.png\" alt=\"\" class=\"wp-image-31888\" srcset=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-16-at-10.16.34\u202fAM-1024x551.png 1024w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-16-at-10.16.34\u202fAM-300x161.png 300w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-16-at-10.16.34\u202fAM-768x413.png 768w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-16-at-10.16.34\u202fAM-1536x826.png 1536w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2025\/05\/Screenshot-2025-05-16-at-10.16.34\u202fAM-2048x1101.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>While you are in the WP Admin, copy your WordPress URL as well.  Then head back over to your <code>.env<\/code> file in your Nuxt project and add your secret key and URL appropriately.<\/p>\n\n\n\n<p>6. Run <strong><code>npm install<\/code><\/strong> to install dependencies.  <\/p>\n\n\n\n<p>7. Run <code>npm run dev <\/code>to start the dev server locally.<br><\/p>\n\n\n\n<p>You should now be able to navigate to your WP Admin, add or edit a post, click the preview link, and see the post preview work in your Nuxt frontend before clicking the publish button:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1920\" height=\"1080\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2025\/05\/ScreenFlow.gif\" alt=\"\" class=\"wp-image-31889\"\/><\/figure>\n\n\n\n<p><br><\/p>\n\n\n\n<p>Stoked!!!! The headless post previews flow works in Nuxt!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"faustjs-plugin\">The Faust.js Plugin<\/h2>\n\n\n\n<p>Faust.js is a plugin that was created to accompany the <a href=\"https:\/\/faustjs.org\/\">Faust.js<\/a> toolkit, a suite of tools for adding common features such as authentication, post previews, and more to headless WordPress sites.<br><\/p>\n\n\n\n<p>The Faust toolkit handles everything from fetching posts and pages to managing previews and authenticated endpoints. While the official implementation is built around React and Next.js, the underlying ideas\u2014 data fetching, post previews, and token-based authentication\u2014are framework-agnostic. &nbsp; This is what we will take advantage of in this article! Stoked!!!<br><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"translating-the-faust.js-auth-flow-to-nuxt\">Translating the Faust.js Auth Flow to Nuxt.js<\/h2>\n\n\n\n<p>Previewing unpublished or draft content requires WordPress to verify that the requester is an authenticated user with proper permissions. Faust.js achieves this via an OAuth2-style sequence of short-lived codes and tokens. Here\u2019s the typical flow\u2014and how each step maps to our Nuxt implementation:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>User clicks <strong>&#8220;Preview in new tab&#8221;<\/strong> in WordPress<\/li>\n<\/ol>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li>WordPress sends the user to Nuxt with <code>preview=true<\/code> and <code>p=[post_id]<\/code> query parameters<\/li>\n<\/ol>\n\n\n\n<ol start=\"3\" class=\"wp-block-list\">\n<li>Middleware intercepts and redirects to WordPress&#8217;s <code>\/generate<\/code> endpoint<\/li>\n<\/ol>\n\n\n\n<ol start=\"4\" class=\"wp-block-list\">\n<li>WordPress authenticates the user and redirects back&nbsp;to the Nuxt app&nbsp;with a one-time OAuth&nbsp;authorization code in the URL query parameters from Faust and WordPress<\/li>\n<\/ol>\n\n\n\n<ol start=\"5\" class=\"wp-block-list\">\n<li>Preview page loads and calls our API endpoint with the code<\/li>\n<\/ol>\n\n\n\n<ol start=\"6\" class=\"wp-block-list\">\n<li>API endpoint exchanges code for tokens using <code>faust\/index.js<\/code><\/li>\n<\/ol>\n\n\n\n<ol start=\"7\" class=\"wp-block-list\">\n<li>API uses the access token to fetch preview data from WordPress GraphQL<\/li>\n<\/ol>\n\n\n\n<ol start=\"8\" class=\"wp-block-list\">\n<li>API sets refresh token in HTTP-only cookie and returns post data<\/li>\n<\/ol>\n\n\n\n<ol start=\"9\" class=\"wp-block-list\">\n<li>Preview page renders the content<\/li>\n<\/ol>\n\n\n\n<p><\/p>\n\n\n\n<p>In subsequent preview requests, if the cookie exists, the API will try to use the refresh token first before falling back to the authorization code.<\/p>\n\n\n\n<p>In the next sections, let\u2019s explain each of the files that make this work. If you want to dive into each line of code in the files, I will link to them in the sections.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-nuxt-config\">The Nuxt Config<\/h2>\n\n\n\n<p>The first file we will take a look at is the <code><a href=\"https:\/\/github.com\/Fran-A-Dev\/nuxt3-headlesswp-post-previews\/blob\/main\/nuxt.config.js\">nuxt.config.js file<\/a><\/code> at the root of the project.<\/p>\n\n\n\n<p>The Nuxt configuration maps environment variables to the&nbsp;application&#8217;s runtime config, separating sensitive server-side values&nbsp;<code>(FAUST_SECRET_KEY)<\/code> from public&nbsp;client-accessible URLs. <\/p>\n\n\n\n<p>This makes&nbsp;WordPress connection details available throughout&nbsp;the app via the <code><a href=\"https:\/\/nuxt.com\/docs\/guide\/going-further\/runtime-config\">useRuntimeConfig()<\/a><\/code> composable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"middleware\">Middleware<\/h2>\n\n\n\n<p>The second file that we will go over is the <code>preview.global.js<\/code> file in the <code><a href=\"https:\/\/github.com\/Fran-A-Dev\/nuxt3-headlesswp-post-previews\/blob\/main\/middleware\/preview.global.js\">middleware<\/a><\/code> folder.<\/p>\n\n\n\n<p>This middleware file serves as the&nbsp;traffic controller for WordPress post previews in our&nbsp;project, intercepting and routing&nbsp;preview requests. <\/p>\n\n\n\n<p>It handles two critical&nbsp;scenarios: first, when an&nbsp;editor clicks <strong>&#8220;Preview&#8221;<\/strong> in&nbsp;WordPress, it redirects them to WordPress&#8217;s authentication endpoint with a&nbsp;properly configured redirect URI; and second, it allows authenticated preview requests with a valid authorization code to access the preview&nbsp;page directly. <\/p>\n\n\n\n<p>The most&nbsp;essential code is the redirect&nbsp;itself: <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" 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><span class=\"hljs-keyword\">return<\/span> navigateTo(\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-string\">`<span class=\"hljs-subst\">${config.public.wordpressUrl}<\/span>\/generate?redirect_uri=<span class=\"hljs-subst\">${config.public.frontendSiteUrl}<\/span>\/preview?preview_id=<span class=\"hljs-subst\">${previewId}<\/span>`<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      { <span class=\"hljs-attr\">external<\/span>: <span class=\"hljs-literal\">true<\/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\">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>This builds the secure&nbsp;authentication pathway between WordPress and our&nbsp;Nuxt application.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"server-api-route\">Server API Route<\/h2>\n\n\n\n<p>Next, let&#8217;s go over the <code>preview.get.js<\/code> file in the <code><a href=\"https:\/\/github.com\/Fran-A-Dev\/nuxt3-headlesswp-post-previews\/blob\/main\/server\/api\/preview.get.js\">server\/api<\/a><\/code> folder.<\/p>\n\n\n\n<p>This server route functions&nbsp;as the authentication and data&nbsp;retrieval engine for WordPress&nbsp;post previews. It handles the secure exchange of authorization codes for access tokens and then uses those tokens to fetch unpublished content. <\/p>\n\n\n\n<p>It implements&nbsp;a multi-tier authentication&nbsp;approach, first trying to use a refresh&nbsp;token from a cookie, falling back&nbsp;to the one-time authorization code if&nbsp;needed, and then making&nbsp;an authenticated GraphQL request&nbsp;to WordPress with the crucial&nbsp;parameter&nbsp;<code>asPreview:&nbsp;true<\/code>&nbsp;to access draft content. <\/p>\n\n\n\n<p>The most important lines are the authentication&nbsp;header<code>&nbsp;Authorization: \\Bearer ${tokens.accessToken}\\<\/code> which allows secure access&nbsp;to unpublished content.<\/p>\n\n\n\n<p>The cookie handling<code>&nbsp;setCookie(event,&nbsp;\"faust_preview_rt\", tokens.refreshToken, {...})<\/code> enables a seamless preview&nbsp;experience for subsequent requests.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"faust-indexjs\">The faust\/index.js<\/h2>\n\n\n\n<p>The next file up is the <code>index.js<\/code> file in the <code><a href=\"https:\/\/github.com\/Fran-A-Dev\/nuxt3-headlesswp-post-previews\/blob\/main\/faust\/index.js\">faust<\/a><\/code> folder.<\/p>\n\n\n\n<p>This Faust utility&nbsp;file serves as the authentication bridge&nbsp;between our Nuxt application and WordPress, providing a secure mechanism for exchanging authorization codes and refresh&nbsp;tokens for valid access tokens. It implements a&nbsp;central approach with the token exchange&nbsp;logic in a single private&nbsp;function while exposing two&nbsp;specific public methods for different authentication scenarios. <\/p>\n\n\n\n<p>The most essential parts of this implementation are&nbsp;the WordPress REST API endpoint construction:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" 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><span class=\"hljs-keyword\">const<\/span> authorizeUrl = <span class=\"hljs-string\">`<span class=\"hljs-subst\">${config.public.wordpressUrl}<\/span>\/?rest_route=\/faustwp\/v1\/authorize`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(authorizeUrl, {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">headers<\/span>: {\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>    <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify(payload),\n<\/span><\/span><span class='shcb-loc'><span>  });\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\">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>This targets&nbsp;the FaustWP plugin&#8217;s&nbsp;authentication endpoint and the security&nbsp;header, which authenticates our app to WordPress using the secret key.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"rendering-the-preview-page\">Rendering the preview page<\/h2>\n\n\n\n<p>The final step before we see our preview data on the browser is to render it.  We do this in the <code>preview.vue<\/code> file that lives in the <code><a href=\"https:\/\/github.com\/Fran-A-Dev\/nuxt3-headlesswp-post-previews\/blob\/main\/pages\/preview.vue\">pages<\/a><\/code> folder.  <\/p>\n\n\n\n<p>This preview page component serves as the frontend interface for displaying WordPress draft&nbsp;posts, orchestrating the authentication flow, and rendering unpublished content. <\/p>\n\n\n\n<p>It&nbsp;manages the preview&nbsp;session by first checking for an existing refresh&nbsp;token cookie, falling back to the&nbsp;URL&#8217;s authorization code when needed, and then making an API request to&nbsp;fetch the protected content with the&nbsp;appropriate credentials. <\/p>\n\n\n\n<p>The authentication logic in the file is as follows:&nbsp;<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" 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><span class=\"hljs-keyword\">if<\/span> (cookie.value) {\n<\/span><\/span><span class='shcb-loc'><span>  queryParams = { <span class=\"hljs-attr\">refreshToken<\/span>: cookie.value, previewId };\n<\/span><\/span><span class='shcb-loc'><span>} <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (code) {\n<\/span><\/span><span class='shcb-loc'><span>  queryParams = { code, previewId };\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\">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>This implements a seamless authentication&nbsp;flow.<\/p>\n\n\n\n<p> Then, the server makes a request to retrieve the draft&nbsp;content while maintaining proper separation between&nbsp;the presentation layer and the authentication&nbsp;logic:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" 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><span class=\"hljs-keyword\">const<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">data<\/span>: fetchData,\n<\/span><\/span><span class='shcb-loc'><span>  pending,\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">error<\/span>: fetchError,\n<\/span><\/span><span class='shcb-loc'><span>} = <span class=\"hljs-keyword\">await<\/span> useFetch(<span class=\"hljs-string\">\"\/api\/preview\"<\/span>, {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"GET\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">query<\/span>: queryParams,\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\">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><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"conclusion\">Conclusion<\/h2>\n\n\n\n<p>We hope this article helped you understand how post previews in Nuxt 3 work in my example project with the Faust plugin!  <\/p>\n\n\n\n<p>As always, we\u2019re super stoked to hear your feedback and learn about the headless projects you\u2019re working on, so hit us up in the <a href=\"https:\/\/wpeng.in\/devrel-discord\/\">Headless WordPress Discord<\/a>!<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Post previews in WordPress allow users to view content exactly as it will appear on the live site before it is published. This feature is essential for authors, editors, and [&hellip;]<\/p>\n","protected":false},"author":20,"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":[],"class_list":["post-31887","post","type-post","status-publish","format-standard","hentry","category-headless"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Post Previews in Headless WordPress with Nuxt 3 - Builders<\/title>\n<meta name=\"description\" content=\"Explore how a Nuxt 3 example project leverages the Faust.js plugin to add WordPress post previews in a headless setup. This summary article explains the core files\u2014middleware, server endpoint, auth helpers, and the Vue preview component\u2014so you understand how it all fits together.\" \/>\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\/post-previews-in-headless-wordpress-with-nuxt-3\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Post Previews in Headless WordPress with Nuxt 3\" \/>\n<meta property=\"og:description\" content=\"A walkthrough explanation of a Nuxt 3 example project that uses the Faust.js plugin to enable headless WordPress draft previews\u2014covering the middleware redirect, server API token exchange, helper utilities, and the preview page component.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/\" \/>\n<meta property=\"og:site_name\" content=\"Builders\" \/>\n<meta property=\"article:published_time\" content=\"2025-05-19T16:16:17+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-05-19T16:16:18+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2025\/05\/WPE-Builders-YouTube-ScreenshotNavy-1920x1080-1.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1080\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Francis Agulto\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\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=\"Francis Agulto\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-headless-wordpress-with-nuxt-3\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-headless-wordpress-with-nuxt-3\\\/\"},\"author\":{\"name\":\"Francis Agulto\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/person\\\/bcdcb4ac0b215c34b6b30e440a24dc54\"},\"headline\":\"Post Previews in Headless WordPress with Nuxt 3\",\"datePublished\":\"2025-05-19T16:16:17+00:00\",\"dateModified\":\"2025-05-19T16:16:18+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-headless-wordpress-with-nuxt-3\\\/\"},\"wordCount\":1288,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-headless-wordpress-with-nuxt-3\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2025\\\/05\\\/ScreenFlow.gif\",\"articleSection\":[\"Headless\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-headless-wordpress-with-nuxt-3\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-headless-wordpress-with-nuxt-3\\\/\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-headless-wordpress-with-nuxt-3\\\/\",\"name\":\"Post Previews in Headless WordPress with Nuxt 3 - Builders\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-headless-wordpress-with-nuxt-3\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-headless-wordpress-with-nuxt-3\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2025\\\/05\\\/ScreenFlow.gif\",\"datePublished\":\"2025-05-19T16:16:17+00:00\",\"dateModified\":\"2025-05-19T16:16:18+00:00\",\"description\":\"Explore how a Nuxt 3 example project leverages the Faust.js plugin to add WordPress post previews in a headless setup. This summary article explains the core files\u2014middleware, server endpoint, auth helpers, and the Vue preview component\u2014so you understand how it all fits together.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-headless-wordpress-with-nuxt-3\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-headless-wordpress-with-nuxt-3\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-headless-wordpress-with-nuxt-3\\\/#primaryimage\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2025\\\/05\\\/ScreenFlow.gif\",\"contentUrl\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2025\\\/05\\\/ScreenFlow.gif\",\"width\":1920,\"height\":1080},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-headless-wordpress-with-nuxt-3\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Post Previews in Headless WordPress with 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\\\/bcdcb4ac0b215c34b6b30e440a24dc54\",\"name\":\"Francis Agulto\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/0c8a05c76944fc987d57296c96dc368055844527088c0aa44297edbfa8b82546?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/0c8a05c76944fc987d57296c96dc368055844527088c0aa44297edbfa8b82546?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/0c8a05c76944fc987d57296c96dc368055844527088c0aa44297edbfa8b82546?s=96&d=mm&r=g\",\"caption\":\"Francis Agulto\"},\"description\":\"Fran Agulto is a Developer Advocate at WP Engine. He is a lover of all things headless WordPress, Rock Climbing, and overall being stoked for people that love what they do and share that stoke with others! Follow me on Twitter for cool stoked headless WP!\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/author\\\/francis-agultowpengine-com-2-2-2-2-2-2-2-2-2-2-2-3\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Post Previews in Headless WordPress with Nuxt 3 - Builders","description":"Explore how a Nuxt 3 example project leverages the Faust.js plugin to add WordPress post previews in a headless setup. This summary article explains the core files\u2014middleware, server endpoint, auth helpers, and the Vue preview component\u2014so you understand how it all fits together.","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\/post-previews-in-headless-wordpress-with-nuxt-3\/","og_locale":"en_US","og_type":"article","og_title":"Post Previews in Headless WordPress with Nuxt 3","og_description":"A walkthrough explanation of a Nuxt 3 example project that uses the Faust.js plugin to enable headless WordPress draft previews\u2014covering the middleware redirect, server API token exchange, helper utilities, and the preview page component.","og_url":"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/","og_site_name":"Builders","article_published_time":"2025-05-19T16:16:17+00:00","article_modified_time":"2025-05-19T16:16:18+00:00","og_image":[{"width":1920,"height":1080,"url":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2025\/05\/WPE-Builders-YouTube-ScreenshotNavy-1920x1080-1.png","type":"image\/png"}],"author":"Francis Agulto","twitter_card":"summary_large_image","twitter_creator":"@wpebuilders","twitter_site":"@wpebuilders","twitter_misc":{"Written by":"Francis Agulto","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/#article","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/"},"author":{"name":"Francis Agulto","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/person\/bcdcb4ac0b215c34b6b30e440a24dc54"},"headline":"Post Previews in Headless WordPress with Nuxt 3","datePublished":"2025-05-19T16:16:17+00:00","dateModified":"2025-05-19T16:16:18+00:00","mainEntityOfPage":{"@id":"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/"},"wordCount":1288,"commentCount":0,"publisher":{"@id":"https:\/\/wpengine.com\/builders\/#organization"},"image":{"@id":"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/#primaryimage"},"thumbnailUrl":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2025\/05\/ScreenFlow.gif","articleSection":["Headless"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/","url":"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/","name":"Post Previews in Headless WordPress with Nuxt 3 - Builders","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/#website"},"primaryImageOfPage":{"@id":"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/#primaryimage"},"image":{"@id":"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/#primaryimage"},"thumbnailUrl":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2025\/05\/ScreenFlow.gif","datePublished":"2025-05-19T16:16:17+00:00","dateModified":"2025-05-19T16:16:18+00:00","description":"Explore how a Nuxt 3 example project leverages the Faust.js plugin to add WordPress post previews in a headless setup. This summary article explains the core files\u2014middleware, server endpoint, auth helpers, and the Vue preview component\u2014so you understand how it all fits together.","breadcrumb":{"@id":"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/#primaryimage","url":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2025\/05\/ScreenFlow.gif","contentUrl":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2025\/05\/ScreenFlow.gif","width":1920,"height":1080},{"@type":"BreadcrumbList","@id":"https:\/\/wpengine.com\/builders\/post-previews-in-headless-wordpress-with-nuxt-3\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/wpengine.com\/builders\/"},{"@type":"ListItem","position":2,"name":"Post Previews in Headless WordPress with 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\/bcdcb4ac0b215c34b6b30e440a24dc54","name":"Francis Agulto","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/0c8a05c76944fc987d57296c96dc368055844527088c0aa44297edbfa8b82546?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/0c8a05c76944fc987d57296c96dc368055844527088c0aa44297edbfa8b82546?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/0c8a05c76944fc987d57296c96dc368055844527088c0aa44297edbfa8b82546?s=96&d=mm&r=g","caption":"Francis Agulto"},"description":"Fran Agulto is a Developer Advocate at WP Engine. He is a lover of all things headless WordPress, Rock Climbing, and overall being stoked for people that love what they do and share that stoke with others! Follow me on Twitter for cool stoked headless WP!","url":"https:\/\/wpengine.com\/builders\/author\/francis-agultowpengine-com-2-2-2-2-2-2-2-2-2-2-2-3\/"}]}},"_links":{"self":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts\/31887","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\/20"}],"replies":[{"embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/comments?post=31887"}],"version-history":[{"count":0,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts\/31887\/revisions"}],"wp:attachment":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/media?parent=31887"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/categories?post=31887"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/tags?post=31887"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}