{"id":31512,"date":"2024-03-15T11:10:53","date_gmt":"2024-03-15T16:10:53","guid":{"rendered":"https:\/\/wpengine.com\/builders\/?p=31512"},"modified":"2024-03-19T15:17:58","modified_gmt":"2024-03-19T20:17:58","slug":"post-previews-in-the-app-router-with-faust-js","status":"publish","type":"post","link":"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/","title":{"rendered":"Post Previews in the App Router with Faust.js for headless WordPress"},"content":{"rendered":"\n<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 administrators to review and approve posts, ensuring that formatting, images, and layouts are correct and meet the intended design and content standards. Post previews are accessible through the WordPress editor, where a &#8220;Preview&#8221; button is available.&nbsp;&nbsp;<\/p>\n\n\n\n<p>Getting this to work in a headless setup is difficult.&nbsp; Luckily, Faust.js does this out of the box.<\/p>\n\n\n\n<p>This article delves into the mechanics of post previews within the experimental app router example project of Faust.js. We will explore the implementation of Post Previews in traditional WordPress, examine the integration with Faust.js&#8217;s App Router, and dissect the underlying processes within the Faust.js framework itself.&nbsp; &nbsp;<\/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 class=\"wp-block-list\" id=\"Prerequisites\">\n<li><a href=\"#prerequisites\">Prerequisites<\/a> <\/li>\n\n\n\n<li><a href=\"#Post-Previews-in-Traditional-WordPress\">Post Previews in Traditional WordPress<\/a><\/li>\n\n\n\n<li><a href=\"#faustjs-and-app-router-support\">Faust.js and App Router Support<\/a><\/li>\n\n\n\n<li><a href=\"#the-default-files\">The Default Files<\/a><\/li>\n\n\n\n<li><a href=\"#create-your-first-custom-template\">Create Your First Custom Template<\/a><\/li>\n\n\n\n<li><a href=\"#the-index-file\">The Index File<\/a><\/li>\n\n\n\n<li><a href=\"#conclusion\">Conclusion<\/a><\/li>\n<\/ul>\n<\/div>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequisites\">Prerequisites<\/h2>\n\n\n\n<p>To fully comprehend and gain insight from this article, you should have a foundational understanding of the App Router, including its naming conventions and its directory and file hierarchy. If you are not yet familiar with these concepts, <a href=\"https:\/\/wpengine.com\/builders\/next-js-13-and-wpgraphql-in-headless-wordpress\/\">please reference my previous article <\/a>on the subject before proceeding with this one.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"Post-Previews-in-Traditional-WordPress\">Post Previews in Traditional WordPress<\/h2>\n\n\n\n<p>Before we discuss how Faust.js with the App Router does Post Previews, let&#8217;s take a look at WordPress&#8217; traditional monolithic architecture approaches it under the hood:<\/p>\n\n\n\n<p><strong>Draft Saving:<\/strong> When you&#8217;re working on a post in WordPress, it automatically saves your changes as a draft. This draft is stored in the WordPress database in the <code>wp_posts<\/code> table, with a post status of &#8216;auto-draft&#8217; or &#8216;draft&#8217;.<\/p>\n\n\n\n<p><strong>Preview Request:<\/strong> When you click the &#8220;Preview&#8221; button, WordPress initiates a request to generate a preview of the post. This request includes a query parameter (usually preview=true) that tells WordPress this is a preview request.<\/p>\n\n\n\n<p><strong>Post Revision Creation:<\/strong> To handle the preview, WordPress creates a post revision. This is a copy of your post at that moment, also stored in the <code>wp_posts<\/code> table, but with a post type of &#8216;revision&#8217;. This ensures that your current edits, even if not saved as a draft, are captured in this revision.<\/p>\n\n\n\n<p><strong>Preview Link Generation:<\/strong> WordPress generates a preview link that includes a nonce (a one-time use security token) to ensure the preview request is valid. This link points to the post URL but with additional query parameters that instruct WordPress to load the revision instead of the published content.<\/p>\n\n\n\n<p><strong>User Role and Permission Check:<\/strong> When the preview link is accessed, WordPress checks the user&#8217;s role and permissions to ensure they have the right to view the preview. This step is crucial for content security and integrity.<\/p>\n\n\n\n<p><strong>Rendering the Preview:<\/strong> If the user has the appropriate permissions, WordPress then loads the post revision data instead of the main post data. The site&#8217;s theme and styling are applied to this revision content, and it&#8217;s rendered in the user\u2019s browser. This process involves the same template hierarchy and rendering engine as the live site, ensuring an accurate representation of how the post will look once published.<\/p>\n\n\n\n<p><strong>Non-Published Content Handling:<\/strong> It&#8217;s important to note that this preview mechanism allows users to see changes in real-time for unpublished (draft or pending) posts as well as changes to already published posts. For published posts, WordPress stores the changes as revisions without affecting the live version until those changes are explicitly published.<\/p>\n\n\n\n<p><strong>Security Measures:<\/strong> The nonce and permission checks play a crucial role in ensuring that only authorized users can access the post previews, protecting unpublished content from unauthorized access.<\/p>\n\n\n\n<p>This engineering design enables WordPress users to securely preview their content, ensuring that only the intended changes are published to the live site. It provides a precise preview of how the content will be presented to the end-users.<\/p>\n\n\n\n<p>Let\u2019s dive into how Faust.js with the App Router replicates this experience and engineering in headless WordPress from a decoupled architecture.  <\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"faustjs-and-app-router-support\">Faust.js and App Router Support<\/h2>\n\n\n\n<p><a href=\"https:\/\/faustjs.org\/tutorial\/getting-started-with-the-experimental-app-router\">The experimental app router example project from Faust.js<\/a> is a boilerplate starter kit that includes a sample site showcasing the utility of functions such as <code>getClient<\/code>, <code>getAuthClient<\/code>, <code>faustRouteHandler<\/code>, <code>loginAction<\/code>, and <code>logoutAction<\/code>. This project can serve as a foundation for your future endeavors and as an informative reference for App Routing in headless WordPress. It is designed for those who are proficient with the command line, have fundamental knowledge of Faust.js and Next.js routing, and have an understanding of the basics of JavaScript, WordPress, and Bash.<\/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<div class=\"wp-block-group has-base-color has-text-color has-background has-link-color wp-elements-688844c5591b0d750f2228c71fe3f5ec 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:#180038;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-682063c7 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-e4830150 wp-block-group-is-layout-flex\">\n<div class=\"wp-block-outermost-icon-block\"><div class=\"icon-container\" style=\"width:38px\"><svg fill=\"none\" viewBox=\"0 0 38 30\" aria-label=\"Frost logo\"><path fill=\"currentColor\" fill-rule=\"evenodd\" d=\"M18.149.498c-.043.01-.16.025-.259.033-.406.03-.933.112-1.53.238-.205.043-.769.198-.98.269-.114.039-.574.207-.618.226l-.292.124c-.3.128-.74.346-1.01.502a4.92 4.92 0 0 1-.206.115 10.57 10.57 0 0 0-4.878 6.728c-.018.106-.035.118-.237.165-.17.039-.23.055-.54.147C3.36 10.302.453 13.9.037 18.401c-.045.49-.05 8.792-.006 9.065.18 1.094.97 1.87 2.052 2.012.375.049 4.136.05 4.527.002 1.185-.148 2.032-1.046 2.102-2.228.055-.925.614-1.62 1.403-1.745.29-.046.37-.05.528-.022.905.158 1.437.76 1.5 1.7.083 1.249.888 2.135 2.084 2.294.363.048 4.162.048 4.518 0 .633-.086 1.262-.421 1.616-.862l.124-.154c.13-.156.29-.575.341-.885.046-.277.047-8.617.002-8.907-.183-1.17-1.208-1.979-2.505-1.979-.386 0-1.057-.13-1.593-.306a5.82 5.82 0 0 1-3.868-4.496c-.16-.907-.112-1.586.187-2.655.417-1.494 1.717-2.941 3.22-3.585l.158-.068c.28-.121.809-.268 1.214-.336a5.03 5.03 0 0 1 1.98.042c2.288.53 3.911 2.078 4.476 4.272.198.768.189.427.19 7.436.002 3.432.012 6.28.023 6.399.304 3.24 2.565 5.63 5.713 6.037.829.107 1.116.107 1.8-.001 2.037-.322 3.807-1.829 4.698-4.001l.046-.112c.148-.36.347-1.107.407-1.53.119-.823.073-1.372-.15-1.817-.255-.508-.802-.924-1.493-1.137-.253-.078-.998-.08-1.2-.005-.026.01-.11.037-.183.06-.666.202-1.24.854-1.493 1.696-.03.101-.093.523-.093.63 0 .044-.02.162-.046.26-.44 1.724-2.917 1.715-3.316-.013-.03-.128-.034-.905-.034-6.275 0-6.296-.005-6.684-.089-7.276a11.333 11.333 0 0 0-.114-.742c-.012-.05-.042-.186-.066-.304-.734-3.596-3.431-6.625-6.951-7.805a9.125 9.125 0 0 0-.855-.246 7.503 7.503 0 0 0-.888-.179 18.617 18.617 0 0 0-.877-.117c-.28-.033-.874-.046-.977-.02Zm-.047 8.132c-1.224.212-1.998 1.382-1.8 2.72.027.186.034.213.115.462.554 1.717 3.03 1.998 3.992.452.055-.088.1-.165.1-.172 0-.006.025-.064.056-.128.131-.27.214-.68.214-1.052 0-1.52-1.194-2.539-2.677-2.282Zm-9.55 5.116c.577 2.153 2.094 4.3 3.988 5.648.78.554 1.328.849 2.38 1.28.077.031.556.195.675.23l.247.07.236.069.101.03.006 1.66c.006 1.695.002 1.779-.078 1.615-.772-1.58-2.366-2.836-4.134-3.256-.626-.149-.883-.178-1.552-.178-2.132.001-4.114 1.027-5.234 2.711-.17.254-.36.57-.412.686-.101.22-.1.256-.1-2.611 0-3 .005-3.148.129-3.748a5.873 5.873 0 0 1 1.94-3.294 5.92 5.92 0 0 1 1.518-.927c.222-.091.262-.09.29.015Z\" clip-rule=\"evenodd\"><\/path><\/svg><\/div><\/div>\n\n\n\n<p class=\"has-large-font-size\" style=\"font-style:normal;font-weight:800;letter-spacing:-1px\">WPGraphQL<\/p>\n<\/div>\n\n\n\n<p class=\"has-small-font-size wp-container-content-c86e52ed\" style=\"line-height:1.5\">An extendable GraphQL <br>schema &amp; API for WordPress.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-horizontal is-content-justification-center is-nowrap is-layout-flex wp-container-core-buttons-is-layout-3bdbf2e2 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\/wpgraphql-builders\/\" style=\"border-radius:99px;padding-top:10px;padding-right:24px;padding-bottom:10px;padding-left:24px\" target=\"_blank\" rel=\"noreferrer noopener\">Download<\/a><\/div>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<p>Now, let&#8217;s focus on how it does Post Previews.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"how-post-previews-work\">How Post Previews Work in Faust.js with the App Router<\/h2>\n\n\n\n<p>Remember, everything in the App Router in Faust.js defaults to being a server component unless otherwise specified with the <code>\u2018use client\u2019<\/code> directive.&nbsp; Let\u2019s look at the Faust.js App Router project\u2019s folders and files that make Post Previews work along with the Faust.js plugin necessary for this.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Environment Variables<\/h3>\n\n\n\n<p>The first action necessary to make post previews work in App Router is the Faust.js plugin\u2019s secret key,&nbsp;<\/p>\n\n\n\n<p>To get the secret key, navigate to the <em>&#8216;Add Plugins&#8217;<\/em> page in WP Admin and search for the Faust.js plugin.<\/p>\n\n\n\n<p>Once that is installed and activated, when you hover over the settings option in the left-side hamburger menu, it will display a Faust option.&nbsp; Click on that and you will see this page:<\/p>\n\n\n\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/lh7-us.googleusercontent.com\/uS50EXkewkPXoaMWqB74cymBUi9hyrlrL5Hg-TPwHD-A0ls_W2VwPPa_iXSUZHPRyYL4lAVzpbBBLlu4bWjiHjOPxQZ0Tqb1AAKYtmRmOlzHCkb8uWZsvDeuOmt1mpfG68H2E3CKdWPeuDVR53VXRhQ\" width=\"637\" height=\"317\"><\/p>\n\n\n\n<p>&nbsp;It is on this page that you can grab your secret key and your front-end URL which in a development server case, it&#8217;s going to be off port 3000 on local host.&nbsp; The last thing you need to get is your WordPress site URL.<\/p>\n\n\n\n<p>Now, navigate over to your code editor. In the <code>.env.local<\/code> file that you created when spinning up the boilerplate, you can add those values to their keys like so:<\/p>\n\n\n\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/lh7-us.googleusercontent.com\/-f25-N8RO8FwswUX3odOU5Q_ByvUUrAaUoaVCicRbhdp_DwwsGP3kgG2jsJ_1C8vveoMo-ZtfvQR98QCZy0ELSKkhY4tP8K-QZ0Wr5ldYILiC93e-FU9RC7wV9iNHMf-fYka5C0vcCFtduQnKsKeQoA\" width=\"666\" height=\"384\"><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Faust.js API Route Handler<\/h3>\n\n\n\n<p>Once you have your environment variables set, ensure that you have a directory in the root of the app folder called <code>api<\/code>.&nbsp; In this <code>api<\/code> folder, you should have a nested folder called faust which will contain a file called <code>[route].ts<\/code>.&nbsp; It should look like this:<br><\/p>\n\n\n\n<p><img loading=\"lazy\" decoding=\"async\" width=\"631\" height=\"445\" src=\"https:\/\/lh7-us.googleusercontent.com\/UWvgM8ZnutJGTi6JqrvgdHm8aBDkoPiB4R65iABIWIEyC2KtbCTdSF95gQLlzKURFD-zj3FkBZgJ4O7mMA851uEGn3FNMOIsowf_moGDKD8hPdHHXHSubQEG-_QW1C_sfsgu_mepuLy00d3lDfzUa58\"><\/p>\n\n\n\n<p>What this code executes is the handling of the endpoints for the auth token and login\/logout functionality.&nbsp;&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Authenticated Requests<\/h3>\n\n\n\n<p>Navigate to the dynamic route segment that is set by the slug as its parameter which is the <code>[slug]\/hasPreviewProps.ts<\/code> file in the root of the <code>app<\/code> folder.&nbsp; You should see this:<\/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-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">hasPreviewProps<\/span>(<span class=\"hljs-params\">props: any<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> props?.searchParams?.preview === <span class=\"hljs-string\">'true'<\/span> &amp;&amp; !!props?.searchParams?.p;\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\">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>The function <code>hasPreviewProps<\/code> checks if the preview mode is activated and if there is a post ID present in the properties passed to it. It returns <code>true<\/code> only if both conditions are met: the preview parameter is set to &#8216;true&#8217; and a post ID is specified. This is used to determine if a post preview should be displayed. If either condition is not fulfilled, the function returns <code>false<\/code>, indicating that the normal, non-preview content should be rendered. This helps in conditionally showing either the live post or its preview during development.<\/p>\n\n\n\n<p>Now that we have a function to help check if previews are activated and present, let&#8217;s look at the <code>page.tsx<\/code> file in the same directory to see how authentication and rendering the preview on the content works.<\/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><span class=\"hljs-keyword\">import<\/span> { getAuthClient, getClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@faustwp\/experimental-app-router'<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { gql } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@apollo\/client'<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { hasPreviewProps } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/hasPreviewProps'<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { PleaseLogin } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/components\/please-login'<\/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\">default<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Page<\/span>(<span class=\"hljs-params\">props<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> isPreview = hasPreviewProps(props);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> id = isPreview ? props.searchParams.p : props.params.slug;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">let<\/span> client = isPreview ? <span class=\"hljs-keyword\">await<\/span> getAuthClient() : <span class=\"hljs-keyword\">await<\/span> getClient();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (!client) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">PleaseLogin<\/span> \/&gt;<\/span><\/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\">const<\/span> { data } = <span class=\"hljs-keyword\">await<\/span> client.query({\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">query<\/span>: gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      query GetContentNode(<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        $id: ID!<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        $idType: ContentNodeIdTypeEnum!<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        $asPreview: Boolean!<\/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\">        contentNode(id: $id, idType: $idType, asPreview: $asPreview) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">          ... on NodeWithTitle {<\/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\">          }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">          ... on NodeWithContentEditor {<\/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\">          }<\/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>    <span class=\"hljs-attr\">variables<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>      id,\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">idType<\/span>: isPreview ? <span class=\"hljs-string\">'DATABASE_ID'<\/span> : <span class=\"hljs-string\">'URI'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">asPreview<\/span>: isPreview,\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\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>{data?.contentNode?.title}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-attr\">dangerouslySetInnerHTML<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">__html:<\/span> <span class=\"hljs-attr\">data<\/span>?<span class=\"hljs-attr\">.contentNode<\/span>?<span class=\"hljs-attr\">.content<\/span> ?? '' }}<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      \/&gt;<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  );<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><\/span><\/span><\/span><\/span><\/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>At the very top of the file, the necessary modules are imported, including functions for authentication and GraphQL queries, the utility function to check for preview props, and a component to prompt for login if needed.<\/p>\n\n\n\n<p>Following our imports, we define an asynchronous <code>Page <\/code>function component that takes props as an argument.<\/p>\n\n\n\n<p>Within this function, we have a preview check.&nbsp; We use the <code>hasPreviewProps<\/code> helper function to determine if the page is in preview mode <code>(isPreview)<\/code> and sets the id based on whether it&#8217;s a preview or a published page, using query parameters <code>(searchParams.p)<\/code> for previews or URL parameters<code> (params.slug)<\/code> for published content.<\/p>\n\n\n\n<p>After that, we initialize the GraphQL Client<strong> <\/strong>with authentication if it&#8217;s a preview with the <code>getAuthClient<\/code> function, otherwise it initializes a regular client with <code>getClient<\/code>. If the client can&#8217;t be initialized, it returns a <code>&lt;PleaseLogin \/&gt; <\/code>component to prompt the user to log in.<\/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\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Page<\/span>(<span class=\"hljs-params\">props<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> isPreview = hasPreviewProps(props);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> id = isPreview ? props.searchParams.p : props.params.slug;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">let<\/span> client = isPreview ? <span class=\"hljs-keyword\">await<\/span> getAuthClient() : <span class=\"hljs-keyword\">await<\/span> getClient();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (!client) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">PleaseLogin<\/span> \/&gt;<\/span><\/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\">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>Once that is done, we execute a WPGraphQL Query using the <code>client.query<\/code> from Apollo to fetch content by variables which are the <code>id<\/code> and the <code>idType<\/code>, and that differs based on whether it&#8217;s a preview or not. It asks for the title, content, and date of a content node.<\/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><span class=\"hljs-keyword\">const<\/span> { data } = await client.query({\n<\/span><\/span><span class='shcb-loc'><span>    query: gql`\n<\/span><\/span><span class='shcb-loc'><span>      query GetContentNode(\n<\/span><\/span><span class='shcb-loc'><span>        $id: ID!\n<\/span><\/span><span class='shcb-loc'><span>        $idType: ContentNodeIdTypeEnum!\n<\/span><\/span><span class='shcb-loc'><span>        $asPreview: Boolean!\n<\/span><\/span><span class='shcb-loc'><span>      ) {\n<\/span><\/span><span class='shcb-loc'><span>        contentNode(id: $id, idType: $idType, asPreview: $asPreview) {\n<\/span><\/span><span class='shcb-loc'><span>          ... on NodeWithTitle {\n<\/span><\/span><span class='shcb-loc'><span>            title\n<\/span><\/span><span class='shcb-loc'><span>          }\n<\/span><\/span><span class='shcb-loc'><span>          ... on NodeWithContentEditor {\n<\/span><\/span><span class='shcb-loc'><span>            content\n<\/span><\/span><span class='shcb-loc'><span>          }\n<\/span><\/span><span class='shcb-loc'><span>          date\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>    variables: {\n<\/span><\/span><span class='shcb-loc'><span>      id,\n<\/span><\/span><span class='shcb-loc'><span>      idType: isPreview ? <span class=\"hljs-string\">'DATABASE_ID'<\/span> : <span class=\"hljs-string\">'URI'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      asPreview: isPreview,\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-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>Finally, the component&nbsp;the main content of the page inside a <code>&lt;main&gt; <\/code>tag, using the data from the WPGraphQL query to populate the title and the content (using <code>dangerouslySetInnerHTML<\/code> for the content to render HTML).<\/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\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>{data?.contentNode?.title}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-attr\">dangerouslySetInnerHTML<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">__html:<\/span> <span class=\"hljs-attr\">data<\/span>?<span class=\"hljs-attr\">.contentNode<\/span>?<span class=\"hljs-attr\">.content<\/span> ?? '' }}<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      \/&gt;<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  );<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><\/span><\/span><\/span><\/span><\/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\">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 setup allows the page to dynamically render the correct version of a post or page based on whether the user is requesting a preview or the live content.<\/p>\n\n\n\n<p>Stoked!!!! Let&#8217;s see this work in action!<\/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\/2024\/03\/GIF-for-PP-App-Router.gif\" alt=\"\" class=\"wp-image-31513\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Faust.js with support for the App Router is a new version of the most used headless WP meta framework on top of Next.js 14. It introduces new ways to handle data, create routes and files as well as rendering methods.&nbsp; Mix it with headless WordPress and WPGraphQL with Post Previews and you have an easy entry into its core functionality. &nbsp; We hope you have a better understanding of how it all works together.<\/p>\n\n\n\n<p>As always, stoked to hear your feedback and any questions you might have on headless WordPress! Hit us up in our <a href=\"https:\/\/discord.com\/invite\/J2khkF9XYK\">Discord<\/a>!<\/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-31512","post","type-post","status-publish","format-standard","hentry","category-headless"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Post Previews in the App Router with Faust.js for headless WordPress - Builders<\/title>\n<meta name=\"description\" content=\"Discover how Faust.js supports headless WordPress with Next.js 14 and the app router, enabling seamless post previews. Perfect for developers, this in-depth guide demystifies the integration process, providing the know-how to elevate your headless CMS experience.\" \/>\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-the-app-router-with-faust-js\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Post Previews with Faust.js&#039;s App Router support for headless WordPress\" \/>\n<meta property=\"og:description\" content=\"Stoked for my latest article where we dive into post previews in headless WordPress using Faust.js. Learn how the App Router support in Faust.js makes Post Previews work in headless.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/\" \/>\n<meta property=\"og:site_name\" content=\"Builders\" \/>\n<meta property=\"article:published_time\" content=\"2024-03-15T16:10:53+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-03-19T20:17:58+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/03\/WPGraphQL-Office-Hours-3.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1600\" \/>\n\t<meta property=\"og:image:height\" content=\"640\" \/>\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=\"8 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-the-app-router-with-faust-js\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-the-app-router-with-faust-js\\\/\"},\"author\":{\"name\":\"Francis Agulto\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/person\\\/bcdcb4ac0b215c34b6b30e440a24dc54\"},\"headline\":\"Post Previews in the App Router with Faust.js for headless WordPress\",\"datePublished\":\"2024-03-15T16:10:53+00:00\",\"dateModified\":\"2024-03-19T20:17:58+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-the-app-router-with-faust-js\\\/\"},\"wordCount\":1586,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-the-app-router-with-faust-js\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/lh7-us.googleusercontent.com\\\/uS50EXkewkPXoaMWqB74cymBUi9hyrlrL5Hg-TPwHD-A0ls_W2VwPPa_iXSUZHPRyYL4lAVzpbBBLlu4bWjiHjOPxQZ0Tqb1AAKYtmRmOlzHCkb8uWZsvDeuOmt1mpfG68H2E3CKdWPeuDVR53VXRhQ\",\"articleSection\":[\"Headless\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-the-app-router-with-faust-js\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-the-app-router-with-faust-js\\\/\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-the-app-router-with-faust-js\\\/\",\"name\":\"Post Previews in the App Router with Faust.js for headless WordPress - Builders\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-the-app-router-with-faust-js\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-the-app-router-with-faust-js\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/lh7-us.googleusercontent.com\\\/uS50EXkewkPXoaMWqB74cymBUi9hyrlrL5Hg-TPwHD-A0ls_W2VwPPa_iXSUZHPRyYL4lAVzpbBBLlu4bWjiHjOPxQZ0Tqb1AAKYtmRmOlzHCkb8uWZsvDeuOmt1mpfG68H2E3CKdWPeuDVR53VXRhQ\",\"datePublished\":\"2024-03-15T16:10:53+00:00\",\"dateModified\":\"2024-03-19T20:17:58+00:00\",\"description\":\"Discover how Faust.js supports headless WordPress with Next.js 14 and the app router, enabling seamless post previews. Perfect for developers, this in-depth guide demystifies the integration process, providing the know-how to elevate your headless CMS experience.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-the-app-router-with-faust-js\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-the-app-router-with-faust-js\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-the-app-router-with-faust-js\\\/#primaryimage\",\"url\":\"https:\\\/\\\/lh7-us.googleusercontent.com\\\/uS50EXkewkPXoaMWqB74cymBUi9hyrlrL5Hg-TPwHD-A0ls_W2VwPPa_iXSUZHPRyYL4lAVzpbBBLlu4bWjiHjOPxQZ0Tqb1AAKYtmRmOlzHCkb8uWZsvDeuOmt1mpfG68H2E3CKdWPeuDVR53VXRhQ\",\"contentUrl\":\"https:\\\/\\\/lh7-us.googleusercontent.com\\\/uS50EXkewkPXoaMWqB74cymBUi9hyrlrL5Hg-TPwHD-A0ls_W2VwPPa_iXSUZHPRyYL4lAVzpbBBLlu4bWjiHjOPxQZ0Tqb1AAKYtmRmOlzHCkb8uWZsvDeuOmt1mpfG68H2E3CKdWPeuDVR53VXRhQ\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/post-previews-in-the-app-router-with-faust-js\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Post Previews in the App Router with Faust.js for headless WordPress\"}]},{\"@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 the App Router with Faust.js for headless WordPress - Builders","description":"Discover how Faust.js supports headless WordPress with Next.js 14 and the app router, enabling seamless post previews. Perfect for developers, this in-depth guide demystifies the integration process, providing the know-how to elevate your headless CMS experience.","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-the-app-router-with-faust-js\/","og_locale":"en_US","og_type":"article","og_title":"Post Previews with Faust.js's App Router support for headless WordPress","og_description":"Stoked for my latest article where we dive into post previews in headless WordPress using Faust.js. Learn how the App Router support in Faust.js makes Post Previews work in headless.","og_url":"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/","og_site_name":"Builders","article_published_time":"2024-03-15T16:10:53+00:00","article_modified_time":"2024-03-19T20:17:58+00:00","og_image":[{"width":1600,"height":640,"url":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/03\/WPGraphQL-Office-Hours-3.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":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/#article","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/"},"author":{"name":"Francis Agulto","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/person\/bcdcb4ac0b215c34b6b30e440a24dc54"},"headline":"Post Previews in the App Router with Faust.js for headless WordPress","datePublished":"2024-03-15T16:10:53+00:00","dateModified":"2024-03-19T20:17:58+00:00","mainEntityOfPage":{"@id":"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/"},"wordCount":1586,"commentCount":0,"publisher":{"@id":"https:\/\/wpengine.com\/builders\/#organization"},"image":{"@id":"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/#primaryimage"},"thumbnailUrl":"https:\/\/lh7-us.googleusercontent.com\/uS50EXkewkPXoaMWqB74cymBUi9hyrlrL5Hg-TPwHD-A0ls_W2VwPPa_iXSUZHPRyYL4lAVzpbBBLlu4bWjiHjOPxQZ0Tqb1AAKYtmRmOlzHCkb8uWZsvDeuOmt1mpfG68H2E3CKdWPeuDVR53VXRhQ","articleSection":["Headless"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/","url":"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/","name":"Post Previews in the App Router with Faust.js for headless WordPress - Builders","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/#website"},"primaryImageOfPage":{"@id":"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/#primaryimage"},"image":{"@id":"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/#primaryimage"},"thumbnailUrl":"https:\/\/lh7-us.googleusercontent.com\/uS50EXkewkPXoaMWqB74cymBUi9hyrlrL5Hg-TPwHD-A0ls_W2VwPPa_iXSUZHPRyYL4lAVzpbBBLlu4bWjiHjOPxQZ0Tqb1AAKYtmRmOlzHCkb8uWZsvDeuOmt1mpfG68H2E3CKdWPeuDVR53VXRhQ","datePublished":"2024-03-15T16:10:53+00:00","dateModified":"2024-03-19T20:17:58+00:00","description":"Discover how Faust.js supports headless WordPress with Next.js 14 and the app router, enabling seamless post previews. Perfect for developers, this in-depth guide demystifies the integration process, providing the know-how to elevate your headless CMS experience.","breadcrumb":{"@id":"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/#primaryimage","url":"https:\/\/lh7-us.googleusercontent.com\/uS50EXkewkPXoaMWqB74cymBUi9hyrlrL5Hg-TPwHD-A0ls_W2VwPPa_iXSUZHPRyYL4lAVzpbBBLlu4bWjiHjOPxQZ0Tqb1AAKYtmRmOlzHCkb8uWZsvDeuOmt1mpfG68H2E3CKdWPeuDVR53VXRhQ","contentUrl":"https:\/\/lh7-us.googleusercontent.com\/uS50EXkewkPXoaMWqB74cymBUi9hyrlrL5Hg-TPwHD-A0ls_W2VwPPa_iXSUZHPRyYL4lAVzpbBBLlu4bWjiHjOPxQZ0Tqb1AAKYtmRmOlzHCkb8uWZsvDeuOmt1mpfG68H2E3CKdWPeuDVR53VXRhQ"},{"@type":"BreadcrumbList","@id":"https:\/\/wpengine.com\/builders\/post-previews-in-the-app-router-with-faust-js\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/wpengine.com\/builders\/"},{"@type":"ListItem","position":2,"name":"Post Previews in the App Router with Faust.js for headless WordPress"}]},{"@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\/31512","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=31512"}],"version-history":[{"count":0,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts\/31512\/revisions"}],"wp:attachment":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/media?parent=31512"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/categories?post=31512"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/tags?post=31512"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}