DengBlog
Picture of the author

deng_blog中关于sanity的使用

Fri May 31 2024
#React#Next#Sanity
Blog
一、sanity是什么?

Sanity CMS 是一个无头CMS,它是一个与表现层或客户端脱钩的内容创作后端系统。它通过API将存储的内容作为数据公开,以便在网站、移动应用和物联网等不同平台上消费,它与WordPress等传统CMS截然不同。

Sanity CMS采取了结构化的内容创作方法,提供了简洁的功能,通过其图像管道管理图像,通过管理文字,以及设计。它还提供了Sanity Studio ,一个用React构建的功能齐全、可定制、可扩展的编辑器。

说人话就是你不要编写后端代码,也不需要写管理后台,CMS,只需要专注于用户端的编写,后端的数据只需要使用sanity即可

二、使用next.js创建项目以及集成sanity
  • 创建项目
1npx create-next-app@latest
2
3// 安装时,会看到以下提示
4What is your project named? my-app
5Would you like to use TypeScript? No / Yes
6Would you like to use ESLint? No / Yes
7Would you like to use Tailwind CSS? No / Yes
8Would you like to use `src/` directory? No / Yes
9Would you like to use App Router? (recommended) No / Yes
10Would you like to customize the default import alias (@/*)? No / Yes
11What import alias would you like configured? @/*
  • 集成sanity.io

需要在 https://www.sanity.io/ 注册自己的账号,再回到项目中执行以下命令:

1npm create sanity@latest -- --template clean --create-project "project-name" --dataset database-name

在新版的sanity网站中注册时就会让你创建studio,而上面的命令会帮你创建一个新的studio和新的数据库,过程中会提示让你登录。

执行完上面的方法就会看到sanity自动帮你在项目中生成了一个文件夹,这时输入项目路径 /studio 就能看到sanity的工作台,也就是CMS

注意,这里首次进入studio会提示登录,需要登录刚才命令登录的同一个账号
Blog

上图是已经有了数据也就是blog和tag type,而首次打开是没有的,那么该怎么操作呢?就需要进入刚刚自动生成的sanity文件夹中 sanity/schema.ts

1import { type SchemaTypeDefinition } from 'sanity'
2import { blogType } from './schemas/blogType'
3import { tagType } from './schemas/tagType'
4
5export const schema: { types: SchemaTypeDefinition[] } = {
6  types: [blogType,tagType],
7}
1import { defineType, defineField, defineArrayMember } from "sanity";
2
3export const blogType = defineType({
4  name: "blog",
5  type: "document",
6  title: "Blog",
7  fields: [
8    defineField({
9      name: "title",
10      type: "string",
11      title: "Blog Title",
12      validation: (Rule) => Rule.required().max(50).error("最多不超过50个字"),
13    }),
14
15    defineField({
16      name: "slug",
17      type: "slug",
18      title: "Slug",
19      options: {
20        source: "title",
21      },
22      validation: (Rule) => Rule.required(),
23    }),
24    defineField({
25      name: "publishDate",
26      type: "datetime",
27      title: "Blog Publish Date",
28      initialValue: () => new Date().toISOString(),
29    }),
30    defineField({
31      name: "description",
32      type: "text",
33      title: "Blog Description",
34      validation: (Rule) => Rule.max(200).error("最多不超过200个字").required(),
35    }),
36    defineField({
37      name: "content",
38      type: "array",
39      title: "Blog Content",
40      of: [
41        { type: "block" },
42        {
43          type: "image",
44          fields: [{ name: "alt", type: "text", title: "Alt" }],
45        },
46        {
47          type: 'code',
48          options:{
49            language: 'tsx',
50          }
51        }
52      ],
53      validation: (Rule) => Rule.required(),
54    }),
55    defineField({
56      name: "tags",
57      type: "array",
58      title: "Tags",
59      of: [
60        {
61          type: "reference",
62          to: [{ type: "tagType" }],
63        },
64      ],
65    }),
66  ],
67});
68
1import { defineType } from "sanity";
2
3export const tagType = defineType({
4  name: "tagType",
5  type: "document",
6  title: "Tag Type",
7  fields: [
8    {
9      name: "name",
10      type: "string",
11      title: "Tag Name",
12    },
13    {
14      name: "slug",
15      type: "slug",
16      title: "Slug",
17      options: {
18        source: "name",
19        maxLength: 50,
20      },
21    },
22  ],
23});
24

需要自己创建对应的schemas type并且导入到schema.ts文件中即可

其中有一点需要注意:在blogType中如下代码如果需要使用插入代码块的功能和高亮文字/文字颜色设置,则需要配置type:code,而sanity本身还不支持直接添加type为code的类型,所以需要下载插件:
  • npm i @sanity/code-input,
  • npm i sanity-plugin-simpler-color-input
并且在sanity.config.ts中配置下载的相关插件
1defineField({
2      name: "content",
3      type: "array",
4      title: "Blog Content",
5      of: [
6        {
7          type: "block",
8          marks: {
9            annotations: [
10              {
11                type: "textColor",
12              },
13              {
14                type: "highlightColor",
15              },
16            ],
17          },
18        },
19        {
20          type: "image",
21          fields: [{ name: "alt", type: "text", title: "Alt" }],
22        },
23        {
24          type: "code",
25          options: {
26            language: "tsx",
27          },
28        },
29      ],
30      validation: (Rule) => Rule.required(),
31    }),
1const PortableComponents = {
2  types: {
3    image: ({ value }: any) => {
4      return (
5        <Image src={urlForImage(value)} alt="Blog" width={700} height={700} />
6      );
7    },
8    // for support insert code block
9    code: ({ value }: any) => {
10      return <CodeBlock value={value} />;
11    },
12  },
13  // for support text color and highlight
14  marks: {
15    textColor: ({ children, value }: any) => (
16      <span style={{ color: value.value }}>{children}</span>
17    ),
18    highlightColor: ({ children, value }: any) => (
19      <span style={{ background: value.value }}>{children}</span>
20    ),
21  },
22};
使文字能够支持添加超链接以及内部链接功能
1defineField({
2      name: "content",
3      type: "array",
4      title: "Blog Content",
5      of: [
6        {
7          type: "block",
8          marks: {
9            annotations: [
10              {
11                name: "link", //外部链接
12                type: "object",
13                title: "External link",
14                fields: [
15                  {
16                    name: "href",
17                    type: "url",
18                    title: "URL",
19                  },
20                  {
21                    title: "Open in new tab",
22                    name: "blank",
23                    description: "是否在新窗口打开?",
24                    type: "boolean",
25                  },
26                ],
27              },
28              {
29                name: "internalLink", //内部链接
30                type: "object",
31                title: "Internal link",
32                fields: [
33                  {
34                    name: "reference",
35                    type: "reference",
36                    title: "Reference",
37                    to: [
38                      { type: "blog" },
39                      // other types you may want to link to
40                    ],
41                  },
42                ],
43              },
44            ],
45          },
46        },
47        
48      ],
49      validation: (Rule) => Rule.required(),
50    }),

三、使用sanity
  • 需要到sanity studio查看自己的project id和数据库名称,并且在项目的.env文件中配置
1NEXT_PUBLIC_SANITY_PROJECT_ID=""
2NEXT_PUBLIC_SANITY_DATASET=""
  • 下载npm i @portabletext/react,portabletext可以支持sanity返回的type为block的内容
1import {PortableText} from '@portabletext/react'
2
3<PortableText
4 value={blog.content}
5 components={PortableComponents}
6/>Ï
1import SyntaxHighlighter from "react-syntax-highlighter";
2import { tomorrowNightBright } from "react-syntax-highlighter/dist/esm/styles/hljs";
3import { urlForImage } from "../../sanity/lib/image";
4import Image from "next/image";
5
6interface CodeBlockProps {
7  value: {
8    code: string;
9    language: string;
10  };
11}
12
13// for support code block
14const CodeBlock = ({ value }: CodeBlockProps) => {
15  const { code, language } = value;
16  return (
17    <SyntaxHighlighter
18      showLineNumbers={true}
19      showInlineLineNumbers={true}
20      language={language}
21      style={tomorrowNightBright}
22      customStyle={{
23        padding: "1em",
24        marginBottom: "2em",
25      }}
26    >
27      {code}
28    </SyntaxHighlighter>
29  );
30};
31
32const PortableComponents = {
33  types: {
34    // for support image
35    image: ({ value }: any) => {
36      return (
37        <Image src={urlForImage(value)} alt="Blog" width={700} height={700} />
38      );
39    },
40    // for support code block
41    code: ({ value }: any) => {
42      return <CodeBlock value={value} />;
43    },
44  },
45  // for support text color and highlight
46  marks: {
47    textColor: ({ children, value }: any) => (
48      <span style={{ color: value.value }}>{children}</span>
49    ),
50    highlightColor: ({ children, value }: any) => (
51      <span style={{ background: value.value }}>{children}</span>
52    ),
53  },
54};
55
56export default PortableComponents;
57
  • 获取sanity数据库中的内容,举个🌰
1export const getBlogDetailBySlug = async (slug: string): Promise<BlogFace> => {
2  
3  try {
4    const sql = `*[_type == "blog" && slug.current == "${slug}"][0]{
5      title,
6      slug,
7      publishDate,
8      description,
9      content,
10      tags[]->{_id,slug,name}
11    }`;
12    const result = await client.fetch(sql);
13    return result;
14  } catch (error) {
15    console.error("Failed to fetch blog data:", error);
16    throw error;
17  }
18};
19