Navigate back to the homepage

RedwoodJS with Sanity.io CMS as Backend

Aryan Jabbari
May 20th, 2020 · 2 min read

RedwoodJS with Sanity.io

This past weekend, I built a tiny application with RedwoodJS using Sanity.io as a CMS. I videostreamed myself building it for those of you who are interested.

What I Built

Sanity.io comes with a movie dataset out of the box. I kept it simple and built a MovieList page and a MovieDetail page. I was mostly focused on how I can get RedwoodJS to work with Sanity.io as a data source.

How to use Sanity.io with RedwoodJS

Create GraphQL Movie Object Type

After standing up my Sanity server (instructions on how to do that in the project README), I used Sanity Vision to get a feel for the movie data object. Since yarn rw g scaffold movie needs a Prisma model in schema.prisma (which I didn’t use for this small project), I kind of cheated. I made a temporary movie model in schema.prisma to leverage yarn rw g scaffold movie. Then, I went into movies.sdl.js and edited it a bit. I added the types I would render in the UI to movies.sdl.js so there are some data in the Sanity.io movie data object that went unaccounted for.

Create Movie Service

I had to edit the movie service to query from Sanity.io rather then the Prisma database. First, I created a Sanity client hooked up to my Sanity project:

1import sanityClient from '@sanity/client';
2
3export const sanity = sanityClient({
4 projectId: process.env.SENTRY_PROJECT_ID,
5 dataset: 'production',
6 useCdn: true,
7});

Then, I used this client in my movies service to fetch all movies and a movie by its slug:

1import { sanity } from '../../lib/sanity';
2
3const moviesQuery = /* groq */ `*[_type == "movie"]`;
4export const movies = () => {
5 return sanity.fetch(moviesQuery);
6};
7
8const movieBySlugQuery = /* groq */ `*[_type == "movie" && slug.current == $slug][0]`;
9export const movie = ({ slug }) => {
10 return sanity.fetch(movieBySlugQuery, { slug });
11};

Update the Movie Cells

Next, I updated the MoviesCell and the MovieCell with updated GraphQL queries and each movie in the MoviesCell linking to the MovieDetailsPage:

1// MoviesCell.js
2import { Link, routes } from '@redwoodjs/router';
3import { urlFor } from 'src/lib/sanity';
4
5export const QUERY = gql`
6 query {
7 movies {
8 poster {
9 asset {
10 _ref
11 }
12 }
13 slug {
14 current
15 }
16 title
17 }
18 }
19`;
20
21export const Loading = () => <div>Loading...</div>;
22
23export const Empty = () => <div>Empty</div>;
24
25export const Failure = ({ error }) => <div>Error: {error.message}</div>;
26
27export const Success = ({ movies }) => {
28 return movies.map((movie) => {
29 return (
30 <Link key={movie.slug.current} to={routes.movieDetail({ slug: movie.slug.current })}>
31 <img src={urlFor(movie.poster.asset).width(200).url()} />
32 </Link>
33 );
34 });
35};
1// MovieCell.js
2import { urlFor } from 'src/lib/sanity';
3
4export const QUERY = gql`
5 query($slug: String!) {
6 movie(slug: $slug) {
7 poster {
8 asset {
9 _ref
10 }
11 }
12 slug {
13 current
14 }
15 title
16 }
17 }
18`;
19
20export const Loading = () => <div>Loading...</div>;
21
22export const Empty = () => <div>Empty</div>;
23
24export const Failure = ({ error }) => <div>Error: {error.message}</div>;
25
26export const Success = ({ movie }) => {
27 return (
28 <div>
29 <h1>{movie.title}</h1>
30 <img src={urlFor(movie.poster.asset).width(500).url()} />
31 </div>
32 );
33};

From there, it was smooth sailing. I rendered MoviesCell and MovieCell in my MovieListPage and MovieDetailPage, respectively.

