{"id":454,"date":"2022-06-24T16:57:13","date_gmt":"2022-06-24T22:57:13","guid":{"rendered":"https:\/\/developers.wpengine.com\/blog\/?p=454"},"modified":"2023-06-15T13:52:31","modified_gmt":"2023-06-15T19:52:31","slug":"headless-wordpress-remix-tailwind-css","status":"publish","type":"post","link":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/","title":{"rendered":"Headless WordPress with Remix and Tailwind CSS"},"content":{"rendered":"\n<p>One of the benefits of headless WordPress architecture is that it allows us total flexibility in how we want to develop our frontend website. Given this flexibility, it allows headless developers to experiment with new and emerging technologies easily. In this post, we&#8217;ll take a look at a newer framework in the JavaScript world and build a simple headless WordPress site using Remix. By the end of this post, you should be able to do the following things:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Create a new Remix project from scratch, or customize this starter, and hook it up to WordPress using Apollo Client<\/li>\n\n\n\n<li>Implement <code>loader<\/code> functions in Remix to query data<\/li>\n\n\n\n<li>Create dynamic routes in Remix to display individual posts by post <code>uri<\/code><\/li>\n\n\n\n<li>Implement different <code>Link<\/code> prefetching strategies based on your use-case<\/li>\n\n\n\n<li>Understand the tradeoffs of using Tailwind CSS to style block editor content<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Headless WordPress with Remix and Tailwind CSS\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/4jT7iKdqoW4?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Getting Started with the Remix WordPress Starter<\/h2>\n\n\n\n<p>To get us started, I&#8217;ve created <a href=\"https:\/\/github.com\/JEverhart383\/remixing-wordpress\">a pretty basic headless WordPress site using the latest version of Remix<\/a>, all of the needed dependencies, and some styling with Tailwind CSS. If you run the following commands in your terminal, you should end up with a fully running site in your browser that is pulling data from a remote WordPress site.<\/p>\n\n\n<pre class=\"wp-block-code language-bash\" aria-describedby=\"shcb-language-1\" 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>git <span class=\"hljs-keyword\">clone<\/span> https:<span class=\"hljs-comment\">\/\/github.com\/JEverhart383\/remixing-wordpress.git<\/span>\n<\/span><\/span><span class='shcb-loc'><span>cd remixing-wordpress\n<\/span><\/span><span class='shcb-loc'><span>npm install\n<\/span><\/span><span class='shcb-loc'><span>npm run dev\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\">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>The rest of this article will walk through some of the different parts of this codebase to explain how this Remix starter is created and discuss specific aspects of Remix that developers coming from other frameworks may want to be aware of. <\/p>\n\n\n\n<p>As prerequisites, you should be familiar with React, WPGraphQL, and ideally one meta-framework like Next.js, Nuxt, Svelete, or Gatsby. For refreshers on those foundational topics, you can reference our <a href=\"https:\/\/developers.wpengine.com\/roadmap\">Headless WordPress Developer Roadmap<\/a>. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Loading Data in the Index Page<\/h2>\n\n\n\n<p>Like other full stack frameworks, Remix uses a page-based routing system, which means that we can access the code that renders our index route in the <code>\/app\/routes\/index.jsx<\/code> file. This file exports a default function named <code>Index<\/code> that returns a React component that represents this route:<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" 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> Post <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/components\/Post\"<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> Header <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/components\/Header\"<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { useLoaderData } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@remix-run\/react\"<\/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> { client }  <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/lib\/apollo\"<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">loader<\/span> (<span class=\"hljs-params\"><\/span>)<\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> PostsQuery = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        query GetPosts {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">            posts {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">                nodes {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">                    title<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">                    content<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">                    date<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">                    slug<\/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-string\">    `<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> client.query({\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">query<\/span>: PostsQuery\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> posts = response?.data?.posts?.nodes\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> posts\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Index<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> posts = useLoaderData();\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\">div<\/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\">Header<\/span> <span class=\"hljs-attr\">title<\/span>=<span class=\"hljs-string\">\"Home Page\"<\/span> &gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Header<\/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 class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"grid gap-8 grid-cols-1 lg:grid-cols-3 p-6\"<\/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\">          {posts.map(post =&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\">              return (<\/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-tag\">&lt;<span class=\"hljs-name\">Post<\/span> <span class=\"hljs-attr\">post<\/span>=<span class=\"hljs-string\">{post}<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{post.title}<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Post<\/span>&gt;<\/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-tag\"><span class=\"hljs-tag\">              )<\/span><\/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-tag\"><span class=\"hljs-tag\">            })}<\/span><\/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-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  );<\/span><\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span><\/span><\/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>In this case our <code>Index<\/code> function creates a grid of our site&#8217;s posts by mapping over them and creating individual <code>Post<\/code> components. In addition to exporting the route component as a default, we also export an async function named <code>loader<\/code>, which is Remix&#8217;s convention for loading data into components. <\/p>\n\n\n\n<p>When this route is requested by the browser, the Remix framework runs the code inside of the <code>loader<\/code> function on the server, and then the component can access that data inside of the component using the <code>useLoaderData<\/code> hook. In our example above, we import a pre-configured instance of <code>ApolloClient<\/code> and execute a GraphQL query, returning that data inside the <code>loader<\/code> function. <\/p>\n\n\n\n<p>To change the example GraphQL origin to your own WordPress site. You can change the <code>url<\/code> property in the <code>\/app\/lib\/apollo.js<\/code> file:<\/p>\n\n\n<pre class=\"wp-block-code language-js\" 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\">import<\/span> { ApolloClient, InMemoryCache} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@apollo\/client\"<\/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\">const<\/span> client = <span class=\"hljs-keyword\">new<\/span> ApolloClient({\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">uri<\/span>: <span class=\"hljs-string\">'Swap your URL here'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">cache<\/span>: <span class=\"hljs-keyword\">new<\/span> InMemoryCache()\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<h3 class=\"wp-block-heading\">How Does Remix Standout on Data Loading<\/h3>\n\n\n\n<p>When looking at something like Next.js in comparison, there are a number of different ways to load data into page components for static or server-rendered pages, like <code>getStaticProps<\/code>, but also methods for having components request their own data from your GraphQL origin on the client using Apollo&#8217;s <code>useQuery<\/code> hook for example. <\/p>\n\n\n\n<p>While there are a lot of <a href=\"https:\/\/wpengine.com\/builders\/rendering-patterns-in-headless-wordpress\">different rendering methods<\/a> available to modern developers, splitting the responsibility of data fetching across multiple different domains (the client and the server) adds to the API surface area that developers contend with as they develop applications and can lead to additional challenges.  <\/p>\n\n\n\n<p>In particular, the Remix team talks about credential management being improved by loading all data on the server. Since the client never makes requests to sensitive origins on its own, we don&#8217;t need to worry about exposing API keys or proxying request manually since Remix already acts as our proxy. Personally, I&#8217;m a huge fan of this selling point as someone who&#8217;d developed a lot of SPAs.<\/p>\n\n\n\n<p>In addition, client-side fetching can sometimes fall victim to a waterfall of network requests, where a parent might fetch its own data, but then pass data into a child component that triggers its own data requests based on the data passed in by the parent component. <\/p>\n\n\n\n<p>Because there is a centralized mechanism for loading data into components in Remix using <code>loader<\/code> functions, it is easier to coordinate and parallelize some of those network requests to avoid this waterfall loading pattern.<\/p>\n\n\n\n<p>As a developer, I tend to appreciate opinionated software more than software that provides a lot of different approaches. Having the opportunity to evaluate a lot of different JavaScript frameworks as a part of my job, I can personally understand the issues Remix is trying to solve here. But it&#8217;s also a divergence from a common practice in the JavaScript world and thus not for everyone or every situation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prefetching with Link Components in Remix<\/h2>\n\n\n\n<p>Now that we have a good understanding of how data loading works in Remix, let&#8217;s take a look at how the framework implements prefetching with <code>Link<\/code> components. <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Link_prefetching_FAQ\">Link prefetching<\/a> isn&#8217;t unique to Remix, as its a part of the browser platform and also used by Next.js, but in some ways it works in tandem to help eliminate any negative impacts of loading data on the server. <\/p>\n\n\n\n<p>Let&#8217;s take a look at the <code>Post<\/code> component in <code>\/app\/components\/Post.jsx<\/code> to examine this in more detail:<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" 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\">import<\/span> { Link } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@remix-run\/react\"<\/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-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Post<\/span>(<span class=\"hljs-params\">{post}<\/span>)<\/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\">Link<\/span> <span class=\"hljs-attr\">prefetch<\/span>=<span class=\"hljs-string\">\"intent\"<\/span> <span class=\"hljs-attr\">to<\/span>=<span class=\"hljs-string\">{<\/span>`\/<span class=\"hljs-attr\">posts<\/span>\/${<span class=\"hljs-attr\">post.slug<\/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\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex <\/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-string\">            items-center <\/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-string\">            bg-gradient-to-r<\/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-string\">            from-cyan-500 <\/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-string\">            to-blue-500 <\/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-string\">            p-8 <\/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-string\">            rounded-lg <\/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-string\">            text-white <\/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-string\">            transition-all <\/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-string\">            hover:-translate-y-1 <\/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-string\">            hover:scale-105\"<\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            &gt;<\/span><\/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\">div<\/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\">&lt;<span class=\"hljs-name\">h2<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"font-semibold text-2xl\"<\/span>&gt;<\/span>{post.title}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/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-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>{ new Date(post.date).toLocaleDateString() }<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/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-tag\"><span class=\"hljs-tag\">                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span><\/span><\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><\/span><\/span><\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    )<\/span><\/span><\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/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>In this code snippet, we are returning the JSX element that represents our post card inside of the post grid on our index page. This card is wrapped in a <code>Link<\/code> component provided by the Remix framework, and here we provide it both a dynamic navigation path via the <code>to<\/code> prop as well as a <code>prefetch=\"intent\"<\/code> attribute. It is this feature of the <a href=\"https:\/\/remix.run\/docs\/en\/v1\/api\/remix#link\">Link component in Remix<\/a> that allows us to enable prefetching on user intent, which occurs when a user interacts with a <code>Link<\/code> component via hover or focus, or on render, which prefetches all available routes on the page. <\/p>\n\n\n\n<p>As you can see in the example GIF below, as we hover over the <code>Link<\/code> components, Remix loads all of the resources (JS, CSS, and data) associated with each route so that the routes are available immediately when the user clicks on them.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2022\/06\/remix-link-prefetch-1.gif\" alt=\"In Remix link prefetching on intent will load route resources when a user hovers or focuses the link\" class=\"wp-image-512\"\/><\/figure>\n\n\n\n<p>While Remix isn&#8217;t the first or only framework to implement prefetching, it pairs very nicely with the server-loaded data in my opinion. Given that Remix can load my application&#8217;s data in the background, I&#8217;m less inclined to stress about the amount of network requests it takes to get that data since it&#8217;s still immediately available for my users.<\/p>\n\n\n\n<p>When examining <code>Link<\/code> components using the <code>prefetch=\"render\"<\/code>attribute, we see that there is the potential of much more network activity using this method. As soon as those components have rendered, they will request additional data from the server, which can be great if you want to prime certain high priority routes.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2022\/06\/remix-link-prefetch-render-1.gif\" alt=\"In Remix link prefetching on render will load route resources a link element is rendered\" class=\"wp-image-514\"\/><\/figure>\n\n\n\n<p>When compared to <a href=\"https:\/\/nextjs.org\/docs\/api-reference\/next\/link\">the prefetching available with Next.js<\/a>, Remix doesn&#8217;t necessarily bring anything controversial to the table but does adopt a a pattern of defaults for prefetching that is different from Next.js. Frankly, I&#8217;m glad to see more frameworks give this technique consideration, as it can drastically improve the user experience during site navigation. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Dynamic Routing in Remix <\/h2>\n\n\n\n<p>Now that we&#8217;ve looked at how to render our <code>Index<\/code> page and use <code>Link<\/code> components to prefetch the data for our posts, let&#8217;s look at how Remix&#8217;s router makes that possible using dynamic routes.<\/p>\n\n\n\n<p>To construct dynamic page-based routes in Remix, we can add additional folders and files to our <code>routes<\/code> folder.  In this case, we create a file called <code>$slug.jsx<\/code> inside of the <code>\/routes\/posts<\/code> directory, which creates a dynamic route segment that responds to browser requests for URLs like <code>\/posts\/[post-slug-here]<\/code>. When we do this, Remix makes <code>$slug<\/code> available as a variable inside of our loader function so we can use it to request data. We can see that played out in our page component for that route: <\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" 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\">import<\/span> Header <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/components\/Header\"<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { useLoaderData } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@remix-run\/react\"<\/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> { client }  <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/lib\/apollo\"<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">loader<\/span> (<span class=\"hljs-params\">{params}<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> slug = params.slug\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> PostQuery = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    query GetPostBySlug($id: ID!) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        post(id: $id, idType: SLUG) {<\/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\">            content<\/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\">            slug<\/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>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> client.query({\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">query<\/span>: PostQuery,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">variables<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">id<\/span>: slug\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\">const<\/span> post = response?.data?.post\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> post\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Index<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> post = useLoaderData()\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\">div<\/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\">Header<\/span> <span class=\"hljs-attr\">title<\/span>=<span class=\"hljs-string\">\"Home Page\"<\/span> &gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Header<\/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\">main<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-gray-100 container mx-auto mt-6 p-6 rounded-lg\"<\/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 class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-4xl\"<\/span>&gt;<\/span>{post.title}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/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-tag\"><span class=\"hljs-tag\">            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-2xl mt-4\"<\/span>&gt;<\/span>{new Date(post.date).toLocaleDateString()}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">article<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mt-4 space-y-2\"<\/span> <span class=\"hljs-attr\">dangerouslySetInnerHTML<\/span>=<span class=\"hljs-string\">{{__html:<\/span> <span class=\"hljs-attr\">post.content<\/span>}}&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">article<\/span>&gt;<\/span><\/span><\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><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><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  );<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/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>The <code>loader<\/code> function of our route components gives us access to any <a href=\"https:\/\/remix.run\/docs\/en\/v1\/api\/conventions#loader-params\">route params<\/a> passed in by the user, but it can also access additional details actual <a href=\"https:\/\/remix.run\/docs\/en\/v1\/api\/conventions#loader-request\">HTTP request <\/a>as well:<\/p>\n\n\n<pre class=\"wp-block-code language-js\" 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\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">loader<\/span> (<span class=\"hljs-params\">{params, request}<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> slug = params.slug\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> url = <span class=\"hljs-keyword\">new<\/span> URL(request.url);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> search = url.searchParams.get(<span class=\"hljs-string\">\"search\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ Do stuff with slug and search here<\/span>\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>In our example above, we then use the value of <code>params.slug<\/code> to populate a GraphQL query for our specific WordPress post: <\/p>\n\n\n<pre class=\"wp-block-code language-js\" aria-describedby=\"shcb-language-7\" 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\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">loader<\/span> (<span class=\"hljs-params\">{params}<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> slug = params.slug\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> PostQuery = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    query GetPostBySlug($id: ID!) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        post(id: $id, idType: SLUG) {<\/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\">            content<\/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\">            slug<\/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>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> client.query({\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">query<\/span>: PostQuery,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">variables<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">id<\/span>: slug\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\">const<\/span> post = response?.data?.post\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> post\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">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>It&#8217;s also worth noting at this point, that the data fetching code that runs inside of the <code>loader<\/code> function can be very flexible for the developer. It&#8217;s up to you in how to create and organize your data layer. In previous versions of this tutorial, I extracted all of the logic you see above into a single function in a separate file, so my <code>loader<\/code> looked like this with one additional import statement:<\/p>\n\n\n<pre class=\"wp-block-code language-js\" aria-describedby=\"shcb-language-8\" 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> { getPostBySlug } <span class=\"hljs-keyword\">from<\/span> ..\/..\/lib\/WordPressService\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">loader<\/span> (<span class=\"hljs-params\">{params}<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> slug = params.slug\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">await<\/span> getPostBySlug(slug)\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><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 gives developers a ton of flexibility in determining the organizational practices for their application. If you&#8217;re a fan of colocating data fetching code in your route modules, you can do that, but if you prefer a different pattern Remix gives you the flexibility to do that as well. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Supporting Tailwind CSS<\/h2>\n\n\n\n<p>A common question that comes up on our <a href=\"https:\/\/developers.wpengine.com\/discord\">Headless WordPress Discord<\/a> server is how to use Tailwind CSS and X framework to develop WordPress sites. For this article, I wanted to look at that specifically to evaluate how Remix works with Tailwind CSS, but also so that I could explore a CSS framework I have little experience with. <\/p>\n\n\n\n<p>Luckily, both the <a href=\"https:\/\/remix.run\/docs\/en\/v1\/guides\/styling#tailwind-css\">Remix docs<\/a> and the <a href=\"https:\/\/tailwindcss.com\/docs\/guides\/remix\">Tailwind CSS docs<\/a> have guides on configuring the tools to work together, and I was able to get Tailwind support running very quickly. <\/p>\n\n\n\n<p>Since this article isn&#8217;t about Tailwind CSS per se, I&#8217;m going to focus on the important aspects of this framework for headless WordPress developers specifically. Tailwind CSS is a utility-first framework, which means that you essentially add utility classes to your HTML elements to style them. Our <code>Post<\/code> component is an example of this: <\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">prefetch<\/span>=<span class=\"hljs-string\">\"render\"<\/span> <span class=\"hljs-attr\">to<\/span>=<span class=\"hljs-string\">{<\/span>`\/<span class=\"hljs-attr\">posts<\/span>\/${<span class=\"hljs-attr\">post.slug<\/span>}`}&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex <\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\"><span class=\"hljs-string\">            items-center <\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\"><span class=\"hljs-string\">            bg-gradient-to-r<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\"><span class=\"hljs-string\">            from-cyan-500 <\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\"><span class=\"hljs-string\">            to-blue-500 <\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\"><span class=\"hljs-string\">            p-8 <\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\"><span class=\"hljs-string\">            rounded-lg <\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\"><span class=\"hljs-string\">            text-white <\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\"><span class=\"hljs-string\">            transition-all <\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\"><span class=\"hljs-string\">            hover:-translate-y-1 <\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\"><span class=\"hljs-string\">            hover:scale-105\"<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\">            &gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>                    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"font-semibold text-2xl\"<\/span>&gt;<\/span>{post.title}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>                    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>{ new Date(post.date).toLocaleDateString() }<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Using a series of CSS classes, we describe the display and styling of this element, but this involves adding classes to the actual HTML. While this pattern is really nice for creating components, it was a bit more challenging to think through how to style some of the block editor content that WordPress returns as a CMS. <\/p>\n\n\n\n<p>If we look at the route component for our <code>$slug<\/code> page, the article component is using React&#8217;s <code><em>dangerouslySetInnerHTML<\/em><\/code> attribute to output all of the rendered markup that makes up the post content:<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-10\" 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-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Slug<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> post = useLoaderData()\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\">div<\/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\">Header<\/span> <span class=\"hljs-attr\">title<\/span>=<span class=\"hljs-string\">\"Home Page\"<\/span> &gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Header<\/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\">main<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-gray-100 container mx-auto mt-6 p-6 rounded-lg\"<\/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 class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-4xl\"<\/span>&gt;<\/span>{post.title}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/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-tag\"><span class=\"hljs-tag\">            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-2xl mt-4\"<\/span>&gt;<\/span>{new Date(post.date).toLocaleDateString()}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">article<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mt-4 space-y-2\"<\/span> <span class=\"hljs-attr\">dangerouslySetInnerHTML<\/span>=<span class=\"hljs-string\">{{__html:<\/span> <span class=\"hljs-attr\">post.content<\/span>}}&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">article<\/span>&gt;<\/span><\/span><\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><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><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  );<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/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-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><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>Because our WordPress editor content is essentially echoed out inside of the <code>article<\/code> element, we don&#8217;t have the opportunity to add Tailwind utility classes to that markup before or as it is rendered without doing some extra work. <\/p>\n\n\n\n<p>In other looks at <a href=\"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-render-blocks-as-html\">styling block editor markup<\/a>, we talk about importing WordPress npm packages to use native CSS, or even writing our own classes to match the markup. But both of those seemed at odds with the Tailwind philosophy of utility-first CSS.<\/p>\n\n\n\n<p>Tailwind would theoretically pair nicely if you are willing to either parse blocks from the HTML response, or use <a href=\"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\">the WPGraphQL Gutenberg extension<\/a> to create more formal components. This pattern would be very powerful, but does require another level of commitment. <\/p>\n\n\n\n<p>Tailwind CSS also has its <a href=\"https:\/\/tailwindcss.com\/docs\/reusing-styles#extracting-classes-with-apply\">own method of extracting classes<\/a> using the <code>@apply<\/code> directive, which may give developers interested in exploring Tailwind with headless WordPress another avenue of targeting block editor markup. If I were to do a deeper project with Tailwind again, this is likely the avenue I would take. In theory, it would let us do something like this to style an image&#8217;s <code>figure<\/code> container:<\/p>\n\n\n<pre class=\"wp-block-code language-js\" aria-describedby=\"shcb-language-11\" 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>@layer components {\n<\/span><\/span><span class='shcb-loc'><span>  .wp-block-image {\n<\/span><\/span><span class='shcb-loc'><span>     @apply <span class=\"hljs-comment\">\/\/our tailwind utility classes here;<\/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-11\"><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>For the purposes of this demo, I was able to make some basic styling changes with Tailwind just by relying on the targeting the parent article element, but this exercise may include some trial and error: <\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">article<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mt-4 space-y-2\"<\/span> <span class=\"hljs-attr\">dangerouslySetInnerHTML<\/span>=<span class=\"hljs-string\">{{__html:<\/span> <span class=\"hljs-attr\">post.content<\/span>}}&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">article<\/span>&gt;<\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up! \ud83c\udf89<\/h2>\n\n\n\n<p>Thanks for exploring Remix, Tailwind CSS, and headless WordPress with me in this article. If you want an example of you can access the finished state of our example app on <a href=\"https:\/\/github.com\/JEverhart383\/remixing-wordpress\">this GitHub branch<\/a>.<\/p>\n\n\n\n<p>Looking for a place to host your headless WordPress project? Check out WP Engine\u2019s&nbsp;<a href=\"https:\/\/wpengine.com\/atlas\/\" target=\"_blank\" rel=\"noreferrer noopener\">Atlas platform<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>One of the benefits of headless WordPress architecture is that it allows us total flexibility in how we want to develop our frontend website. Given this flexibility, it allows headless [&hellip;]<\/p>\n","protected":false},"author":19,"featured_media":649,"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-454","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-headless"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Headless WordPress with Remix and Tailwind CSS - Builders<\/title>\n<meta name=\"description\" content=\"In this post, we explore using the Remix framework and Tailwind CSS with headless WordPress and touch on link prefetching and data fetching\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Headless WordPress with Remix and Tailwind CSS - Builders\" \/>\n<meta property=\"og:description\" content=\"In this post, we explore using the Remix framework and Tailwind CSS with headless WordPress and touch on link prefetching and data fetching\" \/>\n<meta property=\"og:url\" content=\"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/\" \/>\n<meta property=\"og:site_name\" content=\"Builders\" \/>\n<meta property=\"article:published_time\" content=\"2022-06-24T22:57:13+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-06-15T19:52:31+00:00\" \/>\n<meta name=\"author\" content=\"Jeff Everhart\" \/>\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=\"Jeff Everhart\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-remix-tailwind-css\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-remix-tailwind-css\\\/\"},\"author\":{\"name\":\"Jeff Everhart\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/person\\\/b5a7f380738d25f57b00a5aaacf3db52\"},\"headline\":\"Headless WordPress with Remix and Tailwind CSS\",\"datePublished\":\"2022-06-24T22:57:13+00:00\",\"dateModified\":\"2023-06-15T19:52:31+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-remix-tailwind-css\\\/\"},\"wordCount\":1990,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-remix-tailwind-css\\\/#primaryimage\"},\"thumbnailUrl\":\"\",\"articleSection\":[\"Headless\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-remix-tailwind-css\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-remix-tailwind-css\\\/\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-remix-tailwind-css\\\/\",\"name\":\"Headless WordPress with Remix and Tailwind CSS - Builders\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-remix-tailwind-css\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-remix-tailwind-css\\\/#primaryimage\"},\"thumbnailUrl\":\"\",\"datePublished\":\"2022-06-24T22:57:13+00:00\",\"dateModified\":\"2023-06-15T19:52:31+00:00\",\"description\":\"In this post, we explore using the Remix framework and Tailwind CSS with headless WordPress and touch on link prefetching and data fetching\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-remix-tailwind-css\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-remix-tailwind-css\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-remix-tailwind-css\\\/#primaryimage\",\"url\":\"\",\"contentUrl\":\"\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-remix-tailwind-css\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Headless WordPress with Remix and Tailwind CSS\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#website\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\",\"name\":\"Builders\",\"description\":\"Reimagining the way we build with WordPress.\",\"publisher\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\",\"name\":\"WP Engine\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/WP-Engine-Horizontal@2x.png\",\"contentUrl\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/WP-Engine-Horizontal@2x.png\",\"width\":348,\"height\":68,\"caption\":\"WP Engine\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/x.com\\\/wpebuilders\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UCh1WuL54XFb9ZI6m6goFv1g\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/person\\\/b5a7f380738d25f57b00a5aaacf3db52\",\"name\":\"Jeff Everhart\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/237d84b2a63acb8c427ba3246f9c5fc1b436aac578a5df421927c23b4fde45f5?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/237d84b2a63acb8c427ba3246f9c5fc1b436aac578a5df421927c23b4fde45f5?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/237d84b2a63acb8c427ba3246f9c5fc1b436aac578a5df421927c23b4fde45f5?s=96&d=mm&r=g\",\"caption\":\"Jeff Everhart\"},\"description\":\"Jeff is a Senior Developer Advocate at WP Engine, focusing on educational resources that blend JavaScript and WordPress to support the Atlas headless WordPress platform and ecosystem.\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/author\\\/jeff-everhartwpengine-com\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Headless WordPress with Remix and Tailwind CSS - Builders","description":"In this post, we explore using the Remix framework and Tailwind CSS with headless WordPress and touch on link prefetching and data fetching","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/","og_locale":"en_US","og_type":"article","og_title":"Headless WordPress with Remix and Tailwind CSS - Builders","og_description":"In this post, we explore using the Remix framework and Tailwind CSS with headless WordPress and touch on link prefetching and data fetching","og_url":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/","og_site_name":"Builders","article_published_time":"2022-06-24T22:57:13+00:00","article_modified_time":"2023-06-15T19:52:31+00:00","author":"Jeff Everhart","twitter_card":"summary_large_image","twitter_creator":"@wpebuilders","twitter_site":"@wpebuilders","twitter_misc":{"Written by":"Jeff Everhart","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/#article","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/"},"author":{"name":"Jeff Everhart","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/person\/b5a7f380738d25f57b00a5aaacf3db52"},"headline":"Headless WordPress with Remix and Tailwind CSS","datePublished":"2022-06-24T22:57:13+00:00","dateModified":"2023-06-15T19:52:31+00:00","mainEntityOfPage":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/"},"wordCount":1990,"commentCount":0,"publisher":{"@id":"https:\/\/wpengine.com\/builders\/#organization"},"image":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/#primaryimage"},"thumbnailUrl":"","articleSection":["Headless"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/","url":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/","name":"Headless WordPress with Remix and Tailwind CSS - Builders","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/#website"},"primaryImageOfPage":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/#primaryimage"},"image":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/#primaryimage"},"thumbnailUrl":"","datePublished":"2022-06-24T22:57:13+00:00","dateModified":"2023-06-15T19:52:31+00:00","description":"In this post, we explore using the Remix framework and Tailwind CSS with headless WordPress and touch on link prefetching and data fetching","breadcrumb":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/#primaryimage","url":"","contentUrl":""},{"@type":"BreadcrumbList","@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-remix-tailwind-css\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/wpengine.com\/builders\/"},{"@type":"ListItem","position":2,"name":"Headless WordPress with Remix and Tailwind CSS"}]},{"@type":"WebSite","@id":"https:\/\/wpengine.com\/builders\/#website","url":"https:\/\/wpengine.com\/builders\/","name":"Builders","description":"Reimagining the way we build with WordPress.","publisher":{"@id":"https:\/\/wpengine.com\/builders\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/wpengine.com\/builders\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/wpengine.com\/builders\/#organization","name":"WP Engine","url":"https:\/\/wpengine.com\/builders\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/logo\/image\/","url":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/05\/WP-Engine-Horizontal@2x.png","contentUrl":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/05\/WP-Engine-Horizontal@2x.png","width":348,"height":68,"caption":"WP Engine"},"image":{"@id":"https:\/\/wpengine.com\/builders\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/wpebuilders","https:\/\/www.youtube.com\/channel\/UCh1WuL54XFb9ZI6m6goFv1g"]},{"@type":"Person","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/person\/b5a7f380738d25f57b00a5aaacf3db52","name":"Jeff Everhart","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/237d84b2a63acb8c427ba3246f9c5fc1b436aac578a5df421927c23b4fde45f5?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/237d84b2a63acb8c427ba3246f9c5fc1b436aac578a5df421927c23b4fde45f5?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/237d84b2a63acb8c427ba3246f9c5fc1b436aac578a5df421927c23b4fde45f5?s=96&d=mm&r=g","caption":"Jeff Everhart"},"description":"Jeff is a Senior Developer Advocate at WP Engine, focusing on educational resources that blend JavaScript and WordPress to support the Atlas headless WordPress platform and ecosystem.","url":"https:\/\/wpengine.com\/builders\/author\/jeff-everhartwpengine-com\/"}]}},"_links":{"self":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts\/454","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/users\/19"}],"replies":[{"embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/comments?post=454"}],"version-history":[{"count":0,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts\/454\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/"}],"wp:attachment":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/media?parent=454"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/categories?post=454"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/tags?post=454"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}