Jennifer K Shields

advocate, reader, writer, link-hoarder, poet, more

this is a collection of some things

for everything else, see jenniferkshields.nz

adding images to this blog with sanity and next/image

Context: This site is built with Sanity.io and Next.js. I actually built (almost) all of it from my iPad Pro, just to see if I could - so it's always intended to be a little sparse!

But for my most recent post, I wanted to add a couple of relevant photos. Somewhat to my surprise, I realised that Sanity's text editor didn't have an image element already, and that I'd have to implement it.

No worries! Off to google... except what I could find wasn't super straightforward, and I also wanted to use Next.js's new Image Component.

So, here's how!

First, add images to Sanity's relevant content block editor by adding { type: "image", } alongside { type: "block"} in your schema. This left me with:

{ name: "body", 
  title: "long text", 
  type: "array", 
  of: [ 
        { type: "block" }, 
        { type: "image" },
      ], 
  description: "Full text to display on the post page", 
}

And that was that! But now to add it to the front end...

I'm only adding this in one place: the 'blog' schema of Sanity, and the Next.js component that generates each blog page.

I was already using @sanity/block-content-to-react, which takes custom serializers. I wasn't sure what that was, but I took a stab and did some googling and basically: serializers tell react how to present block content.

So, in my [slug].js, I added a serializer constant and wanted it to return a next/image Image component. This would mean grabbing the image URL from Sanity - to cut a long story short, save yourself time and use Sanity's image URL builder. I followed that page to create a custom builder and then an urlFor function to automatically generate the image I needed:

const builder = imageUrlBuilder(sanityClient)

function urlFor(source) { 
    return builder.image(source)
}

Finally, I could put that into my Image component in the serializer and return it:

const serializers = {
 types: {
  image: props => (
    <Image src={urlFor(props.node.asset).url()} layout="fill" />
  )
 }
}

That put the images on the page, but layout="fill" removed them from the content flow thanks to how next/image is currently implemented.

Honestly, this next bit was the hardest part to figure out, and the sooner Next.js updates next/image to allow styling of the wrapper div, the better!

I use theme-ui in this project, which allowed me to create another wrapper div that could target both next/image's wrapper div and the img itself.

const serializers = {
 types: {
  image: props => (
    <div sx={{
      maxWidth: '100%',
      margin: '0 auto',
      height: 'auto',
      display: 'block',
 
      '& > div': {
        position: 'relative !important',
        height: 'auto',
        display: 'block',
      }
 
      '& > div > img': {
        height: '100% !important',
        width: '100% !important',
        position: 'relative !important',
      }
    }}>
      <Image src={urlFor(props.node.asset).url()} layout="fill" />
    </div>
  )
 }
}

This is some real CSS mess, and to be honest some of it is probably not needed, but you know when you finally find a solution to overriding someone else's styling and you're not sure what parts are load-baring? Yeah.

But finally! I had my images in the content flow, fitting to the parent div of the post, and looking nice! They're served by Sanity's CDN and optimised by next/image, and I'm pretty happy.

I ended up adding captions (by expanding { type: 'image' } to include fields like you would when defining a schema, and adding a <p>{props.node.caption}</p> after the Image component) and now I'm happy with how it's ended up!

You can see the output here, and find a gist of my [slug].js here.

2021-02-22
Back home ->