Working with Remix, GraphQL, and Hygraph
Remix aims to encourage you to embrace natural progressive enhance with React, without having to make the tough decisions on whether something should be static, or server rendered, Remix "just works" where you want it to.
You have probably seen the news on the release of the anticipated Remix. Remix is a new framework focused on modern UX, and relies on tried and tested web fundamentals.
What does that mean? Remix aims to encourage you to embrace natural progressive enhancement with React, without having to make the tough decisions on whether something should be static, or server rendered, Remix "just works" where you want it to.
We've been moving our frontend to the CDN for a few years now, and in some cases, waiting several hours for pages to be statically built too.
One of the newer approaches in static site generation is the idea of building pages on the fly, and caching them for some time, handling the regeneration at the edge. Some companies build their most popular landing pages ahead of time, while leaving the less popular ones to render on demand, cache, and revalidate (SWR).
The infrastructure behind server rendering traditional websites didn't go away while everyone was migrating to Static Site Generators either. If you've been working in the backend or serverless space, you've no doubt seen the breakthrough with services like Cloudflare Workers and Fastly Compute@Edge.
Remix does a lot of fancy stuff on the server for processing parallel requests, sending your complete page over the wire in one go, without the extra DOM bloat. If you've worked with frameworks like Laravel or Ruby on Rails, you will probably feel right at home.
Let's dive into how you can get started fetching from Hygraph inside your Remix app.
Remix, Run...Anchor
I'll imagine you're ahead of me with your Remix application running already. If not, follow the official docs on getting started.
We'll be using the following Hygraph project endpoint through the post, but you're welcome to follow along using your own. You'll need to provide an Authorization header with your API token if it's not public.
https://api-eu-central-1.hygraph.com/v2/ck8sn5tnf01gc01z89dbc7s0o/master
Step 1: Let's get Set UpAnchor
We'll be using the Remix loaders to fetch content for our pages. We could use the built in Fetch API, but let's use the package graphql-request
that is a thin abstraction so we can set a client, pass variables, and catch any errors for us.
We'll also need to install the peer dependency graphql
:
npm install graphql-request graphql
Step 2: Create an Index RouteAnchor
Inside of the app/routes
folder, you'll want to create the index file. Let's call it index.jsx
. If you're using TypeScript, it'll be .tsx
. The sx
extension is important!
Inside app/routes/index.jsx
add import the hook useLoaderData
, json
, and Link
:
import { useLoaderData, json, Link } from "remix";import { GraphQLClient, gql } from "graphql-request";
We'll now create a "Loader". This is core to Remix, and we can use it to fetch data from Hygraph using the GraphQLClient on the server.
const GetProductsQuery = gql`{products {slugname}}`;export let loader = async () => {const hygraph = new GraphQLClient("https://api-eu-central-1.hygraph.com/v2/ck8sn5tnf01gc01z89dbc7s0o/master");const { products } = await hygraph.request(GetProductsQuery);return json({ products });};
Now all that's left to do is use export default
to return our route component. For the sake of this post, we'll list all products from our query on the page, and Link
to them:
export default function Index() {let data = useLoaderData();return (<ul>{data.products.map(({ slug, name }) => (<li key={slug}><Link to={`/products/${slug}`} prefetch="intent"><a>{name}</a></Link></li>))}</ul>);}
Step 3: Product Routes by Slug ParamsAnchor
Another typical use case when building a site is the ability to create pages with data from a database somewhere. This isn't a new concept, and thankfully with Hygraph, we can fetch those pages from the CMS.
In our example, we'll fetch a Product by slug
that matches our page route.
We can use GraphQL variables to pass the slug of the current page to the query so we only fetch a single product content entry from Hygraph. The query looks like this:
const GetProductBySlug = gql`query ProductPageQuery($slug: String!) {product(where: { slug: $slug }) {namedescriptionprice}}`;
Now, inside of the app/routes
folder, create the folder/file products/$slug.jsx
and add the following, along with the query, and loader for the page:
import { useLoaderData, json } from "remix";import { GraphQLClient, gql } from "graphql-request";const GetProductBySlug = gql`query ProductPageQuery($slug: String!) {product(where: { slug: $slug }) {namedescriptionprice}}`;export let loader = async ({ params }) => {const { slug } = params;const hygraph = new GraphQLClient("https://api-eu-central-1.hygraph.com/v2/ck8sn5tnf01gc01z89dbc7s0o/master");const { product } = await hygraph.request(GetProductBySlug, {slug,});return json({ product });};
The loader here is doing similar to what we did on the index page; however, this time we're fetching params
from the loader arguments, and from that we can access slug
(it's slug
because we named the file $slug
.
Now, you just need to invoke the useLoaderData
hook and create your page:
export default function ProductPage() {let data = useLoaderData();return (<><h1>{data.product.name}</h1><p>{data.product.description}</p><p>{data.product.price / 100}</p></>);}