跳转到内容
搜索文档

使用 Nuxt.js 和 Sanity.io 在 Cloudflare Pages 上构建博客

最后更新 查看 MarkdownAgent 设置

在本教程中,你将使用 Nuxt.js 和 Sanity.io 构建博客应用并部署到 Cloudflare Pages。Nuxt.js 是基于 Vue.js 前端框架构建的强大静态站点生成器。Sanity.io 是无头 CMS 工具,用于管理应用数据而无需维护数据库。

前提条件

创建新的 Sanity 项目

要开始,使用 Sanity 模板之一创建新的 Sanity 项目——博客模板。若要自定义配置,可以修改 schema 或选择自定义模板。

安装 Sanity 并配置数据集

通过从 npm 安装 @sanity/cli 客户端并在终端运行 sanity init 创建新的 Sanity 项目:

npm i @sanity/cli
npx sanity init

创建 Sanity 项目时,可以选择使用预定义 schema 之一。Schema 描述 Sanity 数据集中数据的形状——若从头开始新项目,可以选择从头初始化 schema,但现在请选择 Blog(博客) schema。

检查 schema

项目创建后,可以进入文件夹并在本地启动 studio:

cd my-sanity-project
npx sanity start

Sanity studio 是你可以为数据集创建新记录的地方。默认情况下,本地运行 studio 可在 localhost:3333 访问——现在前往该地址创建作者记录。你也可以在此创建博客文章。

在 Sanity Project 仪表板中创建博客文章

部署数据集

准备部署 studio 时,运行 sanity deploy 为 studio 选择唯一 URL。这意味着你(或你邀请管理博客的任何人)可以通过 yoururl.sanity.studio 域访问 studio。

npx sanity deploy

部署 Sanity studio 后:

  1. 进入 Sanity 管理面板 (manage.sanity.io)。
  2. 找到你的项目。
  3. 选择 API
  4. http://localhost:3000 添加为项目的允许 CORS 来源。

这意味着来自 Nuxt 应用对 Sanity 数据集的请求将被列入允许名单。

Sanity 项目的 CORS 设置

创建新的 Nuxt.js 项目

接下来,创建 Nuxt.js 项目。在新终端中,使用 create-nuxt-app 设置新的 Nuxt 项目:

npx create-nuxt-app blog

重要的是,在设置过程中确保选择 Universal (SSR / SSG)(通用(SSR / SSG)) 渲染模式和 Static (Static/JAMStack hosting)(静态(静态/JAMStack 托管)) 部署目标。

完成项目后,cd 进入新项目,通过运行 yarn dev(或若选择 npm 作为包管理器,则运行 npm run dev)启动本地开发服务器:

cd blog
npm run dev

集成 Sanity.io

Nuxt.js 应用设置完成后,向 Nuxt 项目添加 Sanity 的 @sanity/nuxt 插件:

npm i @nuxtjs/sanity @sanity/client

要在 Nuxt.js 应用中配置插件,你需要提供一些配置详情。最简单的方法是将 studio 中的 sanity.json 文件夹复制到应用目录(但也有其他方法:参阅 @nuxt/sanity 文档)。

Adding sanity.jsonsh
cp ../my-sanity-project/sanity.json .

最后,在 Nuxt 配置中将 @nuxtjs/sanity 添加为 build module

nuxt.config.jsjs
{
	buildModules: ["@nuxtjs/sanity"];
}

设置组件

在应用中配置 Sanity 后,可以开始使用它渲染博客。你将设置几个页面从 Sanity API 拉取数据并渲染。请注意,若不熟悉 Nuxt,建议阅读 Nuxt 指南,了解使用 Nuxt 构建应用的基本概念。

设置首页

首先,更新 index 页面,访问根路由(/)时渲染。在 pages/index.vue 中:

pages/index.vuehtml
<template>
	<div class="container">
		<div>
			<h1 class="title">My Blog</h1>
		</div>
		<div class="posts">
			<div v-for="post in posts" :key="post._id">
				<h2><a v-bind:href="post.slug.current" v-text="post.title" /></h2>
			</div>
		</div>
	</div>
</template>

<script>
	import { groq } from "@nuxtjs/sanity";

	export default {
		async asyncData({ $sanity }) {
			const query = groq`*[_type == "post"]`;
			const posts = await $sanity.fetch(query);
			return { posts };
		},
	};
</script>

<style>
	.container {
		margin: 2rem;
		min-height: 100vh;
	}
	.posts {
		margin: 2rem 0;
	}
