advocate, reader, writer, link-hoarder, poet, more
this is a collection of some things
for everything else, see jenniferkshields.nz
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.