Tag: Sanity.io

How to Make Taxonomy Pages With Gatsby and Sanity.io

In this tutorial, we’ll cover how to make taxonomy pages with Gatsby with structured content from Sanity.io. You will learn how to use Gatsby’s Node creation APIs to add fields to your content types in Gatsby’s GraphQL API. Specifically, we’re going to create category pages for the Sanity’s blog starter.

That being said, there is nothing Sanity-specific about what we’re covering here. You’re able to do this regardless of which content source you may have. We’re just reaching for Sanity.io for the sake of demonstration.

Get up and running with the blog

If you want to follow this tutorial with your own Gatsby project, go ahead and skip to the section for creating a new page template in Gatsby. If not, head over to sanity.io/create and launch the Gatsby blog starter. It will put the code for Sanity Studio and the Gatsby front-end in your GitHub account and set up the deployment for both on Netlify. All the configuration, including example content, will be in place so that you can dive right into learning how to create taxonomy pages.

Once the project is iniated, make sure to clone the new repository on GitHub to local, and install the dependencies:

git clone git@github.com:username/your-repository-name.git cd your-repository-name npm i

If you want to run both Sanity Studio (the CMS) and the Gatsby front-end locally, you can do so by running the command npm run dev in a terminal from the project root. You can also cd into the web folder and just run Gatsby with the same command.

You should also install the Sanity CLI and log in to your account from the terminal: npm i -g @sanity/cli && sanity login. This will give you tooling and useful commands to interact with Sanity projects. You can add the --help flag to get more information on its functionality and commands.

We will be doing some customization to the gatsby-node.js file. To see the result of the changes, restart Gatsby’s development server. This is done in most systems by hitting CTRL + C in the terminal and running npm run dev again.

Getting familiar with the content model

Look into the /studio/schemas/documents folder. There are schema files for our main content types: author, category, site settings, and posts. Each of the files exports a JavaScript object that defines the fields and properties of these content types. Inside of post.js is the field definition for categories:

{   name: 'categories',   type: 'array',   title: 'Categories',   of: [     {       type: 'reference',       to: {         type: 'category'       }     }   ] },

This will create an array field with reference objects to category documents. Inside of the blog’s studio it will look like this:

An array field with references to category documents in the blog studio
An array field with references to category documents in the blog studio

Adding slugs to the category type

Head over to /studio/schemas/documents/category.js. There is a simple content model for a category that consists of a title and a description. Now that we’re creating dedicated pages for categories, it would be handy to have a slug field as well. We can define that in the schema like this:

// studio/schemas/documents/category.js export default {   name: 'category',   type: 'document',   title: 'Category',   fields: [     {       name: 'title',       type: 'string',       title: 'Title'     },     {       name: 'slug',       type: 'slug',       title: 'Slug',       options: {         // add a button to generate slug from the title field         source: 'title'       }     },     {       name: 'description',       type: 'text',       title: 'Description'     }   ] }

Now that we have changed the content model, we need to update the GraphQL schema definition as well. Do this by executing npm run graphql-deploy (alternatively: sanity graphql deploy) in the studio folder. You will get warnings about breaking changes, but since we are only adding a field, you can proceed without worry. If you want the field to accessible in your studio on Netlify, check the changes into git (with git add . && git commit -m"add slug field") and push it to your GitHub repository (git push origin master).

Now we should go through the categories and generate slugs for them. Remember to hit the publish button to make the changes accessible for Gatsby! And if you were running Gatsby’s development server, you’ll need to restart that too.

Quick sidenote on how the Sanity source plugin works

When starting Gatsby in development or building a website, the source plugin will first fetch the GraphQL Schema Definitions from Sanity deployed GraphQL API. The source plugin uses this to tell Gatsby which fields should be available to prevent it from breaking if the content for certain fields happens to disappear. Then it will hit the project’s export endpoint, which streams all the accessible documents to Gatsby’s in-memory datastore.

In order words, the whole site is built with two requests. Running the development server, will also set up a listener that pushes whatever changes come from Sanity to Gatsby in real-time, without doing additional API queries. If we give the source plugin a token with permission to read drafts, we’ll see the changes instantly. This can also be experienced with Gatsby Preview.

