在本教程中,你将使用 Nuxt.js 和 Sanity.io 构建博客应用并部署到 Cloudflare Pages。Nuxt.js 是基于 Vue.js 前端框架构建的强大静态站点生成器。Sanity.io 是无头 CMS 工具,用于管理应用数据而无需维护数据库。
- 计算机上安装较新版本的 npm ↗
- Sanity.io ↗ 账户
要开始,使用 Sanity 模板之一创建新的 Sanity 项目——博客模板。若要自定义配置,可以修改 schema 或选择自定义模板。
通过从 npm 安装 @sanity/cli 客户端并在终端运行 sanity init 创建新的 Sanity 项目:
npm i @sanity/cliyarn add @sanity/clipnpm add @sanity/clibun add @sanity/clinpx sanity inityarn sanity initpnpm sanity init创建 Sanity 项目时,可以选择使用预定义 schema 之一。Schema 描述 Sanity 数据集中数据的形状——若从头开始新项目,可以选择从头初始化 schema,但现在请选择 Blog(博客) schema。
项目创建后,可以进入文件夹并在本地启动 studio:
cd my-sanity-projectnpx sanity startyarn sanity startpnpm sanity startSanity studio 是你可以为数据集创建新记录的地方。默认情况下,本地运行 studio 可在 localhost:3333 访问——现在前往该地址创建作者记录。你也可以在此创建博客文章。
准备部署 studio 时,运行 sanity deploy 为 studio 选择唯一 URL。这意味着你(或你邀请管理博客的任何人)可以通过 yoururl.sanity.studio 域访问 studio。
npx sanity deployyarn sanity deploypnpm sanity deploy部署 Sanity studio 后:
- 进入 Sanity 管理面板 (manage.sanity.io ↗)。
- 找到你的项目。
- 选择 API。
- 将
http://localhost:3000添加为项目的允许 CORS 来源。
这意味着来自 Nuxt 应用对 Sanity 数据集的请求将被列入允许名单。
接下来,创建 Nuxt.js 项目。在新终端中,使用 create-nuxt-app 设置新的 Nuxt 项目:
npx create-nuxt-app blogyarn dlx create-nuxt-app blogpnpx create-nuxt-app blog重要的是,在设置过程中确保选择 Universal (SSR / SSG)(通用(SSR / SSG)) 渲染模式和 Static (Static/JAMStack hosting)(静态(静态/JAMStack 托管)) 部署目标。
完成项目后,cd 进入新项目,通过运行 yarn dev(或若选择 npm 作为包管理器,则运行 npm run dev)启动本地开发服务器:
cd blognpm run devyarn run devpnpm run devNuxt.js 应用设置完成后,向 Nuxt 项目添加 Sanity 的 @sanity/nuxt 插件:
npm i @nuxtjs/sanity @sanity/clientyarn add @nuxtjs/sanity @sanity/clientpnpm add @nuxtjs/sanity @sanity/clientbun add @nuxtjs/sanity @sanity/client要在 Nuxt.js 应用中配置插件,你需要提供一些配置详情。最简单的方法是将 studio 中的 sanity.json 文件夹复制到应用目录(但也有其他方法:参阅 @nuxt/sanity 文档 ↗)。
cp ../my-sanity-project/sanity.json .最后,在 Nuxt 配置中将 @nuxtjs/sanity 添加为 build module:
{
buildModules: ["@nuxtjs/sanity"];
}在应用中配置 Sanity 后,可以开始使用它渲染博客。你将设置几个页面从 Sanity API 拉取数据并渲染。请注意,若不熟悉 Nuxt,建议阅读 Nuxt 指南 ↗,了解使用 Nuxt 构建应用的基本概念。
首先,更新 index 页面,访问根路由(/)时渲染。在 pages/index.vue 中:
<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:
<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 的每个对象:
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:
<script>
export default {
async asyncData({ params, $sanity }) {
console.log(params); // { slug: "hello-world" }
},
};
</script>考虑到这一点,你可以构建 pages/_slug.vue,获取传入的 slug 值,向 Sanity 查询匹配的博客文章,并渲染博客文章的 post 标题:
<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 查询,查找 _type 为 post 且 slug 匹配 /hello-world 的对象。从该集合中,可以获取数组中的第一个对象(使用 JavaScript 中的数组索引运算符 [0]),并将其设置为页面数据中的 post。
你已渲染博客的 post 标题,但仍缺少博客文章本身的内容。要渲染此内容,导入 sanity-blocks-vue-component ↗ 包,它将 Sanity 的 Portable Text ↗ 格式渲染为 Vue 组件。
首先,安装 npm 包:
npm i sanity-blocks-vue-componentyarn add sanity-blocks-vue-componentpnpm add sanity-blocks-vue-componentbun add sanity-blocks-vue-component安装包后,创建 plugins/sanity-blocks.js,导入组件并将其注册为 Vue 组件 block-content:
import Vue from "vue";
import BlockContent from "sanity-blocks-vue-component";
Vue.component("block-content", BlockContent);在 Nuxt 配置 nuxt.config.js 中,将该文件作为 plugins 指令的一部分导入:
{
plugins: ["@/plugins/sanity-blocks.js"];
}在 pages/_slug.vue 中,现在可以使用 <block-content> 组件渲染内容。这采用自定义 HTML 组件的形式,接受三个参数::blocks 指定渲染内容(本例中为 child),v-for 接受获取 child 来源的迭代器(本例中为 post.body),:key 通过为每篇文章提供唯一值(即 _id 值)帮助 Vue 跟踪状态渲染 ↗。
<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 组件渲染内容摘要,取博客文章内容的第一块并渲染:
<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 发布项目分两步:首先将项目推送到 GitHub,然后在 Cloudflare Pages 仪表板中基于该 GitHub 仓库设置新项目。Pages 将在每次发布时部署新版本,并在打开新 pull request 时设置预览部署。
要将项目推送到 GitHub,创建新仓库 ↗,并按照说明将本地 Git 仓库推送到 GitHub。
将项目推送到 GitHub 后,将站点部署到 Pages:
-
在 Cloudflare 仪表板中,前往 Workers & Pages 页面。
Go to Workers & Pages ↗ -
选择 Create application(创建应用程序) > Pages > Import an existing Git repository(导入现有 Git 仓库)。
-
选择你创建的新 GitHub 仓库,然后选择 Begin setup(开始设置)。
-
在 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.io 数据集时,Sanity 将请求你的唯一 Deploy Hook URL,从而开始新的 Cloudflare Pages 部署。这样,Pages 应用将保持最新,无论你添加新博客文章还是编辑现有文章。
完成本指南后,你已成功部署由 Nuxt、Sanity.io 和 Cloudflare Pages 驱动的博客。你可以在 GitHub 上找到两个代码库的源代码:
- 博客前端:https://github.com/signalnerve/nuxt-sanity-blog ↗
- Sanity 数据集:https://github.com/signalnerve/sanity-blog-schema ↗
若你喜欢本教程,可能还有兴趣了解如何使用 Cloudflare Workers(我们强大的无服务器函数平台)增强现有站点。请参阅 使用 Pages Functions 为前端构建 API 教程 了解更多。