教程:使用 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.
这允许您在项目文件夹中运行该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)
安装完成后,您可以运行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
并将以下内容添加到你的 package.json 中:
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}
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
现在,如果您在浏览器中访问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
})
为每个新的博客文章添加一个新文件是不切实际的,甚至很麻烦。所以,让我们创建一个页面模板,以便我们能够使用 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}`)
})
})
明白了👆如果您对或
进行更改,则必须重新启动 npm run dev 。server.js
package.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"
}
}
您的前端文件夹现在应如下所示:
~/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
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
如果你去,[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
我们使用 async/await 是因为我们正在进行异步 API 调用,因为它使代码更容易理解。client.fetch()
接受两个参数:一个查询和一个带有参数和值的对象。
专业提示
本教程中的 GROQ 语法可以这样理解:
*
👈 选择所有文档
[_type == 'post' && slug.current == $slug]
👈 将选择范围缩小到类型为“post”的文档,以及其中与我们在参数中拥有相同 slug 的文档
[0]
👈选择列表中第一个且唯一一个
为了让前端服务器能够真正从 Sanity 获取数据,我们必须将其域名添加到CORS-settings中。换句话说,我们必须将其localhost:3000
(以及最终托管博客的域名)添加到 Sanity 的 CORS 源设置中。如果您进入sanity manage
shell,您将被带到浏览器中的项目设置页面。导航到设置页面并添加http://localhost:3000
为新源。
现在您可以在 Sanity 中创建并发布至少带有 slug 和 title 的帖子:
访问http://localhost:3000/hello-world并确认 H1 拼写正确为“Hello world!”。现在,您已成功将前端连接到 Sanity。🎉
5. 添加署名,注明作者和类别
在内容工作室中,你会发现可以添加作者和类别条目。请至少添加一位作者,并附上图片。
返回您的博客文章,并在“作者”字段中附加此作者,如下所示:
发布更改,然后返回代码编辑器。我们刚才的操作是从博客文章中引用作者。引用是 Sanity 的一个强大功能,它使得跨类型连接和重用内容成为可能。如果检查你的块文档 ( ctrl + alt/opt + i
),你会看到该对象如下所示:
"author": {
"_ref": "fdbf38ad-8ac5-4568-8184-1db8eede5d54",
"_type": "reference"
}
如果我们现在直接提取作者变量 ( ),我们就能得到这样的内容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
这里我已将投影添加{title, "name": author->name}
到查询中。这里我指定了在 API 调用中希望返回文档中的内容。我们需要为作者姓名创建一个键,并用箭头 跟随作者文档中 name 属性的引用->
。换句话说,我们要求 Sanity 跟随 下的 id _ref
,并仅返回从该文档调用的变量的值name
。
让我们尝试对类别进行同样的操作。首先,在 Content Studio 中创建至少两个类别。我添加了一个 Next.js 类别,另一个是Tutorials类别。
现在,我们有一个指向博客文章类别的数组。如果你查看文档检查器,你会发现它们和作者条目一样,都是带有_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
类别的投影与作者的投影基本相同,唯一的区别是我在关键类别后面附加了方括号,因为它是一个引用数组。
但是我们也想把作者的照片添加到署名中!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
输入图像 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
我们将 react-component 导入为BlockContent
,并从 post-document 中获取 body 。我们将 body 以 的形式发送,并从 中blocks-prop
添加数据集,以便让组件知道从哪里获取可能出现在富文本字段中的图像。projectID
client-config
BlockContent
我还添加了一个名为 的 prop imageOptions
,用于控制图像的默认输出。就是这样!您可以自定义不同元素的输出,甚至可以通过传入我们称之为“序列化器”的函数来添加您自己的自定义块类型——我们将在另一篇博文中介绍这些函数。
这篇教程就到这里!现在,我们已经了解了如何为一个非常常见的内容设置编写前端层,而这仅仅是触及了 Sanity 和 React 结合所能实现的功能和精妙之处的冰山一角。
您可以从 GitHub下载示例项目,并随时在Gitter上向我们提问,或者通过其他方式找到我们。
文章来源:https://dev.to/sanity-io/tutorial-run-a-sanity-backed-blog-with-react-and-nextjs-1eek