</style>

Vue SFC(单文件组件)是 Vue 的独特功能,允许将 JavaScript、HTML 和 CSS 组合在单个文件中。在 pages/index.vue 中,提供了 template 标签,代表 Vue 组件。

重要的是,v-for 用作指令,告诉 Vue 为 posts 数组中的每个 post 渲染 HTML:

Inspecting the v-for directivehtml
<div v-for="post in posts" :key="post._id">
	<h2><a v-bind:href="post.slug.current" v-text="post.title" /></h2>
</div>

要填充 posts 数组,使用 asyncData 函数,Nuxt 提供此函数进行异步调用(如网络请求)以填充页面数据。

$sanity 对象由 Nuxt 和 Sanity.js 集成提供,用于向 Sanity 数据集发出请求。通过调用 $sanity.fetch 并传递查询,可以从 Sanity 数据集检索特定数据,并将其作为页面数据返回。

若未使用过 Sanity,你可能不熟悉 GROQ——Sanity 提供的 GRaph Oriented Query 语言,用于与数据集交互。GROQ 是一种强大的语言,允许你告诉 Sanity API 从数据集中获取哪些数据。对于第一个查询,你将告诉 Sanity 检索数据集中 _type 值为 post 的每个对象:

A basic GROQ queryjs
const query = groq`*[_type == "post"]`;
const posts = await $sanity.fetch(query);

设置博客文章页面

index 页面为数据集中的每篇博客文章渲染链接,使用 slug 值设置博客文章的 URL。例如,若创建名为 "Hello World" 的博客文章并将 slug 设为 hello-world,Nuxt 应用应能处理 /hello-world 页面的请求,并从 Sanity 检索相应的博客文章。

Nuxt 内置支持此类页面,通过在 pages 中创建 _slug.vue 格式的文件。在页面的 asyncData 函数中,可以使用 params 参数引用 slug:

pages/_slug.vuehtml
<script>
	export default {
		async asyncData({ params, $sanity }) {
			console.log(params); // { slug: "hello-world" }
		},
	};
</script>

考虑到这一点,你可以构建 pages/_slug.vue,获取传入的 slug 值,向 Sanity 查询匹配的博客文章,并渲染博客文章的 post 标题:

pages/_slug.vuehtml
<template>
	<div class="container">
		<div v-if="post">
			<h1 class="title" v-text="post.title" />
			<div class="content"></div>
		</div>
		<h4><a href="/">← Go back</a></h4>
	</div>
</template>

<script>
	import { groq } from "@nuxtjs/sanity";

	export default {
		async asyncData({ params, $sanity }) {
			const query = groq`*[_type == "post" && slug.current == "${params.slug}"][0]`;
			const post = await $sanity.fetch(query);
			return { post };
		},
	};
</script>

<style>
	.container {
		margin: 2rem;
		min-height: 100vh;
	}

	.content {
		margin: 2rem 0;
		max-width: 38rem;
	}

	p {
		margin: 1rem 0;
	}
</style>

访问例如 /hello-world 时,Nuxt 将获取传入的 slug hello-world,并向 Sanity 发出 GROQ 查询,查找 _typepost 且 slug 匹配 /hello-world 的对象。从该集合中,可以获取数组中的第一个对象(使用 JavaScript 中的数组索引运算符 [0]),并将其设置为页面数据中的 post

渲染博客文章内容

你已渲染博客的 post 标题,但仍缺少博客文章本身的内容。要渲染此内容,导入 sanity-blocks-vue-component 包,它将 Sanity 的 Portable Text 格式渲染为 Vue 组件。

首先,安装 npm 包:

npm i sanity-blocks-vue-component

安装包后,创建 plugins/sanity-blocks.js,导入组件并将其注册为 Vue 组件 block-content

plugins/sanity-blocks.jsjs
import Vue from "vue";
import BlockContent from "sanity-blocks-vue-component";
Vue.component("block-content", BlockContent);

在 Nuxt 配置 nuxt.config.js 中,将该文件作为 plugins 指令的一部分导入:

nuxt.config.jsjs
{
	plugins: ["@/plugins/sanity-blocks.js"];
}

pages/_slug.vue 中,现在可以使用 <block-content> 组件渲染内容。这采用自定义 HTML 组件的形式,接受三个参数::blocks 指定渲染内容(本例中为 child),v-for 接受获取 child 来源的迭代器(本例中为 post.body),:key 通过为每篇文章提供唯一值(即 _id 值)帮助 Vue 跟踪状态渲染

