Add a New Embed Type
Space Madness works hard to make the content writing and editing process easy. One way of doing this is providing a simple "Embed" component in the Sanity Studio block editor.
Copy your URL, paste it into the Embed URL box, and click "Publish". That's all there is to it.
Underneath that great UX is a flexible system that helps us craft an optimal embed experience. We control what embeds are included and how they will render. This guide will help you dig into the inner workings of this system as we'll add a new embed renderer for CodePen.
Inspecting the User Experience
Let's start by taking a closer look at the user experience for adding an embed to a Sanity document. We'll try with two URLs: one that is supported, and one that isn't supported yet.
Copy this to your clipboard:
https://www.youtube.com/watch?v=O5nskjZ_GoI&list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo&index=2
Head over to your Sanity Studio and select a document for testing.
Click into the block editor and add an Embed
block.
Paste in the URL from your clipboard. You should see no errors.
Click Publish, then go visit your page. The embed works. Dead Simple.
Now let's try with an unsupported service.
Copy this to your clipboard:
https://codepen.io/bramus/pen/JjvEExW
Now repeat the process.
You should see something like this:
This is a good experience! The content author is immediately alerted to the issue and the document cannot be published.
Space Madness has a clever system for building new Embeds that also includes validation of URLs within the Sanity Studio. Let's start building our new CodePen embed to learn out how.
Update the Embed Registry
All of our code for embeds lives in the sanity-astro-embeds
package. This package is pulled into our Astro site to render the embeds on our page. Some of its functions are also pulled into the Sanity site to validate the embed URLs. We won't have to worry much about the structure since we only need to make edits in one place.
Let's explore the file packages/sanity-astro-embeds/src/embedRegistry.tsx
.
This file might look a bit scary. It mixes Regular Expressions, React, and some gnarly Typescript, but it's relatively easy to make sense of it if we break it down. Let's start by taking a look at the Twitter entry in the registry object.
twitter: {
title: "Twitter",
regexp: /^https?:\/\/twitter\.com/,
getRenderProps: (urlOrId: string) => {
const renderProps = {
wrapperClass: "aspect-video",
} as ComponentPropsWithoutRef<typeof YouTube> & {
wrapperClass: string;
};
const tweetIdRegex = /^https?:\/\/twitter\.com\/([\w-]+\/status\/\d+)/;
const match = urlOrId.match(tweetIdRegex);
renderProps.tweetLink = match ? match[1] : "";
return renderProps;
},
render: (props: ComponentPropsWithoutRef<typeof Tweet>) =>
props.tweetLink ? <Tweet {...props} /> : null,
},
Let's break down each attribute:
title
sets a human readable title for the embed service.
regexp
is a Regular Expression that we use to check if a URL is supported. We're using a simple URL match to twitter.com. It's not perfect, but it'll probably work for most use cases.
getRenderProps
is a function that will take the ID or URL from an input component and churn out data that matches the props of an embed component. We do this separately in case there is extra build time work we want to do. We want our output in the render
function to be static.
render
is the function that renders the React embed component. It takes the props from the getRenderProps
function and passes them to an MDX Embed component.
We're making use of the fantastic library. It has an extensive list of components for popular services. We won't have to reinvent the wheel here. We just need to wrap the wheel in bubble wrap so we don't hurt ourselves.
Let's start building the scaffold of our own CodePen
Registry service.
Scaffold a new Service
Let's get down to business and add our new service.
First up, is supported by MDX Embed, so let's import that at the top of our registry file.
import { Spotify, Tweet, Vimeo, YouTube, CodePen } from "mdx-embed";
Scroll down to the bottom of the embedRegistry
object and add a new codePen
attribute with the following structure:
codePen: {
title: "CodePen",
regexp: /^https?:\/\/codepen\.io\/[\w-]+\/(pen|full|details)\/[\w-]+$/,
getRenderProps: (urlOrId: string) => {
const renderProps = {
wrapperClass: "aspect-video",
} as ComponentPropsWithoutRef<typeof CodePen> & {
wrapperClass: string;
};
// TODO
return renderProps;
},
render: (props: ComponentPropsWithoutRef<typeof CodePen>) =>
<div>TODO</div>
},
This is the basic starting point for any new embed. We provide a title and a RegExp that matches our "share" URL.
For the RegExp, Chat GPT can help you out with the details. Feel free to test out your RegExp in a tool like RegExr to help make sense of what URLs will work for a given pattern.
For the getRenderProps
function, you'll notice this line:
const renderProps = {
wrapperClass: "aspect-video",
} as ComponentPropsWithoutRef<typeof CodePen> & {
wrapperClass: string;
};
MDX Embed does not export Types, so we use some React magic to squeeze those props out from the exported CodePen
component. We re-use this pattern again with the render function's props:
render: (props: ComponentPropsWithoutRef<typeof CodePen>) =>
This is a bit wonky, but it gives us some guidance on what data each MDX Embed component expects.
The wrapper class will be applied to the div surrounding the client component. Here we use an aspect-video
utility class to preserve space in the document for our future iframe.
Let's explore how to build our getRenderProps
function by trying to fill in the renderProps
object. Try typing out renderProps
followed by a period:
Now we can see that codePenId
is required and there are several optional parameters. The MDX Embed docs provide a bit more context on what we need from the URL:
https://codepen.io/team/codepen/pen/PNaGbb
Chat GPT can help us out again to extract the ID from the URL. Here's a version of RegExp that should work:
getRenderProps: (urlOrId: string) => {
const renderProps = {} as ComponentPropsWithoutRef<typeof CodePen>;
const codePenRegex =
/^https?:\/\/codepen\.io\/[\w-]+\/(?:pen|full|details)\/([\w-]+)$/;
const match = urlOrId.match(codePenRegex);
if (match && match.length > 1) {
renderProps.codePenId = match[1];
}
return renderProps;
},
We won't worry about any of the optional parameters for now. We have the required value and we can render our component.
Go ahead and update the render
function to check if codePenId
exists in the props and render the CodePen
component.
render: (props: ComponentPropsWithoutRef<typeof CodePen>) =>
props.codePenId ? <CodePen {...props} /> : null,
Our component is functional at this point. 🎉
Head back to Sanity Studio and try to add your CodePen embed. Sanity's validation engine should now recognize the CodePen URL as valid. You can publish the document and inspect your work on your Astro site.
With Great Power...
We'll wrap up the guide here. You have all the information necessary to tweak your embeds to your hearts content.
Please make note of the following:
sanity-astro-embeds
will load all embeds using theclient:visible
directive to try to keep page loads fast. This may result in some scroll performance issues as users scroll down the page.- Each Embed service you add will cause more JavaScript to load for your users. Exercise caution and check that your pages aren't getting bogged down by Embeds.
- You are not limited to the components provided by MDX Embed. Dig into how they build their components, and you can build your own.