Working with Remix, GraphQL, and GraphCMS

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.

Jamie Barton
Jamie Barton
Remix + GraphCMS

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 GraphCMS 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 GraphCMS 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.graphcms.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 GraphCMS using the GraphQLClient on the server.

const GetProductsQuery = gql`
{
products {
slug
name
}
}
`;
export let loader = async () => {
const graphcms = new GraphQLClient(
"https://api-eu-central-1.graphcms.com/v2/ck8sn5tnf01gc01z89dbc7s0o/master"
);
const { products } = await graphcms.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 GraphCMS, 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 GraphCMS. The query looks like this:

const GetProductBySlug = gql`
query ProductPageQuery($slug: String!) {
product(where: { slug: $slug }) {
name
description
price
}
}
`;

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 }) {
name
description
price
}
}
`;
export let loader = async ({ params }) => {
const { slug } = params;
const graphcms = new GraphQLClient(
"https://api-eu-central-1.graphcms.com/v2/ck8sn5tnf01gc01z89dbc7s0o/master"
);
const { product } = await graphcms.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>
</>
);
}

  • Jamie Barton
  • Developer Relations

    Jamie is a software engineer turned developer advocate. Born and bred in North East England, he loves learning and teaching others through video and written tutorials. Jamie maintains Build your DXP, Headless Commerce Resources, and GraphQL WTF.

Related articles

It's Easy To Get Started

GraphCMS plans are flexibly suited to accommodate your growth. Get started for free, or request a demo to discuss larger projects with more complex needs