DEV Community

Cover image for Using GROQ to Extract an Excerpt from a Sanity Block Content Field
Jason St-Cyr
Jason St-Cyr

Posted on • Originally published at jasonstcyr.com on

Using GROQ to Extract an Excerpt from a Sanity Block Content Field

I was digging into Sanity to learn a little bit more about GROQ and how the queries work while building out a potential replacement for my blog hosting. When listing articles, one of the most common things I want to do is show a little excerpt from the article, but I don’t want to create excerpts for every article. I would rather pull something short from the existing content. Maybe the first paragraph, or the first 200-300 characters, that sort of thing.

When looking at Sanity as an option, I discovered that the standard way most rich content is stored is using an array object of type ‘block’. For example, this is my initial schema type for my “post” content:

import {defineField, defineType} from 'sanity'

export const postType = defineType({
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      type: 'string',
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'slug',
      type: 'slug',
      options: {source: 'title'},
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'publishedAt',
      type: 'datetime',
      initialValue: () => new Date().toISOString(),
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'body',
      type: 'array',
      of: [{type: 'block'}],
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'image',
      type: 'image',
    }),
  ],
})
Enter fullscreen mode Exit fullscreen mode

When pulling the content back from Sanity, the default query would get ALL of the content for that 'body' field, but returned as an array. This is great when you want to display all the content on a page for the reader. However, on a listing page, I only want a part of that content to be displayed, ideally the first 150 or 200 characters. (Note that I've made the field required, this will be important later)

Pulling Part of the Block Content

I found a very interesting GROQ query that was able to get a short value from a longer piece of block content but it seemed to be doing a lot of transforming of the field for what should be a very fast query when you are doing a listing page. I was trying to find a performant way to pull less data across the wire for listing pages, but I felt like that approach was doing too much. Using the Sanity Studio to play around with the GROQ queries I learned that I could do some querying and essentially get the first block of text off of my body field using body[0].children[0].text:

*[
  _type == "post"
  && defined(slug.current)
]|order(publishedAt desc)[0...12]{
  _id,
  title,
  slug,
  publishedAt,
  image {
    asset->{
      _id,
      url,
      metadata {
        dimensions {
          width,
          height
        }
      }
    }
  },
  "summary": body[0].children[0].text,
}
Enter fullscreen mode Exit fullscreen mode

This allowed me to retrieve a smaller amount of data for the summary, but it wasn't quite short enough for my listing page yet. Because I've made my schema require body field content, I can be certain there is content in the field and I won't run into issues with body[0] throwing errors, but should there be nothing I have noticed that the GROQ returns a null value, so I will need to make sure I handle that later.

Displaying an Excerpt

In my Next.js application, I then wanted to make sure I only displayed a portion of this summary to fit with the design I was going for. When displaying the summary, I used the slice function to cut the field down to size:

{post.summary && (
   <p className="text-gray-400">{post.summary.slice(0, 200)}...</p>
 )}
Enter fullscreen mode Exit fullscreen mode

Note that I handled the null scenario here and only output a summary if one is found. Not the prettiest way to get there, but if anybody else wants a way to get that excerpt without a bunch of transformations in GROQ, this allowed me to reduce the amount of content coming back in the query while still keeping it to the specific size I wanted on the page.

Screenshot of output page showing multiple posts. Each post has a short summary displayed.

Key Takeaways

I hope this helps somebody! Honestly, it will probably be me finding this in a search the next time I'm trying to remember how I did that. The key pieces to focus on are:

  1. Make your block content field required in your schema so you can be sure there is a value when you query.
  2. In your GROQ query, pull only the first child of the first block to reduce data being transmitted for large content pieces.
  3. In your component, use 'slice' on the string value to cut it down to the length you want.

If anybody found a better way to do this, I'd really love to hear about it! I am still learning Sanity and GROQ and could use the help!

Top comments (0)

👋 Kindness is contagious

Sign in to DEV to enjoy its full potential—unlock a customized interface with dark mode, personal reading preferences, and more.

Okay