Adding a category page template in Gatsby

Now that we have the GraphQL schema definition and some content ready, we can dive into creating category page templates in Gatsby. We need to do two things:

  • Tell Gatsby to create pages for the category nodes (that is Gatsby’s term for “documents”).
  • Give Gatsby a template file to generate the HTML with the page data.

Begin by opening the /web/gatsby-node.js file. Code will already be here that can be used to create the blog post pages. We’ll largely leverage this exact code, but for categories. Let’s take it step-by-step:

Between the createBlogPostPages function and the line that starts with exports.createPages, we can add the following code. I’ve put in comments here to explain what’s going on:

// web/gatsby-node.js  // ...  async function createCategoryPages (graphql, actions) {   // Get Gatsby‘s method for creating new pages   const {createPage} = actions   // Query Gatsby‘s GraphAPI for all the categories that come from Sanity   // You can query this API on http://localhost:8000/___graphql   const result = await graphql(`{     allSanityCategory {       nodes {         slug {           current         }         id       }     }   }   `)   // If there are any errors in the query, cancel the build and tell us   if (result.errors) throw result.errors    // Let‘s gracefully handle if allSanityCatgogy is null   const categoryNodes = (result.data.allSanityCategory || {}).nodes || []    categoryNodes     // Loop through the category nodes, but don't return anything     .forEach((node) => {       // Desctructure the id and slug fields for each category       const {id, slug = {}} = node       // If there isn't a slug, we want to do nothing       if (!slug) return        // Make the URL with the current slug       const path = `/categories/$ {slug.current}`        // Create the page using the URL path and the template file, and pass down the id       // that we can use to query for the right category in the template file       createPage({         path,         component: require.resolve('./src/templates/category.js'),         context: {id}       })     }) }

Last, this function is needed at the bottom of the file:

// /web/gatsby-node.js  // ...  exports.createPages = async ({graphql, actions}) => {   await createBlogPostPages(graphql, actions)   await createCategoryPages(graphql, actions) // <= add the function here }

Now that we have the machinery to create the category page node in place, we need to add a template for how it actually should look in the browser. We’ll base it on the existing blog post template to get some consistent styling, but keep it fairly simple in the process.

// /web/src/templates/category.js import React from 'react' import {graphql} from 'gatsby' import Container from '../components/container' import GraphQLErrorList from '../components/graphql-error-list' import SEO from '../components/seo' import Layout from '../containers/layout'  export const query = graphql`   query CategoryTemplateQuery($ id: String!) {     category: sanityCategory(id: {eq: $ id}) {       title       description     }   } ` const CategoryPostTemplate = props => {   const {data = {}, errors} = props   const {title, description} = data.category || {}    return (     <Layout>       <Container>         {errors && <GraphQLErrorList errors={errors} />}         {!data.category && <p>No category data</p>}         <SEO title=How to Make Taxonomy Pages With Gatsby and Sanity.io description={description} />         <article>           <h1>Category: How to Make Taxonomy Pages With Gatsby and Sanity.io</h1>           <p>{description}</p>         </article>       </Container>     </Layout>   ) }  export default CategoryPostTemplate

We are using the ID that was passed into the context in gatsby-node.js to query the category content. Then we use it to query the title and description fields that are on the category type. Make sure to restart with npm run dev after saving these changes, and head over to localhost:8000/categories/structured-content in the browser. The page should look something like this:

A barebones category page with a site title, Archive link, page title, dummy content and a copyright in the footer.
A barebones category page

Cool stuff! But it would be even cooler if we actually could see what posts that belong to this category, because, well, that’s kinda the point of having categories in the first place, right? Ideally, we should be able to query for a “pages” field on the category object.

Before we learn how to that, we need to take a step back to understand how Sanity’s references work.

Querying Sanity’s references

Even though we’re only defining the references in one type, Sanity’s datastore will index them “bi-directionally.” That means creating a reference to the “Structured content” category document from a post lets Sanity know that the category has these incoming references and will keep you from deleting it as long as the reference exists (references can be set as “weak” to override this behavior). If we use GROQ, we can query categories and join posts that have them like this (see the query and result in action on groq.dev):

