惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

U
Unit 42
Microsoft Azure Blog
Microsoft Azure Blog
Engineering at Meta
Engineering at Meta
博客园 - 【当耐特】
人人都是产品经理
人人都是产品经理
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
Blog — PlanetScale
Blog — PlanetScale
酷 壳 – CoolShell
酷 壳 – CoolShell
aimingoo的专栏
aimingoo的专栏
Jina AI
Jina AI
小众软件
小众软件
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
大猫的无限游戏
大猫的无限游戏
博客园 - 聂微东
Y
Y Combinator Blog
云风的 BLOG
云风的 BLOG
I
InfoQ
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Martin Fowler
Martin Fowler
P
Proofpoint News Feed
MyScale Blog
MyScale Blog

Netlify Developers

AI model comparison using Netlify's AI Gateway | Netlify Developers Build an AI agent to automatically process and summarize form submissions | Netlify Developers Build an AI agent to automatically generate relevant images for blog posts | Netlify Developers Prerendering SPA content pages to enable AI agent access | Netlify Developers Identify image optimization opportunities with Netlify Observability | Netlify Developers Fix top 404 page not found errors with Netlify Observability | Netlify Developers Introducing AI Gateway: built-in AI model access on Netlify | Netlify Developers Introducing Observability: your project insights on Netlify | Netlify Developers From first publish to first update with AI agents | Netlify Developers How to use Agent Runners on Netlify | Netlify Developers Add forms to your project with AI + Netlify | Netlify Developers Track Real User Metrics | Netlify Developers Lighthouse performance scores on Netlify | Netlify Developers Review your AI project before you ship | Netlify Developers Using environment variables in AI Projects on Netlify | Netlify Developers Build prototypes and MVPs fast with AI + Netlify | Netlify Developers Pause auto-publishing on Netlify | Netlify Developers Run serverless functions, storage, and more natively in Nuxt dev | Netlify Developers Netlify Office Hours: The AX Arcade challenge | Netlify Developers Netlify Office Hours: Prompting with style | Netlify Developers Netlify Office Hours: RAG Apps with OpenAI + Netlify DB | Netlify Developers How to build a RAG application with Neon, Netlify & OpenAI | Netlify Developers Netlify Office Hours: Netlify DB (powered by Neon) | Netlify Developers Netlify Office Hours: Netlify MCP Server | Netlify Developers Netlify Office Hours: Netlify Vite Plugin | Netlify Developers Building MCPs with Netlify | Netlify Developers Deploying sites from your AI tool | Netlify Developers Adding your domain using Netlify API | Netlify Developers Build a context driven chat bot with Netlify Blob and OpenAI | Netlify Developers Simplify deployments with Netlify’s branch-matching environment variables | Netlify Developers
How to use Sanity Portable Text with Astro | Netlify Deve...
2024-05-10 · via Netlify Developers

Providing content editors with the ability to apply rich text formatting, be it in long-form content with styled headings, or providing emphasis in places of particular importance can greatly enhance a user’s reading experience. Sanity Studio supports many different formatting methods using their editor interface and within this guide we’ll explain how to ensure those same formatting rules are applied when the data is displayed on a web page.

#TL;DR

In a previous guide we built a simple Astro site that uses Sanity as the CMS.

Within this guide, you’ll discover how to use Portable Text to enable rich text formatting and the process for embedding images within the content body.

You can see a preview of the deployed site and the repository for the code on the following links.

#Prerequisites

These are the things you’ll need to set up before starting this tutorial:

#What is Portable Text?

Portable Text is a JSON based rich text specification for modern content editing platforms. Portable text is an agnostic abstraction of rich text that can be serialized into pretty much any markup language, be it HTML, Markdown, SSML, XML, etc.

Portable Text is built on the idea of rich text as an array of blocks. Each block can have a style and a set of mark definitions, which describe how the data could be presented. Portable Text also allows for custom content objects, enabling editing and rendering environments to mix rich text with custom content types.

Here’s an example.

{

"style": "normal",

"_type": "block",

"children": [

{

"_type": "span",

"marks": ["a-key", "emphasis"],

"text": "some text"

}

],

"markDefs": [

{

"_key": "a-key",

"_type": "markType",

"extraData": "some data"

}

]

}

#Install dependencies

There two dependencies required to enable Portable Text and embedded images are astro-portabletext and @sanity/image-url. You’ll need both installed before proceeding.

Run the following in your terminal.

npm install astro-portabletext @sanity/image-url


#Update Sanity Schema

Open sanity.config.ts and add the following types to the schema.

// sanity.config.ts

import { defineConfig } from "sanity";

import { defineConfig, defineArrayMember } from "sanity";

import { structureTool } from "sanity/structure";

export default defineConfig({

title: "Netlify Astro Sanity",

projectId: import.meta.env.PUBLIC_SANITY_STUDIO_PROJECT_ID,

dataset: import.meta.env.PUBLIC_SANITY_STUDIO_DATASET,

plugins: [structureTool()],

schema: {

types: [

{

type: "document",

name: "post",

title: "Post",

fields: [

{

name: "title",

title: "Title",

type: "string",

},

{

name: "slug",

title: "Slug",

type: "slug",

options: {

source: "title",

maxLength: 96,

},

},

{

title: "Block Content",

name: "blockContent",

type: "array",

of: [

defineArrayMember({

title: "Block",

type: "block",

styles: [

{ title: "H2", value: "h2" },

{ title: "H3", value: "h3" },

{ title: "H4", value: "h4" },

{ title: "Normal", value: "p" },

{ title: "Quote", value: "blockquote" },

],

lists: [

{ title: "Bullet", value: "bullet" },

{ title: "Number", value: "number" },

],

marks: {

decorators: [

{ title: "Strong", value: "strong" },

{ title: "Emphasis", value: "em" },

],

annotations: [

{

title: "URL",

name: "link",

type: "object",

fields: [

{

title: "URL",

name: "href",

type: "url",

},

],

},

],

},

}),

{

title: "Image",

type: "image",

fields: [

{

name: "alt",

title: "Alt",

type: "string",

},

],

},

],

},

],

},

],

},

});

