Updated on: Sun Feb 07 2021
Headless cms Sanity.io offers us powerful functionality out of the box. But it also has a flexibility to be expanded in different ways. Here are some notes on how to teach your sanity studio to deal with external and internal links.
Prerequisites: we have blog on running on next.js and sanity.io providing data to it. Everything already had being set up and work fine. To work with blog post body sanity provides us with reach content editor. Assume it's type already defined and included into sanity studio schema. Default configuration for it could live in blockContent.js and look like this:
copied javascript
export default {
title: 'Block Content',
name: 'blockContent',
type: 'array',
of: [
{
title: 'Block',
type: 'block',
styles: [
{title: 'Normal', value: 'normal'},
{title: 'H1', value: 'h1'},
{title: 'H2', value: 'h2'},
{title: 'H3', value: 'h3'},
{title: 'H4', value: 'h4'},
{title: 'Quote', value: 'blockquote'},
],
lists: [{title: 'Bullet', value: 'bullet'}],
marks: {
decorators: [
{title: 'Strong', value: 'strong'},
{title: 'Emphasis', value: 'em'},
],
annotations: [
{
title: 'URL',
name: 'link',
type: 'object',
fields: [
{
title: 'URL',
name: 'href',
type: 'url',
}
],
},
]
},
}
],
}
To add a possibility render external and internal links differently we have to extend our link annotation a bit. First let's just add a boolean flag to it:
copied javascript
...
{
title: 'URL',
name: 'link',
type: 'object',
fields: [
{
title: 'URL',
name: 'href',
type: 'url',
},
{
title: 'Open in new tab',
name: 'blank',
type: 'boolean'
}
]
}
...
Then in BlogPost component we can use it to render external link in a different way via serializers prop feeded to block content (everything here in context of next.js):
copied jsx
import BlockContent from "@sanity/block-content-to-react";
import Link from "next/link";
...
const serializers = {
marks: {
link: ({ children, mark: {blank, href} }) => {
if (blank) {
return <a href={href} target="_blank" rel="nofollow">{children}</a>
}
return <Link href={href}>
<a>{children}</a>
</Link>;
}
}
}
...
<BlockContent
blocks={body}
projectId={projectId}
dataset={dataset}
serializers={serializers}
/>
Important parts here: link in serializers stands for "link" name of annotation in block content type definition provided above. Renderer is a simple react component and it got all props to needed to properly render external and internal links.
Now let's make a step further and add a possibility to create internal links via direct references to other posts. To do this we have to add another one annotation to annotations list in our blockContent.js:
copied javascript
{
title: 'Internal link',
name: 'internalLink',
type: 'object',
fields: [
{
type: 'reference',
name: 'item',
to: [
{
type: 'post'
}
]
}
]
}
It has type object and fields array with one single element with type reference pointing to our post type. If you named things differently in your schema make appropriate changes. Now we have to add this internalLink to groq blog post query and teach serializers on how to render those references:
copied jsx
...
const postQuery = groq`*[_type == "post" && slug.current == $slug][0]{
body[]{
...,
markDefs[]{
...,
_type == "internalLink" => {
...,
"postUrl": "/post/" + @.item->slug.current
}
}
}
}
`;
...
const serializers = {
...
marks: {
...
internalLink: ({ children, mark: {postUrl} }) => {
return <Link href={postUrl}>
<a>{children}</a>
</Link>;
}
},
};
Groq query literally says "if there is mark with type internalLink then put field postUrl with slug of post it referenced to" and internalLink serializer renders it to next/link component.
Last thing here is possibility to define appearance of our new link annotations in sanity studio:
copied javascript
{
name: "link",
...
blockEditor: {
icon: () => <div>🌍</div>,
render: ({children}) => <span style={{borderBottom: "2px solid green"}}>{children}</span>
}
}