Notes on My Experience

  1. Unfortunately, my project does not build. I documented this here and I’d love some help in getting it deployed!

  2. At first, I mentally prepared myself to not use the API side of RedwoodJS at all. I expected to be able to use Sanity.io directly from the cells. However, much to my disappointment, cells are tightly coupled with the API side (at least that’s my understanding). The exported QUERY is run against the API side with the data being injected into the cell as props. I’m a bit worried that makes it impossible to leverage everything RedwoodJS has to offer without the API side (though, at the same time, maybe that’s the point of using an opinionated framework? 🤔).

    • What I secretly wish: What if, instead of a GraphQL query that is exported and run against the API side, there was an exported function that returns an object that is then injected to props? That way, instead of:
    1// MoviesCell.js
    2import { Link, routes } from '@redwoodjs/router';
    3import { urlFor } from 'src/lib/sanity';
    4
    5export const QUERY = gql`
    6 query {
    7 movies {
    8 poster {
    9 asset {
    10 _ref
    11 }
    12 }
    13 slug {
    14 current
    15 }
    16 title
    17 }
    18 }
    19`;
    20// Loading, Error and Empty removed for brevity
    21
    22export const Success = ({ movies }) => {
    23 return movies.map((movie) => {
    24 return (
    25 <Link key={movie.slug.current} to={routes.movieDetail({ slug: movie.slug.current })}>
    26 <img src={urlFor(movie.poster.asset).width(200).url()} />
    27 </Link>
    28 );
    29 });
    30};

    we have:

    1import { Link, routes } from '@redwoodjs/router';
    2import { request } from 'graphql-request';
    3import { urlFor } from 'src/lib/sanity';
    4
    5const QUERY = gql`
    6 query {
    7 movies {
    8 poster {
    9 asset {
    10 _ref
    11 }
    12 }
    13 slug {
    14 current
    15 }
    16 title
    17 }
    18 }
    19`;
    20
    21export const getter = () => {
    22 const data = request('/api', QUERY);
    23 return data;
    24};
    25
    26// Loading, Error and Empty removed for brevity
    27
    28export const Success = ({ movies }) => {
    29 return movies.map((movie) => {
    30 return (
    31 <Link key={movie.slug.current} to={routes.movieDetail({ slug: movie.slug.current })}>
    32 <img src={urlFor(movie.poster.asset).width(200).url()} />
    33 </Link>
    34 );
    35 });
    36};

    We’re able to run the same query we did before AND use data stemming from a different source instead of being tightly coupled to the API side.

  3. I ran into a problem where I needed my Sanity client on both the web and API sides. I wasn’t easily able to share code so I had to write the same code in both the /api and /web directories. 🤮 I’m excited to see what goes into the cookbook to solve this.

    • I remember using nx a bit for monorepos and they had a nx generate @nrwl/node:library <name> command that was quite nifty.
  4. I funnily found out the Redwood Router supports page redirects. There was nothing about it in the docs but I tried it and it just worked:

1<Route path="/" redirect="/movies" />

📧 Join my email list and get notified about new content

Stay updated with all my blog posts and future randomness!

More articles from The WebDev Coach

I Migrated Away from Apollo Client to Vercel SWR and Prisma graphql-request...and You Can Too!

I Migrated Away from Apollo Client to Vercel SWR and Prisma graphql-request...and You Can Too! GraphQL requests are simply POST requests…

May 13th, 2020 · 1 min read

Add Sentry to Vercel Next.js API Routes

Add Sentry to Vercel Next.js API Routes To add Sentry to Next.js, you can wrap your entire route in a try block and have the Sentry…

May 10th, 2020 · 1 min read
© 2018–2020 The WebDev Coach
Link to $https://twitter.com/aryanjabbariLink to $https://www.youtube.com/c/thewebdevcoachLink to $https://dev.to/aryanjnycLink to $https://github.com/AryanJ-NYCLink to $https://www.linkedin.com/in/aryanjabbariLink to $https://www.instagram.com/thewebdevcoach/