The new content is of type blockContent. Within this object we can use the of type to create an array of the two new content types. The first type is block, the second is image.

#block

The block type schema is where you can define the fields that will appear in Sanity Studio and will allow editors to create headings, blockquotes, lists, marks for bold and italic text and annotations such as links.

You can read more about the available bocks in the Sanity docs: Usage.

The image type is for images that can be added to the body of a blog post, we’ve also defined a field for the alt text which will be the text added to the alt attribute on the HTML img element.

#image

If you’ve added both of the above, head back over to /admin and you should see a new field in one of your posts where you can format headings, and add an image etc. Be sure to click publish to save your changes!

You can read more about images in the Sanity docs: Presenting Images.

Sanity blocks UI

#Rendering Portable Text

Open src/pages/posts/[slug].astro and make the following changes.

// src/pages/posts/[slug].astro

---

export const prerender = true;

import { sanityClient } from 'sanity:client';

import { PortableText } from 'astro-portabletext';

import { List } from 'astro-portabletext/components';

export async function getStaticPaths() {

const posts = await sanityClient.fetch(`*[_type == "post" && defined(slug.current)]`);

return posts.map((post) => {

return {

params: {

slug: post.slug.current,

},

};

});

}

const { slug } = Astro.params;

const { title } = await sanityClient.fetch(`*[_type == "post" && slug.current == $slug][0]`, { slug });

const { title, blockContent } = await sanityClient.fetch(`*[_type == "post" && slug.current == $slug][0]`, { slug });

const components = {

list: List,

}

---

<html>

<body>

<a href='/'>Back</a>

<h1>{title}</h1>

<PortableText value={blockContent} components={components} />

</body>

</html>

The main changes here are that we’re now destructuring blockContent from the sanityClient query and passing it onto the <PortableText /> component via the value prop.

In order to ensure the new Portable Text values within blockContent can be correctly converted into HTML we’re passing a components object onto the <PortableText /> via the components prop. It’s here where we can define which components map to which content schema types and ensure HTML elements are used.

You’ll notice in the components object we’ve defined a key for list. This key maps to a helpful Sanity component named List.

#List Component

Sanity’s List component can determine which HTML list type, ol (ordered list) or ul (unordered list) to use based on the listItem type that is present within the response.

You can also provide your own components.

Here’s an example of a custom list component which uses the listItem type to determine which HTML list element is returned.

// src/components/list.astro

---

const {

node: { listItem },

...attrs

} = Astro.props;

---

{

listItem === 'number' ? (

<ol {...attrs}>

<slot />

</ol>

) : (

<ul {...attrs}>

<slot />

</ul>

)

}

The usage in the components object would remain the same. E.g

const components = {

list: List,

}

#Rendering Images

For images there’s a similar setup, but this time we will be using a custom image component.

The custom image component contains a number of other Sanity helpers to convert the image _ref into an image url that can be used as the src on an HTML img element.

#Create an Image component

Create a new file named image.astro in src/components and add the following code.

// src/components/image.astro

---

import imageUrlBuilder from '@sanity/image-url';

const builder = imageUrlBuilder({

projectId: import.meta.env.PUBLIC_SANITY_STUDIO_PROJECT_ID,

dataset: import.meta.env.PUBLIC_SANITY_STUDIO_DATASET,

});

const {

node: {

asset: { _ref },

alt,

},

...attrs

} = Astro.props;

const src = builder.image(_ref).auto('format').fit('max').width(512).toString();

---

<img src={src} alt={alt} {...attrs} />

This component has access to the asset _ref which looks something like this: image-1085765fe253da8ca898a06fce40c049fd6f63e2-1024x576-jpg.

By passing the _ref to Sanity’s imageUrlBuilder helper function it can construct the correct path to the image asset based on your projectId and dataset.

Add console.log(src) to see the final constructed url. In our example it looks something like this:

https://cdn.sanity.io/images/hvpty17w/production/1085765fe253da8ca898a06fce40c049fd6f63e2-1024x576.jpg?w=512&fit=max&auto=format

The additional builder methods are to help with image formatting. You can read more about those in the Sanity docs: Builder methods.

#How to add custom components

In this next part we’ll explain how to add custom components to the content schema. In our example we’ve added a CTA (Call to action) which has two fields. Text, which is the text to display, and URL, which is the url to open in a new browser window.

The HTML anchor element is wrapped within a div which can be styled to suit your needs.

#Add custom schema

Open src/pages/posts/[slug].astro and make the following changes below the image schema type.

{

title: 'Image',

...

},

{

title: 'CTA',

type: 'object',

name: 'cta',

fields: [

{

name: 'text',

title: 'Text',

type: 'string',

},

{

title: 'URL',

name: 'href',

type: 'url',

},

],

},

If you head back over to Sanity Studio (/admin) you should see the following CTA button has been added to the editor toolbar.

Sanity CTA button

Clicking this will open a new modal where you can add the Text and URL for the call to action.

Sanity CTA values

#Finished

And that’s it, you’re now able to add rich tech formatting and images to the body of your content, as well as being able to create custom components. Congrats!