教程:使用 React 和 Next.js 运行由 Sanity 支持的博客 1. 安装 Sanity 和预配置的博客模式 2. 安装 Next.js 并运行它 3. 制作动态页面模板 4. 从 Sanity 获取一些内容 5. 添加包含作者和类别的署名 6. 添加富文本内容

2025-06-07

教程:使用 React 和 Next.js 运行 Sanity 支持的博客

1. 安装 Sanity 和预配置的博客架构

2. 安装 Next.js 并运行

3.制作动态页面模板

4. 从Sanity获取一些内容

5. 添加署名,注明作者和类别

6.添加富文本内容

有时你只需要一个博客。虽然博客平台种类繁多,但将博客内容与其他内容(例如文档(如本例)、产品、作品集或其他内容)放在一起可能很有道理。博客的内容模型(或数据模式)也是使用 Sanity 和独立前端轻松创建无头页面的起点。

在本教程中,我们将创建一个以Sanity作为内容后端、以基于 React 的框架 Next.js 来渲染网页的博客。

1. 安装 Sanity 和预配置的博客架构

如果您还没有这样做,请使用 npm 安装 Sanity 命令行 (CLI) 工具。

npm i -g @sanity/cli.
Enter fullscreen mode Exit fullscreen mode

这允许您在项目文件夹中运行该sanity init命令,事实上,这是下一步。您将被要求创建一个 Google 或 Github 帐户。之后,您可以创建一个新项目,并要求您选择一个项目模板。请选择博客架构模板。不过,首先,您需要为您的项目和数据集命名(如果您需要用于测试的数据集,可以添加更多数据集),并选择内容工作室文件的存储路径。

$ Select project to use: Create new project
$ Informal name for your project: sanity-tutorial-blog
$ Name of your first data set: production
$ Output path: ~/Sites/sanity-tutorials/blog
$ Select project: template Blog (schema)
Enter fullscreen mode Exit fullscreen mode

安装完成后,您可以运行sanity start并启动 Content Studio,开始编辑内容。这些内容将立即同步到云端,并在您点击“发布”后通过 API 访问。运行后,sanity deploy您将上传该 Studio,并将其发布到 Web 上,供有访问权限的用户使用(您可以通过访问manage.sanity.io添加用户)。

现在,您可以利用存储在项目文件夹中的 Schema 做很多事情schemas/schema.js,但那是另一个教程的内容了。现在,我们只想让博客正常运行!

2. 安装 Next.js 并运行

Next.js (由Zeit公司开发)提供了一个简洁的设置,用于制作基于 React 的网页,该网页在首次请求时即可进行服务器渲染,并具有许多其他有用的功能。如果您习惯使用 React,或者尝试过 create-react-app,那么上手应该不难。

为你的前端文件创建一个文件夹,然后运行以下命令npm init为你的项目创建一个 package.json 文件。之后,使用以下命令安装你的 Next.js 依赖项:

npm install --save next react react-dom
Enter fullscreen mode Exit fullscreen mode

并将以下内容添加到你的 package.json 中:

{
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  }
}
Enter fullscreen mode Exit fullscreen mode