pages/_slug.vuehtml
<template>
	<div class="container">
		<div v-if="post">
			<h1 class="title" v-text="post.title" />
			<div class="content">
				<block-content
					:blocks="child"
					v-for="child in post.body"
					:key="child._id"
				/>
			</div>
		</div>
		<h4><a href="/">← Go back</a></h4>
	</div>
</template>

<script>
	import { groq } from "@nuxtjs/sanity";

	export default {
		async asyncData({ params, $sanity }) {
			const query = groq`*[_type == "post" && slug.current == "${params.slug}"][0]`;
			const post = await $sanity.fetch(query);
			return { post };
		},
	};
</script>

<style>
	.container {
		margin: 2rem;
		min-height: 100vh;
	}

	.content {
		margin: 2rem 0;
		max-width: 38rem;
	}

	p {
		margin: 1rem 0;
	}
</style>

pages/index.vue 中,可以使用 block-content 组件渲染内容摘要,取博客文章内容的第一块并渲染:

pages/index.vuehtml
<template>
	<div class="container">
		<div>
			<h1 class="title">My Blog</h1>
		</div>
		<div class="posts">
			<div v-for="post in posts" :key="post._id">
				<h2><a v-bind:href="post.slug.current" v-text="post.title" /></h2>
				<div class="summary">
					<block-content
						:blocks="post.body[0]"
						v-bind:key="post.body[0]._id"
						v-if="post.body.length"
					/>
				</div>
			</div>
		</div>
	</div>
</template>

<script>
	import { groq } from "@nuxtjs/sanity";

	export default {
		async asyncData({ $sanity }) {
			const query = groq`*[_type == "post"]`;
			const posts = await $sanity.fetch(query);
			return { posts };
		},
	};
</script>

<style>
	.container {
		margin: 2rem;
		min-height: 100vh;
	}
	.posts {
		margin: 2rem 0;
	}
	.summary {
		margin-top: 0.5rem;
	}
</style>

博客 schema 中还有许多其他内容可以添加到项目。作为练习,考虑以下之一以继续加深对使用无头 CMS 构建的理解:

  • 创建 pages/authors.vue,渲染作者列表(类似 pages/index.vue,但针对 _type == "author" 的对象)
  • 阅读 Sanity 关于 在 GROQ 中使用引用 的文档,并在博客文章页面中渲染作者信息

使用 Cloudflare Pages 发布

使用 Cloudflare Pages 发布项目分两步:首先将项目推送到 GitHub,然后在 Cloudflare Pages 仪表板中基于该 GitHub 仓库设置新项目。Pages 将在每次发布时部署新版本,并在打开新 pull request 时设置预览部署。

要将项目推送到 GitHub,创建新仓库,并按照说明将本地 Git 仓库推送到 GitHub。

将项目推送到 GitHub 后,将站点部署到 Pages:

  1. 在 Cloudflare 仪表板中,前往 Workers & Pages 页面。

    Go to Workers & Pages ↗
  2. 选择 Create application(创建应用程序) > Pages > Import an existing Git repository(导入现有 Git 仓库)

  3. 选择你创建的新 GitHub 仓库,然后选择 Begin setup(开始设置)

  4. Set up builds and deployments(设置构建和部署) 部分,Build settings(构建设置) > Framework preset(框架预设) 下选择 Nuxt。Pages 将自动为你设置正确的字段。

站点部署后,你将收到唯一的 URL 以在生产环境中查看。

为了在 Sanity.io 数据变更时自动部署项目,可以使用 Deploy Hooks。在 Pages 项目 > Settings(设置) 中创建新的 Deploy Hook URL。在 Sanity 项目的 Settings 页面,找到 Webhooks(Webhook) 部分,添加 Deploy Hook URL,如下所示:

在 Sanity 仪表板上添加 Deploy Hook URL

现在,当你更改 Sanity.io 数据集时,Sanity 将请求你的唯一 Deploy Hook URL,从而开始新的 Cloudflare Pages 部署。这样,Pages 应用将保持最新,无论你添加新博客文章还是编辑现有文章。

结论

完成本指南后,你已成功部署由 Nuxt、Sanity.io 和 Cloudflare Pages 驱动的博客。你可以在 GitHub 上找到两个代码库的源代码:

若你喜欢本教程,可能还有兴趣了解如何使用 Cloudflare Workers(我们强大的无服务器函数平台)增强现有站点。请参阅 使用 Pages Functions 为前端构建 API 教程 了解更多。

这篇文档对您有帮助吗?