*[_type == "category"]{   _id,   _type,   title,   "posts": *[_type == "post" && references(^._id)]{     title,     slug   } } // alternative: *[_type == "post" && ^._id in categories[]._ref]{

This ouputs a data structure that lets us make a simple category post template:

[   {     "_id": "39d2ca7f-4862-4ab2-b902-0bf10f1d4c34",     "_type": "category",     "title": "Structured content",     "posts": [       {         "title": "Exploration powered by structured content",         "slug": {           "_type": "slug",           "current": "exploration-powered-by-structured-content"         }       },       {         "title": "My brand new blog powered by Sanity.io",         "slug": {           "_type": "slug",           "current": "my-brand-new-blog-powered-by-sanity-io"         }       }     ]   },   // ... more entries ]

That’s fine for GROQ, what about GraphQL?

Here‘s the kicker: As of yet, this kind of query isn’t possible with Gatsby’s GraphQL API out of the box. But fear not! Gatsby has a powerful API for changing its GraphQL schema that lets us add fields.

Using createResolvers to edit Gatsby’s GraphQL API

Gatsby holds all the content in memory when it builds your site and exposes some APIs that let us tap into how it processes this information. Among these are the Node APIs. It’s probably good to clarify that when we are talking about “node” in Gatsby — not to be confused with Node.js. The creators of Gatsby have borrowed “edges and nodes” from Graph theory where “edges” are the connections between the “nodes” which are the “points” where the actual content is located. Since an edge is a connection between nodes, it can have a “next” and “previous” property.

The edges with next and previous, and the node with fields in GraphQL’s API explorer
The edges with next and previous, and the node with fields in GraphQL’s API explorer

The Node APIs are used by plugins first and foremost, but they can be used to customize how our GraphQL API should work as well. One of these APIs is called createResolvers. It’s fairly new and it lets us tap into how a type’s nodes are created so we can make queries that add data to them.

Let’s use it to add the following logic:

  • Check for ones with the SanityCategory type when creating the nodes.
  • If a node matches this type, create a new field called posts and set it to the SanityPost type.
  • Then run a query that filters all posts that has lists a category that matches the current category’s ID.
  • If there are matching IDs, add the content of the post nodes to this field.

Add the following code to the /web/gatsby-node.js file, either below or above the code that’s already in there:

// /web/gatsby-node.js // Notice the capitalized type names exports.createResolvers = ({createResolvers}) => {   const resolvers = {     SanityCategory: {       posts: {         type: ['SanityPost'],         resolve (source, args, context, info) {           return context.nodeModel.runQuery({             type: 'SanityPost',             query: {               filter: {                 categories: {                   elemMatch: {                     _id: {                       eq: source._id                     }                   }                 }               }             }           })         }       }     }   }   createResolvers(resolvers) }

Now, let’s restart Gatsby’s development server. We should be able to find a new field for posts inside of the sanityCategory and allSanityCategory types.

A GraphQL query for categories with the category title and the titles of the belonging posts

Adding the list of posts to the category template

Now that we have the data we need, we can return to our category page template (/web/src/templates/category.js) and add a list with links to the posts belonging to the category.

// /web/src/templates/category.js import React from 'react' import {graphql, Link} from 'gatsby' import Container from '../components/container' import GraphQLErrorList from '../components/graphql-error-list' import SEO from '../components/seo' import Layout from '../containers/layout' // Import a function to build the blog URL import {getBlogUrl} from '../lib/helpers'  // Add “posts” to the GraphQL query export const query = graphql`   query CategoryTemplateQuery($ id: String!) {     category: sanityCategory(id: {eq: $ id}) {       title       description       posts {         _id         title         publishedAt         slug {           current         }       }     }   } ` const CategoryPostTemplate = props => {   const {data = {}, errors} = props   // Destructure the new posts property from props   const {title, description, posts} = data.category || {}    return (     <Layout>       <Container>         {errors && <GraphQLErrorList errors={errors} />}         {!data.category && <p>No category data</p>}         <SEO title=How to Make Taxonomy Pages With Gatsby and Sanity.io description={description} />         <article>           <h1>Category: How to Make Taxonomy Pages With Gatsby and Sanity.io</h1>           <p>{description}</p>           {/*             If there are any posts, add the heading,             with the list of links to the posts           */}           {posts && (             <React.Fragment>               <h2>Posts</h2>               <ul>                 { posts.map(post => (                   <li key={post._id}>                     <Link to={getBlogUrl(post.publishedAt, post.slug)}>{post.title}</Link>                   </li>))                 }               </ul>             </React.Fragment>)           }         </article>       </Container>     </Layout>   ) }  export default CategoryPostTemplate 

This code will produce this simple category page with a list of linked posts – just liked we wanted!

The category page with the category title and description, as well as a list of its posts

Go make taxonomy pages!

We just completed the process of creating new page types with custom page templates in Gatsby. We covered one of Gatsby’s Node APIs called createResolver and used it to add a new posts field to the category nodes.

This should give you what you need to make other types of taxonomy pages! Do you have multiple authors on your blog? Well, you can use the same logic to create author pages. The interesting thing with the GraphQL filter is that you can use it to go beyond the explicit relationship made with references. It can also be used to match other fields using regular expressions or string comparisons. It’s fairly flexible!

The post How to Make Taxonomy Pages With Gatsby and Sanity.io appeared first on CSS-Tricks.


, , ,

Recreating the CodePen Gutenberg Embed Block for Sanity.io

Chris recently put out a neat CodePen Embed Block for the Gutenberg editor in WordPress. It allows you to embed a Pen just by dropping in its URL. From there, you get access to control the size, theme, and the default tabs that render on initial load. Super neat!

Having a live preview of the embedded Pen while writing is so handy!

But it got me thinking: How difficult would it be to recreate it with Sanity Studio’s Portable Text editor? (Spoiler: Not that difficult). Since I already knew how to do it, it took me under seven minutes from start to finish. This tutorial takes you through how to get up and running with a studio, and how to add the schemas and the custom preview component for a CodePen embed.

That felt so cool that I want to teach you how to do it as well. Let’s dive right into it.

Getting Sanity Studio up and running locally

First, you’ll need to install Sanity Studio locally on your machine. In this tutorial we will be using the blog studio that you can initiate from the command line, but you can also check out the different starters on sanity.io/create. You should be able to tag along with one of those too.

This tutorial assumes that you have a bit of knowledge of JavaScript. It will use a bit of React, but only a small part. You should have installed node and npm if you haven’t already.

Oh, and you’ll want the Sanity CLI, which you can snag with the command line:

npm install --global @sanity/cli

Once the installation is done, you can initiate a new Sanity Studio with a new project by running the command sanity init. It will let you log in with your Google or GitHub account (or make a new account with an email/password). Give your project a name and follow the instructions. When given the options for a project template, choose the blog one:

? Select project template   Movie project (schema + sample data)   E-commerce (schema + sample data) ❯ Blog (schema)   Clean project with no predefined schemas

After completing the steps, change directory (cd) into the new project folder and open it in your favorite code editor. To start the developer server that will also hot reload your studio when you make changes, run sanity start. To stop this server, you press ctrl + C in most command line tools.

Adding the schemas for a CodePen embed

Schemas define which document types that are available in the Studio, and which input fields they have. These schemas are defined in JavaScript objects that you import into the schemas.js file, where they are exported as a function that the Studio translates into its UI. There’s a lot you can do with these schemas, but in this tutorial, we will keep it reasonably simple.

Start with adding a new file inside /yourproject/schemas called codepen.js. Then type in this code:

export default {   name: "codepen",   type: "object",   title: "CodePen Embed",   fields: [     {       name: "url",       type: "url",       title: "CodePen URL"     }   ] };

Then you can go to /yourproject/schemas/schema.js and add the following two lines of code to it:

import createSchema from "part:@sanity/base/schema-creator"; import schemaTypes from "all:part:@sanity/base/schema-type";  import blockContent from "./blockContent"; import category from "./category"; import post from "./post"; import author from "./author"; import codepen from "/codepen.js"; // <= first import the object  export default createSchema({   name: "default",   types: schemaTypes.concat([     post,     author,     category,     blockContent,     codepen // <= add it to the schema types array   ]) });

So what did we just do? Well, we have now made this CodePen object available as a type in other schemas in the Studio. In other words, you can now add type: 'codepen' to get those fields anywhere else in the schema code where you add fields. Adding this type to the rich text field is also our next step. Hang on!

Adding the CodePen field to the rich text editor

Before diving into the code bit, let us take a step back and look at what is going on in terms of the data formats we operate with, and how WordPress and Sanity differ slightly.

While Gutenberg stores rich text as JSON in its runtime (which is great!), what developers end up dealing with is mostly this content as HTML and JSON objects inside of HTML comments.

Sanity stores and distributes rich text content as Portable Text, which developers then serializes in their frontends. That means that you get fine-grained control over how rich text content is rendered by letting you use custom components for your favorite framework, either it's ReactVueSvelte, or .NETPHP, or even Markdown.

In other words, you store your content as structured data in Sanity’s backend, and then decide how you want to use the data inside your frontend components. But enough exposition, let's get back to the code!

Open /schemas/blockContent.js and notice that it's of the type array. Yes, rich text is an array of different types, where one of them has to be of the block type (in which text paragraphs are stored). So the simplest way of making rich text is the following schema definition:

export default {   name: "body",   type: "array",   title: "Body",   of: [     {       type: "block"     }   ] };

Now, blockContent.js has a bunch of more stuff. You can see styles, lists, marks, and so on. All defining which properties should be available for the author. In the top array, there are two types block and image. We are going to add the third one, codepen:

export default {   title: "Block Content",   name: "blockContent",   type: "array",   of: [     {       type: "block"       // ...     },     {       type: "image",       options: { hotspot: true }     },     {       type: "codepen"     }   ] };

Save the file, and that's it! If you now run sanity start in your command line (assuming you haven't already), and open the Studio on https://localhost:3333, you should be able to find your new field in the rich text editor under the "post" type:

Sanity Studio with a CodePen button in the Rich Text editor.

If you try out the new button, you'll get a modal with the URL field that you defined in the previous section. Feel free to add the URL from a cool CodePen that you have found. We will use this one from the legendary Sara Drasner; it's pretty cool.

Just showing the URL value in the editor isn't especially inspiring, though. So let's go ahead and add the actual CodePen embed so we can interact with it directly in the editor!

Adding the CodePen embed as a preview

Open /yourproject/schemas/codepen.js again. Now we are going to make a small React component for our preview. Start by importing React in the top, and the boilerplate for the React component that we will turn into the embed:

import React from "react";  const CodePenPreview = ({ value }) => {   return <pre>{JSON.stringify(value, null, 2)}</pre>; };  export default {   name: "codepen",   type: "object",   title: "CodePen Embed",   fields: [     {       name: "url",       type: "url",       title: "CodePen URL"     }   ] };

The JSON.stringify stuff is a temporary little way of outputting the incoming data in a readable manner. You could also use console.log(value), but who has time to open the developer console?

Now you must tell Sanity how to use this component for the preview. As well as which of the fields in the object it should select for the value in the preview component.

import React from "react";  const CodePenPreview = ({ value }) => {   return <pre>{JSON.stringify(value, null, 2)}</pre>; };  export default {   name: "codepen",   type: "object",   title: "CodePen Embed",   preview: {     select: {       url: "url"     },     component: CodePenPreview   },   fields: [     {       name: "url",       type: "url",       title: "CodePen URL"     }   ] };

The editor should look something like this after you saved your changes:

Cool! Now we want to take the url value and somehow integrate it with a CodePen embed. The easiest way to go about this is to fit the markup for CodePen’s iFrame embed, and fit into our preview component in React.

The original iFrame element will look like this:

<iframe height="265" style="width: 100%;" scrolling="no" title="React Animated Page Transitions" src="https://codepen.io/sdras/embed/gWWQgb?height=265&theme-id=dark&default-tab=js,result" frameborder="no" allowtransparency="true" allowfullscreen="true">   See the Pen <a href='https://codepen.io/sdras/pen/gWWQgb'>React Animated Page Transitions</a> by Sarah Drasner   (<a href='https://codepen.io/sdras'>@sdras</a>) on <a href='https://codepen.io'>CodePen</a>. </iframe>

If we paste this snippet into our preview component, it will almost work. In order to make it JSX-compatible you'll have to some few changes to some of the HTML-attributes. Make sure that you change:

  • style="width: 100%;" to style={{width: "100%"}}
  • frameborder="no" to frameBorder="no"
  • allow-transparency="true" to allowTransparency
  • allow-fullscreen="true" to allowFullScreen

You can remove the content (links, etc.) inside of the iframe, because it isn't particularly useful inside the studio. What we should end up with is something like this:

import React from "react"; import Codepen from "react-codepen-embed";  const CodePenPreview = ({ value }) => {   return (     <iframe       height="265"       style={{ width: '100%' }}       scrolling="no"       title="React Animated Page Transitions"       src="https://codepen.io/sdras/embed/gWWQgb?height=370&theme-id=dark&default-tab=js,result"       frameBorder="no"       allowTransparency       allowFullScreen     />); };  // ...

When saved, we should be able to see the CodePen embed inside the rich text editor:

Notice that the iFrame has an embed URL with some parameters for how it should be displayed. Of course, we could've asked someone to dive into CodePen to obtain this URL, but it's probably better for to use the regular one. We'll take the effort to reassemble into what we need:

The last part is to take the URL from the field, and get the hash and user out of it.

We split the URL string on forward slashes into an array. Then we use array destructuring to assign the different array elements to a variable. Since we only need the user and the hash we leave the other positions empty. This method isn't bulletproof, as it assumed a specific format for the URL, but it works for this example. Then we reassemble the embedUrl by using template literals.

import React from "react";  const CodePenPreview = ({ value }) => {   const { url } = value;   const splitURL = url.split("/");   // [ 'https:', '', 'codepen.io', 'sdras', 'pen', 'gWWQgb' ]   const [, , , user, , hash] = splitURL;   const embedUrl = `https://codepen.io/$ {user}/embed/$ {hash}?height=370&theme-id=dark&default-tab=result`;   return (     <iframe       height="370"       style={{ width: '100%' }}       scrolling="no"       title="CodePen Embed"       src={embedUrl}       frameBorder="no"       allowTransparency       allowFullScreen     />   ); }; // ...

Save the changes and voilá; we're pretty much done with the custom CodePen block!

Taking it further

Now, you probably noticed that Chris had put more settings into his custom block. Nothing is stopping us from doing the same! If we look up the documentation for the React CodePen embed component that we installed, we'll find a table of properties that it can take. We can add these as fields in the schema definition. For example, if we wanted to add the themeId, we could do it as follows:

import React from "react"; import Codepen from "react-codepen-embed";  const CodePenPreview = ({ value }) => {   const { url, themeId = "dark" } = value; // <= add themeId here, default it to "dark"   const splitURL = url.split("/");   // [ 'https:', '', 'codepen.io', 'sdras', 'pen', 'gWWQgb' ]   const [, , , user, , hash] = splitURL;   const embedUrl = `https://codepen.io/$ {user}/embed/$ {hash}?height=370&theme-id=$ {themeId}&default-tab=result`; // <= add themeId here   return (     <iframe       height="370"       style={{ width: '100%' }}       scrolling="no"       title="CodePen Embed"       src={embedUrl}       frameBorder="no"       allowTransparency       allowFullScreen     />   ); };  export default {   name: "codepen",   type: "object",   title: "CodePen Embed",   preview: {     select: {       url: "url",       themeId: "themeId" // <= add themeId here     },     component: CodePenPreview   },   fields: [     {       name: "url",       type: "url",       title: "CodePen URL"     },     // Add the new field below     {       name: "themeId",       type: "string",       title: "Theme ID",       description: 'You can use "light" and "dark" also.'     }   ] };


We just looked at how schemas for Sanity Studio work, and learned how to make previews for custom components to boot! Hopefully, you now know enough to make pretty much any custom component with a preview using these same principles. If you do, I would love to know about it either on Twitter or in the comments.

The post Recreating the CodePen Gutenberg Embed Block for Sanity.io appeared first on CSS-Tricks.


, , , , ,