Next.js 根据您在文件系统上存放文件的位置进行路由。因此,如果您添加一个名为 的文件夹pages并添加到其中,index.js它将成为您网站的首页。同样,如果您添加about.js,则在启动项目后,/pages它将显示在 上[localhost:3000/about](http://localhost:3000)。为了确保一切就绪,请尝试将以下代码添加到pages/index.js,然后在您的 shell 中输入npm run dev

const Index = (props) => <div>Hello world!</div>

export default Index
Enter fullscreen mode Exit fullscreen mode

现在,如果您在浏览器中访问localhost:3000,您应该会向世界致以问候。

3.制作动态页面模板

到目前为止一切顺利,但现在到了最有趣的部分:让我们从 Sanity 获取一些内容,并在 React 中渲染。首先安装连接到 Sanity API 所需的依赖项:npm install @sanity/client --save。在根 frontend 文件夹中创建一个名为 的新文件client.js。打开该文件并输入以下内容:

import sanityClient from '@sanity/client'

export default sanityClient({
  projectId: 'your-project-id', // you can find this in sanity.json
  dataset: 'production', // or the name you chose in step 1
  useCdn: true // `false` if you want to ensure fresh data
})
Enter fullscreen mode Exit fullscreen mode

为每个新的博客文章添加一个新文件是不切实际的,甚至很麻烦。所以,让我们创建一个页面模板,以便我们能够使用 Sanity 中的 URL 段。遗憾的是,Next.js 并没有提供开箱即用的动态页面。为了解决这个问题,我们必须添加一些服务器代码。让我们重用 Sanity 中的代码,nextjs/examples并在根文件夹中添加一个server.js文件,代码如下:

const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const pathMatch = require('path-match')

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
const route = pathMatch()
const match = route('/blog/:slug')

app.prepare()
  .then(() => {
    createServer((req, res) => {
      const { pathname, query } = parse(req.url, true)
      const params = match(pathname)
      if (params === false) {
        handle(req, res)
        return
      }
      // assigning `query` into the params means that we still
      // get the query string passed to our application
      // i.e. /blog/foo?show-comments=true
      app.render(req, res, '/blog', Object.assign({}, query, params))
    })
      .listen(port, (err) => {
        if (err) throw err
        console.log(`> Ready on http://localhost:${port}`)
      })
  })
Enter fullscreen mode Exit fullscreen mode

明白了👆如果您对
进行更改,则必须重新启动 npm run dev server.jspackage.json

您还应该运行npm install http url path-match --save以获取必要的依赖项,并将脚本部分更改为package.json

{
  "scripts": {
    "dev": "node server.js",
    "build": "next build",
    "start": "NODE_ENV=production node server.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

您的前端文件夹现在应如下所示:

~/blog/frontend
# install tree with homebrew install tree
$ tree -I node_modules
.
├── client.js
├── package-lock.json
├── package.json
└── pages
    ├── blog.js
    └── index.js
├── server.js
1 directory, 6 files
Enter fullscreen mode Exit fullscreen mode

Next.js 带有一个名为 的特殊函数,getInitialProps该函数在渲染模板之前被调用,并将 props 返回给 React 组件/pages。这是获取页面所需数据的理想位置。

👆
getInitialProps 仅适用于 pages 文件夹中的文件,并且用于路由,也就是说,它不会被包含在这些页面中的 React 组件调用。更多信息请参阅 Next.js 文档

编写 React 页面组件的方法有很多种,这只是一个简化的示例,方便您轻松上手。将以下内容添加到 blog.js 中。这里我们将 slug 设置为标题,以便在添加从 Sanity 获取内容的代码之前测试代码是否正常工作:

const Post = ({ title = 'No title' }) => (
  <div>
    <h1>{title}</h1>
  </div>
)

Post.getInitialProps = ({ query: { slug = '' } }) => { 
  const title = slug
  return { title }
}

export default Post
Enter fullscreen mode Exit fullscreen mode

如果你去,[localhost:3000/blog/whatever](http://localhost:3000/blog/whatever)你现在应该会看到页面上打印为 H1 的“whatever”。

4. 从Sanity获取一些内容

现在,我们已经为 Next.js 设置好了首页模板(index.js),以及一个自定义服务器,使得 blog.js 模板能够将 /blog/ 下的 slug 作为查询。现在,有趣的部分开始了,让我们为它添加一些 Sanity 功能:

import client from '../client'

const BlogPost = ({ title = 'No title' }) => (
  <div>
    <h1>{title}</h1>
  </div>
)

BlogPost.getInitialProps = async ({ query: { slug } }) => {
  const { title } = await client.fetch('*[_type == "post" && slug.current == $slug][0]', { slug })
  return { title }
}

export default BlogPost
Enter fullscreen mode Exit fullscreen mode

我们使用 async/await 是因为我们正在进行异步 API 调用,因为它使代码更容易理解。client.fetch()接受两个参数:一个查询和一个带有参数和值的对象。

专业提示
本教程中的 GROQ 语法可以这样理解:

*👈 选择所有文档

[_type == 'post' && slug.current == $slug]👈 将选择范围缩小到类型为“post”的文档,以及其中与我们在参数中拥有相同 slug 的文档

[0]👈选择列表中第一个且唯一一个

为了让前端服务器能够真正从 Sanity 获取数据,我们必须将其域名添加到CORS-settings中。换​​句话说,我们必须将其localhost:3000(以及最终托管博客的域名)添加到 Sanity 的 CORS 源设置中。如果您进入sanity manageshell,您将被带到浏览器中的项目设置页面。导航到设置页面并添加http://localhost:3000为新源。

现在您可以在 Sanity 中创建并发布至少带有 slug 和 title 的帖子:

内容工作室中的一篇帖子,标题栏中写着“Hello World”

访问http://localhost:3000/hello-world并确认 H1 拼写正确为“Hello world!”。现在,您已成功将前端连接到 Sanity。🎉

5. 添加署名,注明作者和类别

在内容工作室中,你会发现可以添加作者和类别条目。请至少添加一位作者,并附上图片。

内容工作室已填写作者姓名和图像

返回您的博客文章,并在“作者”字段中附加此作者,如下所示:

在参考文献字段中输入 Knut Melvær 作为作者的动画 GIF

发布更改,然后返回代码编辑器。我们刚才的操作是从博客文章中引用作者。引用是 Sanity 的一个强大功能,它使得跨类型连接和重用内容成为可能。如果检查你的块文档 ( ctrl + alt/opt + i),你会看到该对象如下所示:

"author": {
  "_ref": "fdbf38ad-8ac5-4568-8184-1db8eede5d54",
  "_type": "reference"
}
Enter fullscreen mode Exit fullscreen mode

如果我们现在直接提取作者变量 ( ),我们就能得到这样的内容const { title, author } = await client.fetch('*[slug.current == $slug][0]',{ slug }),但在本例中,它对我们来说用处不大。这时,GROQ 中的投影功能就派上用场了。投影是 GROQ 的一个强大功能,它允许我们根据需求指定 API 响应。

import client from '../client'

const BlogPost = ({ title = 'No title', name = 'No name' }) => (
  <div>
    <h1>{title}</h1>
    <span>By {name}</span>
  </div>
)

BlogPost.getInitialProps = async ({ query: { slug } }) => {
  const document = await client.fetch('*[_type == "post" && slug.current == $slug][0]{title, "name": author->name}', { slug })
  return document
}

export default BlogPost
Enter fullscreen mode Exit fullscreen mode

这里我已将投影添加{title, "name": author->name}到查询中。这里我指定了在 API 调用中希望返回文档中的内容。我们需要为作者姓名创建一个键,并用箭头 跟随作者文档中 name 属性的引用->。换句话说,我们要求 Sanity 跟随 下的 id _ref,并仅返回从该文档调用的变量的值name

让我们尝试对类别进行同样的操作。首先,在 Content Studio 中创建至少两个类别。我添加了一个 Next.js 类别,另一个是Tutorials类别。

Next.js 和教程作为数组字段中的类别

现在,我们有一个指向博客文章类别的数组。如果你查看文档检查器,你会发现它们和作者条目一样,都是带有_ref-id 的对象。所以我们也必须使用投影来获取它们。

import client from '../client'


const BlogPost = ({ title = 'No title', name = 'No name', categories = [] }) => (
  <div>
    <h1>{title}</h1>
    <span>By {name}.</span>
    {categories && (
      <ul>Posted in
        { categories.map(category => (
          <li key={category}>{category}</li>
        ))}
      </ul>
      )
    }
  </div>
)

BlogPost.getInitialProps = async ({ query: { slug } }) => {
  const document = await client.fetch('*[_type == "post" && slug.current == $slug][0]{title, "name": author->name, "categories": categories[]->title}', { slug })
  return document
}

export default BlogPost
Enter fullscreen mode Exit fullscreen mode

类别的投影与作者的投影基本相同,唯一的区别是我在关键类别后面附加了方括号,因为它是一个引用数组。

但是我们也想把作者的照片添加到署名中!Sanity 中的图片和文件资源也是引用,这意味着如果我们要获取作者图片,首先必须找到作者文档和图片资源的引用。我们可以通过访问 直接获取 imageUrl "imageUrl": author->image.asset->url,但在这种情况下,使用我们自己制作的图片 url 包会更方便。使用 ,在前端项目中安装该包npm i --save @sanity/image-url。它会获取图片对象并确定图片的获取位置,这使得使用焦点功能等也更加容易。

import client from '../client'
import imageUrlBuilder from '@sanity/image-url'
const builder = imageUrlBuilder(client)

function urlFor(source) {
  return builder.image(source)
}

const BlogPost = ({ title = 'No title', name = 'No name', categories = [], authorImage = {} }) => (
  <div>
    <h1>{title}</h1>
    <span>By {name}.</span>
    {categories && (
      <ul>Posted in
        { categories.map(category => (
          <li key={category}>{category}</li>
        ))}
      </ul>
      )
    }
    <div>
      <img src={urlFor(authorImage).width(50).url()} />
    </div>
  </div>
)

BlogPost.getInitialProps = async ({ query: { slug } }) => {
  const document = await client.fetch(`*[_type == "post" && slug.current == $slug][0]{
      title,
      "name": author->name,
      "categories": categories[]->title,
      "authorImage": author->image
    }`, { slug })
  return document
}

export default BlogPost
Enter fullscreen mode Exit fullscreen mode

输入图像 URL 构建器的代码行后,我们可以在函数中发送来自 Sanity 的图像对象,并在末尾urlFor()附加不同的方法(例如.width(50))和方法。.url()

6.添加富文本内容

如果没有对文本内容的强大支持,博客就无法算作博客。Sanity 中富文本的结构使其能够应用于多种场景:从浏览器中的 HTML 到语音界面中的语音实现。关于 block-content 及其可扩展性,有很多值得探讨的地方,但在本教程中,我们将仅使用 block-content -to-react软件包中自带的开箱即用功能。使用 进行安装npm install --save @sanity/block-content-to-react

import BlockContent from '@sanity/block-content-to-react'
import imageUrlBuilder from '@sanity/image-url'
import client from '../client'
const builder = imageUrlBuilder(client)
function urlFor(source) {
  return builder.image(source)
}
const BlogPost = ({ title = 'No title', name = 'No name', categories = [], authorImage = {}, body = [] }) => (
  <div>
    <h1>{title}</h1>
    <span>By {name}.</span>
    {categories && (
      <ul>Posted in
        { categories.map(category => (
          <li key={category}>{category}</li>
        ))}
      </ul>
      )
    }
    <div>
      <img src={urlFor(authorImage).width(50).url()} />
    </div>
    <BlockContent
      blocks={body}
      imageOptions={{w: 320, h: 240, fit: 'max'}}
      projectId={client.clientConfig.projectId}
      dataset={client.clientConfig.dataset}
    />
  </div>
)

BlogPost.getInitialProps = async ({ query: { slug } }) => {
  const document = await client.fetch(`*[_type == "post" && slug.current == $slug][0]{
      title,
      "name": author->name,
      "categories": categories[]->title,
      "authorImage": author->image,
      body
    }`, { slug })
  return document
}

export default BlogPost
Enter fullscreen mode Exit fullscreen mode

我们将 react-component 导入为BlockContent,并从 post-document 中获取 body 。我们将 body 以 的形式发送,并从 中blocks-prop添加数据集,以便让组件知道从哪里获取可能出现在富文本字段中的图像。projectIDclient-configBlockContent

我还添加了一个名为 的 prop imageOptions,用于控制图像的默认输出。就是这样!您可以自定义不同元素的输出,甚至可以通过传入我们称之为“序列化器”的函数来添加您自己的自定义块类型——我们将在另一篇博文中介绍这些函数。

网页展示了通过 Next.js 渲染的 Sanity 内容

这篇教程就到这里!现在,我们已经了解了如何为一个非常常见的内容设置编写前端层,而这仅仅是触及了 Sanity 和 React 结合所能实现的功能和精妙之处的冰山一角。

您可以从 GitHub下载示例项目,并随时在Gitter上向我们提问,或者通过其他方式找到我们

文章来源:https://dev.to/sanity-io/tutorial-run-a-sanity-backed-blog-with-react-and-nextjs-1eek
PREV
移居德国
NEXT
面向 Java 开发者的 Kubernetes - 设置 k8s 克隆此存储库 先决条件 安装工具