除本站自媒体官号以外,本文的内容禁止任一组织、个人、账号或平台,以任何形式转载到 RainyDesign.cn 站外。
任何个人或组织进行非法转载的,本站工作人员可对其追究法律责任。
具体信息详见 条例与条款 。
Next.JS + Strapi + Tailwind CSS 全套网建实战教学 —— Strapi
通过 Strapi 搭建所有的组件、页面与数据类型,设置权限并填充必要的内容,以便开始接口测试。
章节回顾
我们在上一个章节已经做好了准备工作,课程的完整目录如下:
-
网站部署 (撰写中)
网站部署:Cloudflare、Vercel或租赁云服务器(仍在撰写)
网站部署:注册域名 + SSL证书申请(仍在撰写)
SEO:搜索引擎提交收录(仍在撰写)
-
资源与教程项目包下载
点击此处下载,最后更新于:2026 年 5 月 26 日。为获取良好的学习体验,请务必以最新版本为主! - FAQ(常见疑问解答)
二、Strapi
Hello 宝子们,我是Rainy,今天我们继续来讲解本课程的第二章:Strapi 的基本知识。
2.1 Strapi:创建无头内容管理系统
首先为大家介绍 Headless CMS 的概念:CMS 是 Content Management System 的缩写,所以 Headless CMS 就是“无头内容管理系统”;顾名思义, Headless CMS 不像传统的建站系统那样包含了网站前台,而是只提供数据管理后台和 API 接口,不包含前端展示层,以此管理接口、权限、Webhook、Token 等内容。

开始前,我们需要先理解 Strapi 必须关联数据库,创建项目时它将自动完成结构与初始内容配置。
strapi-app 后面的 @... 可以不用输入。以下内容以 ~/Documents/my-project 目录为例。确保以上内容后,请在终端输入:
Terminal
代码已复制!
# 以下为终端命令行 cd ~/Documents # 回到用户的 Documents(文档)目录 mkdir my-project # 创建项目目录 cd my-project # 进入目录 npx create-strapi-app@5.23.1 strapi # 个人喜欢用 yarn,但是要安装特定版本必须用 npx,yarn 的命令为 yarn create strapi-app [project-name] (默认安装最新版)。具体请自查官方文档:https://strapi.io # 等待读条后出现以下选项 > Please log in or sign up. # 默认让你登录,直接跳过 Skip > Do you want to use the default database (sqlite) ? # 默认数据库是 SQLite,我们用 MySQL 所以 n n > Choose your default database client # 手动选择 MySQL mysql > Database name: # 这里要输入上个章节创建的数据库的名称 `my-project` > Host: (127.0.0.1) # 默认 [直接回车] > Port: (3306) # 默认 [直接回车] > Username: # 输入数据库的用户名 [Your mysql username] # (……宝子你不会没明白我意思吧?输入自己设置的 mysql 用户名) > Password: # 输入数据库的密码 [Your mysql password] # (同上) > Enable SSL connection? # 因为是本地部署所以暂时不用 SSL,后续是直接 http://127.0.0.1:1337 或 http://localhost:1337 访问本地 Strapi n > Start with an example structure & data? # 参考的数据结构与默认数据,默认为是 y > Start with Typescript? # 看着用,本教程不包含 Typescript 所以 n n > Install dependencies with npm? # 默认为是,也就会直接安装所有依赖 y > Initialize a git repository? # 默认为是,看着用 y > Participate in anonymous A/B testing (to improve Strapi)? # 默认为是,不想看 Strapi 用调的可以 n(Strapi 会偶尔弹出提示,但并不频繁)。 y # 等待读条完毕后显示 "Strapi Your application was created!" 并打印命令后继续 cd strapi # 进项目目录 npm install # 如果上一步没有安装依赖,则需要此时安装 npm run dev # 启动项目;之后也可以用 yarn dev 启动项目。
Okay,现在系统会自动切到你的默认浏览器并打开新标签页,以显示 Strapi 的注册界面。

Strapi 欢迎页
现在我们得先登录。因为是本地系统,所以账号密码也请自设:

创建你的账户并点选按钮开始
可能会弹出一个职业询问框,随便选一个职业点确认,之后就会如下图所示来到 Strapi 的欢迎页:

Strapi 欢迎页
这里简单介绍下 Strapi 的各个页面和功能:
| 页面名称 | 英文 | 功能与说明 |
|---|---|---|
| 主页 | Home | 欢迎、指南、事项、最后编辑、数据指标等 |
| 内容管理器 | Content Manager | 管理创建的各项数据结构中的内容 |
| 媒体库 | Media Library | 存储静态内容,它们都会被存放到项目下的 /public/uploads 目录中 |
| 内容类型构建器 | Content-Type Builder | 管理数据结构,仅在开发模式下(yarn run develop)可用,用于管理数据结构与接口 |
| 部署 | Deploy | Strapi 提供了官方付费托管方案,不过我们也可以用 CLI 自行部署,具体下述 |
| 插件广场 | Marketplace | Strapi 官方社区有很多适配版本的插件,可在这里查阅并安装 |
| 设置 | Settings | 管理各项设置 |
点进每个页面看一下,因为我们在创建 Strapi 项目的时候于 Start with an example structure & data? 选择了是,所以此处会发现 Strapi 已经默认设置了 Article、Author、Category、User 四个 Collection types,以及 About、Global 两个 Single types,并上传了一些图片到媒体库中,同时在组件分类下创建好了 Shared 目录与各个组件;如果选否则没有以上预设参考内容。
常见问题解决:
- 如果 Strapi 安装或启动失败:
- 安装时提示 node 版本号不正确;
切换 Node.JS 至合适的运行环境
- 查看 MySQL 数据库是否正在运行;
Unix OS:
brew services start mysql或mysql.server start,Windows OS:net start mysql- 检查数据库账号密码是否对应;
终端打
root -u _username_ -p,输入密码登陆后输入SHOW DATABASES查看- 是否错误启用了 SSL 连接。
不确定的话就重新走一遍 Strapi 安装流程。
如仍未解决问题,请查阅 Strapi 使用指南 - 故障排查 了解更多排障方案。
2.2 Strapi:管理组件与字段
本节我们将讲述如何通过 Strapi 直接管理上个章节创建的数据库。我们先点击左侧菜单栏的第二个图标(羽毛)访问 content manager。如图所示,由于先前在 参考的数据结构与默认数据 (Start with an example structure & data?) 这一项中默认选择了“是”,所以一些常见页面都已经自动创建好了:

Strapi Content Manager - 内容管理器
不过对应我们的 字段列表文档 所需的页面结构还需要手动创建一些,现在请访问内容类型构建器(左侧菜单栏第四个图标)进行数据结构管理。

Strapi Content-Type Builder 内容类型构建器
我们先了解一下左侧第二个侧边栏中的两种类型区别:集合类型 (Collection types) 、单一类型 (Single types) 与组件 (Components) 。众所周知,一个网站上可能会有很多博客文摘,以及对应的标签与作者,但关于我们、条款政策、联络、主页等页面通常只有一个,且导航与页尾也是通用的,所以我们可以对应 字段列表文档 中的数据结构,将各项数据表分类好:
Pages
Collection types
- Article
- Author
- Category
- Contact
- Subscription
Single types
- Global
- Home
- Course
- Blog
- About
Components
Course
- Courses
- Specialized courses
- Specialized courses contents
Global
- About Us
- Contact
- Footer
- Information
- Meta
- Navigation
- Social
- Subscription
Home
- Comparison content
- Comparison detail list
- Comparison informations
- Frameworks
- Guide chapter content
- Introduction
- Playground contents
Shared (default)
- Media (default)
- Quote (default)
- Rich text (default)
- Seo (default)
- Slider (default)
2.2.1 Strapi:创建全局组件
接下来,我们就要边参考 字段列表文档,边将 Strapi 中的每个页面填充起来。不过在此之前,我们需要先将数据类型创建并封装在各自的组件下,以便于后续管理以及全局调用。结合设计稿与字段列表文档来看,我们会发现全页有一组调用率非常高的数据类型,所以我们将其封装成一个专属子组件——Information ;它包含了常用的 heading、description、buttonText、buttonLink、openInNewTab、image 这六项字段,现在一起实操一下:
点击左侧 Components 栏目的加号创建一个新组件,以 Information 为例:

在弹出的窗口中,在组件显示名一栏下输入 Pascal Case 的 Information ,然后在右侧的目录中选择 global 以分类到全局目录下,再选择一个图标继续:

接下来我们需要在 Information 组件中点击 Add new field,然后在弹出窗口中为该组件添加一个名为 heading 的 text 数据类型,随后默认 Short text 不做修改,点击窗口右上角的 Advanced Settings 选项卡,切换至高级设置并勾选 Required field,以及将默认内容粘贴到 Default value 一项中来:


现在请继续创建下一个数据字段 description。作为一个长文本类型,这里要选择 Long text,也同样需要设置 Required field:


我们可以点击 Add another field 连续创建下一个字段,无需每次都确认保存,将 buttonText、 buttonLink 依次创建好,且可以在高级设置里头为 buttonText 设置对应的默认值(Start Guide >>)。注意与组件显示名不同的是,这些数据字段均需遵循 camelCase 命名规则:


接下来继续创建名为 openInNewTab 的 Boolean 类型,顾名思义用于控制终端点击按钮时是否打开新标签:

这里依然为必填项,如果仅站内跳转,默认值可以给个 false,如跳出站外建议设置为 true:

紧接着是入站背景图片 image 的设置,这是一个媒体类型,我们选择 media ,且由于我们只需要用到一张图片,所以选择 Single media:

在高级设置中仅允许图片类型:

点击 Finish 提交之后我们可以点左上角 Save 进行保存,此时 Strapi 会自动重新构建并刷新页面以应用更改(迟迟没有反应比如白屏的话可以手动刷新页面):

刷新页面后,我们能看到新的组件 Information 已于 Global 目录下添加:

之后添加好用于 HTML head 结构的 Meta,以及全局使用的关于信息 About Us 、联络 Contact 、页尾 Footer 、导航 Navigation 、社媒 Social 以及订阅 Subscription ,可以一并完成,稍后一次性提交。其中创建 About Us 时需注意与 Information 大致相同,但 Image 是 Multiple media 即复数类型,且为必填项:

此外可以按住并拖动左侧的排序按钮变动组件或字段排序:

而创建 Contact 这一组件时出现了 enum 这一类型,这里我们直接在 value 一项中通过换行区分不同类型即可:

还需注意部分组件需要在高级设置中设置最大字符长度,比如 Meta 中的 metaTitle 与 metaDescription:

此外,新建的组件会显示 New(左侧显示为绿色的 N,下同),有变动的组件会显示 Modified,删除组件则会提示 Deleted,保存时各项改动为一次性提交至Strapi,等待重启即可保存变动,具体方法同先前讲解的添加已有的组件:

对内容变动不满意的话还可以点击左侧栏目右边的菜单键,在下拉菜单中选择撤销上一步变动:

至此全局组件均已添加完毕,接下来开始搭建页面数据结构。

2.2.2 Strapi:创建单一类型数据结构
我们先配置全局数据结构,这些数据将在所有页面中可用。
打开 Single type 下的 Global 页面,我们会发现其默认创建好了几种数据类型,并调用了 Strapi 示例创建的 Shared 目录下的 Seo 组件(自带的内容可以先留着不删用于测试),我们直接添加 globalAddOn 与 globalMetaInjection 两种数据类型:

接下来我们来调用先前设置好的组件,此处以 Footer 为例,点击 Use an existing component 并继续,在窗口步骤 2/2 内命名为 footer ,并于右侧菜单 Select a component 中下拉选择 global - Footer ,然后选择类型为 Single component,最后不要忘了在 Advanced Settings 中勾选必填项:



再将先前创建好的其他组件如 Navigation 、 Contact 、 Social 、 About Us 、 Subscription 悉数调用:

这样全局数据就告一段落了,现在开始管理单一数据类型,先从 主页 (Home) 开始进行举例,请点击左侧 Single types 栏目的加号,在弹出的窗口中于 Display name 内输入 Home,其他内容默认不做修改,点击 Continue 按钮以继续创建:


接下来我们需要为主页添加一些专属组件。首先是 introduction ,我们点击 Add new field 然后在弹出窗口的最下方找到 Component:

这里的第一步和直接创建组件相同,直接在页面中创建组件时也会在 Components 中生成对应的新组件,所以我们需要采用 Pascal Case 将其命名为 Introduction ,并分类到新的 home 目录下:

下一步则和调用相同——选择类型为 Single component,最后不要忘了在 Advanced Settings 中勾选必填项:


点击 Add first field to the component 后为其添加数据类型:

搞定 Introduction 之后,接下来的 Frameworks 也是相同的逻辑,其区别只在于第二步需要选择 Repeatable Components 类型,再于 Advanced Settings 中设置数量限制,最小与最大均为 6 :


紧接着的 comparisons 引用的是 Global - Information 这个全局组件:

接下来讲讲包含两层后代组件的父组件 Comparison informations ,它的结构关系为:
- comparisonInformations (Home - Comparison informations)
- contents (Home - Comparison content, repeatable; min:3; max:3)
- detailList (Home - Comparison detailrlist, repeatable)
- contents (Home - Comparison content, repeatable; min:3; max:3)
我们先将父组件 Comparison informations 的数据类型创建好,直到需要创建子组件 Comparison contents 为止:

我们将其调用并命名为言简意赅的 contents ;由于这是个可复用组件,我们可以将其最大与最小数量均限制为 3 个以匹配项目需求:


现在我们将为子组件 Comparison content 创建数据字段,调用并命名为 contents。需注意其中各项字段的参数,如 ratings 与 downloads 均限制了字符串长度最大为 5 :


到了 detailList 这一项,我们需要为其创建 Comparison detail list 子组件,调用并命名为 detailList ;


完成的结构如图所示:

接下来的 guideChapters 引用的也是 Global - Information 这个全局组件:

确认无误后可以先保存一下,然后继续完成余下的数据类型。接下来的 guideChapterContents 、playground 、 playgroundContents 、seo 均没有新的知识点了,多做练习巩固一下:

完成主页后,我们按导航顺序的 Home - Course - Blog - About 来陆续完成其他页面。不过由于本项目的字段列表是事先设计好的,所以可以一口气在组件列表中创建余下的所有组件再行引入:


进行到 Blog 页面时出现了新字段类型 relation ,这些字段需要选择一些关联数据,其中 articlesOfTheDay 、 articlesOfTheDay2 为一对一的每日推荐文章,而推荐类目 featuredCategories 则为一对多:


最后是 About 页面内容,因为改动较大,所以需要将原有的字段全部删除并重新填写:

这样一来所有单页类型的字段设置就都完成了。

上图所示,蓝框为 Pascal Case 的组件命名,红框为 Camel Case 的组件字段
2.2.3 Strapi:调整字段布局
Okay,现在让我们回到 Content Manager 页面,确认 Home 是否已创建好并正确地显示了所有组件与字段:

内容正确,但布局看起来有点乱,此时我们需要点击右上角的菜单按钮,在下拉菜单中选择 Configure the view 进行布局管理:

我们会发现在这里无法直接控制名为 introduction 的组件中的字段布局,需要点下面的 Set the component's layout 来到组件布局页:

在这里我们可以自由地排列组件与其中字段的顺序,并点选编辑按钮进行大小控制以及显示 placeholder 与 description,或是切换字段启用状态:

按照你喜欢的布局进行调整并保存即可,再回到 home 页面查看下布局变动:


确认无误后,可以回到布局修改页面根据自己喜好改动所有布局并保存验证:

可按需为部分字段添加占位符以及标注,如可为 metaKeyword 添加使用逗号拆分关键词说明:

如有必要甚至可以将部分字段设置为不可编辑(即只读),以便提交内容或按照缺省值设置后锁定内容:


至此主页的组件与其中的字段也以创建完毕,接下来我们可以参考 Strapi 使用指南 - 字段列表 创建所有类型的页面组件与其中的字段,并管理各页面和组件的视图设置。请于全部完成后继续下一节课程内容。
2.3 Strapi:管理多媒体资源
点击左侧菜单栏的第三个按钮以打开媒体库,先前已经提到过,在配置阶段的参考内容选择了"是"的话,这里会默认上传一些图片。不过无论有没有这些图片,我们都需要为内容分个类,点击右上角的 Add new folder 按钮创建一个文件夹。


为了便于排序,我们需要依次创建带有序号的 0. Global、4. About Us、3. Blog、2. Course、1. Home,如图所示:

我们可以再在 0.Global 目录下新建一个 0. Temp 目录,用于将 Strapi 自带的 11 张图片存储进来,之后一些临时图片也可以上传到这里:

图片有 11 张,但媒体库页面默认每页仅显示 10 张,我们可以让一页显示的图片更多一点,在页面滚动到底部,于下方左侧 [number] Entries per page 中调整到 20 张 / 每页:

一张张手动勾选有点麻烦,我们可以点击右上角工具栏中的第二个按钮,将布局切换为列表模式,方便我们全选所有图片:

工具栏中的设置按钮(齿轮图标)可用于修改排序方式和调整每页显示的文件数量。选择完毕后将图片移动到 0. Global - 0. Temp 目录下:


/public/uploads 目录下,且不会随着 Strapi 创建文件夹自行分类。这是考虑到便于管理 Strapi 媒体库文件夹与静态内容,而将所有文件平铺于单个层级;上传文件时,Strapi 会自动为文件添加基于时间戳与哈希算法的命名,以确保文件名的唯一性,且图片会自动生成多种尺寸规格(如缩略图、小图、中图、大图),以适应不同使用场景。
然后是上传文件,点击右上角的 Add new assets 会弹出一个拖拽上传窗口:

我们可以从本地上传一些内容,或是点击 From URL 选项卡从网络地址获取内容:

确认文件后它们会被暂存到等待列表,我们可以将鼠标悬停在上面显示删除按钮,也可以点击编辑资源信息:

在资源细节窗口中,我们除了删除、下载、复制链接以及裁剪图片外,还可以查看资源信息、修改文件名称、设置 Alt 值与说明,还可直接将其分类到特定目录下:

点击右下角的 Finish 结束调整并保存,回到等待列表并点击右下角的 Upload * asset(s) to the library 以开始上传:

如需替换文件而非删除,可点击 Replace Media 替换图片,这样可以确保之前在 Strapi 中引用该文件的内容不会丢失资源链接:

这样一来我们就将文件上传到 Strapi 的 /public/uploads 目录下,并可以通过 URL 访问了:


为确保课程的顺利进行,我们需要下载 课程资源包 并解压内容,然后按文件夹分类上传至 Strapi:

请于上传完毕后继续下一节课程内容。
2.4 Strapi:填写内容、配置 API 权限并测试
本小节我们需要填写内容、设置好接口的访问权限,并介绍一些工具便于测试数据接口。
2.4.1 Single type 单一类型
先让我们从全局(Global)开始填写内容,先前采用预设参考值的话,这里会有 siteName、favicon、siteDescription、defaultSeo 这些字段且填写好了测试内容,不过无论有无这些字段,我们都应该在下面追加好了 globalAddOn、globalMetaInjection、navigation、footer、contact、social、aboutUs、subscription 这些组件与其中的字段:

现在我们一个个填写过来,首先 globalAddOn 顾名思义是全站附加值,比如 meta title 、image alt 都会用到一个网站后缀,格式如 “Next.JS + Strapi + Tailwind CSS 全套建站指南 | Rainy Design Studio 雨点设计工作室”,那么这里的“ | Rainy Design Studio 雨点设计工作室”就是一个重复性的、常见的全站附加值;一个近在眼前的例子是,我们可以将鼠标悬停到下图稍作等待来查看图片 Alt 值:

这里填写 globalAddOn 有助于 SEO 的规范性,能一键修改全站附加值,这大大减少了编辑图片 Alt 值的繁复性;下一个 globalMetaInjection 则是用于 Gtag 或 Meta Pixel 等需要全站 meta 注入 script 的预留字段项。可提到如此重要的字段,违反直觉的一点却是:这其实并不是一个必填选项,至少在测试阶段不需要,且修改这两项对于 Next.JS 的 SSG 而言意味着需要一次 rebuild,我们会在后续的课程中着重讲解 Next.JS 的 SSG 。
接下来的内容就显得很直白了——这里我们点击 footer 的添加按钮,如果你在本文小节 2.2.1 Strapi:创建全局组件 章节中按照流程所示,于内容类型构建器中为该组件设置了默认值,系统便会自动填充这些内容:


接下来继续填写 contact 和 social ,此处因为都是 Repeatable Component,所以可以连续创建多项;不过它们可能不以 type 作为入口名称(Entry title),而是默认拿取了 text 的值做显示:

现在编辑它需要跳转到布局页面,这会导致没有保存的改动丢失,所以我们需要先把 aboutUs 和 subscription 填好,然后点击 save 保存,再回来处理这一问题:


Strapi 报错,并自动填充了上一次提交的内容
如需启用该选项,请跳转至 Content-Type Builder ,点击页面后点击右上角的 Edit 按钮,并于弹出的窗口中选择 Advanced Settings 选项卡,再勾选该选项并提交保存即可。

接下来我们为组件设置正确的入口名称。通过点击右上角下拉菜单中的 Configure the view 打开布局页面,滚动到 contact 与 social 组件,然后点击 Set the component‘s layout 打开组件的布局页面:

我们能看到最上方的 Entry title 选中了 text,这里我们需要手动将其调整为 type 然后在右上角保存继续,这样回到 Global 页面中再次填写数据就会以 type 作为显示名称了:

接下来将内容陆续填好,暂时留空的部分比如链接可以填个 / ,点击 Save 保存提交,然后 Publish 一下,没有错误或漏填的话就可以顺利发布了:

So far so good,接下来继续完成其他 Single types 的内容填写,一些暂时没有数据的部分比如链接可以直接填写 / ,文字或富文本类型则可以先填入 Lorem ipsum 这类假字,图片也可以直接拿媒体资源库中 0. global - 0. temp 目录下的任意图片暂作测试,先 Save 然后点 Publish 检查是否有遗漏字段,依次填完后我们就可以对内容做测试了。
文字与图片资源均已打包在了 课程资源包 之中,请解压后使用
课程资源包/网站资源/下的内容。




2.4.2 Collection type 集合类型
参考用的数据结构与默认数据已经足够做测试了,所以我们暂时不需要填写。
Collection type 显示的是一个列表,此处以博客文章 Article 为例:

我们也可以通过点击工具栏上的小齿轮打开显示设置菜单:

与先前媒体资源库中显示图片数量的逻辑一样,在该菜单中直接切换字段的显隐并不会保存,如需设置并保存排序规则、页面文档数量与字段的显隐,则需要点击 Configure the view 打开显示配置页面:

我们可以如图所示将文章按照日期倒序排序,并显示文档 id 、标题、作者、封面与创建日期,然后保存查看:

OK,这样就设置好了,然后来测试一下查找功能:

再来设置两个筛选条件:

再分别进入 id 为 2,3,4 的文档并发布,以用作下文的 API 测试:

筛选与查找条件将会在切换集合类型后失效。
除此以外,我们还需要添加 Contact 与 Subscription 类型以作用户数据收集:


最后在 Article 与 Category 两个集合类型下方添加 seo (Global - Meta) 组件:


不要忘了进入两者的文档并保存 / 发布内容:




2.4.3 设置公共 API
完成以上内容后,我们需要去设置中对部分接口开放访问:点击左侧菜单栏的 Settings 访问设置页面,菜单滚动到最下方的 Users & Permissions Plugin 查看用户与权限功能,点击 Roles 打开角色窗口,再点击 Public 查看公共角色组,我们能看到所有的接口权限陈列于下方:

如我们在创建项目时启用了预设参考,则这里部分的接口权限已经设置好了,为做数据测试我们先点击 Global 展开内容,确保 find 已勾选,然后临时勾选下 update 做测试,点一下右侧的小齿轮可以查看 API 路由,然后在右上角点 Save 保存:

OK,现在我们就可以通过 REST API 直接访问 Global 的数据了 (http://127.0.0.1:1337/api/global ):

我们可以看到收到的 JSON 响应的 data 对象中除了自定义的字段数据外,还包含了系统自动生成的元数据字段 id、documentId、createdAt、updatedAt、publishedAt 这些隐藏内容,它们对于 Collection type 来说至关重要,会在后续课程中为宝子们陆续讲解。
现在继续说说如何测试接口;相信很多宝子们对 Postman 很熟悉,这里 Rainy 就不多做讲解了,B 站教程也相当的多;但如果你对新工具感兴趣的话,这里我便拿 Insomnia 作示范:

先 Get 一下数据做确认:

由于先前我们为 Global 启用了 update ,所以我们可以直接将数据 PUT 过去,此处将 Body 设置为 JSON 类型单传一项键对值即可:

可以看到接口回传的数据已修改成功;接下来请回到 Strapi 中的 Settings - Users & Permissions Plugin - Roles - Public 页面,将各页面的权限按照下表设置:
| 页面名称 | 页面类型 | 权限设置 | 说明 |
|---|---|---|---|
| About | Single type | find | 关于我们 - 单页,公共权限为仅读取 |
| Article | Collection type | find; findOne | 博客文章 - 合集,公共权限为读取列表及内容 |
| Author | Collection type | find; findOne | 作者 - 合集,公共权限为读取列表及内容 |
| Blog | Single type | find | 博客,公共权限为仅读取 |
| Category | Collection type | find; findOne | 分类 - 合集,公共权限为读取列表及内容 |
| Contact | Collection type | create | 联络 - 合集,公共权限为仅提交创建 |
| Course | Single type | find | 课程 - 单页,公共权限为仅读取 |
| Global | Single type | find | 全局内容 - 单页,公共权限为仅读取 |
| Home | Single type | find | 主页 - 单页,公共权限为仅读取 |
| Subscription | Collection type | create | 订阅 - 合集,公共权限为仅提交创建 |
其余内容均不做改动,修改完毕后点 Save 保存。
2.5 Strapi:常用 API 讲解,实现增删改查及筛排分页
由于我们是基于 HTTP 标准考量整个项目的开发逻辑,搭建一个网站而非一个 H5 应用,所以相对于 GraphQL ,我们更建议选用 REST API 进行前端开发。以下是 Google Gemini 给出的 API 对比结论:
| 功能 | 如果出现以下情况,请选择 REST | 如果出现以下情况,请选择 GraphQL |
|---|---|---|
| 数据获取 | 您的数据需求简单且可预测,并且资源从不同的 URL 获取。可以对相关数据发出多个请求。 | 您需要在单个请求中获取复杂、嵌套或相关的数据。这对于最大限度地减少往返次数非常理想,从而有利于提高数据密集型应用的性能。 |
| API 结构 | 您更喜欢更简单、面向资源且具有多个不同端点的架构。这符合 HTTP 标准,易于理解。 | 您希望客户端使用查询语言使用单个端点来定义其数据需求。这允许灵活的、客户端驱动的数据获取。 |
| 性能 | HTTP 缓存具有高优先级。REST 受益于标准 HTTP 缓存机制,因为它的端点是可预测的。 | 您需要减少网络开销,尤其是在移动客户端或带宽有限的环境中。 GraphQL 通过仅获取请求的数据来消除过度获取。 |
| 客户端 | 您或您的团队已经熟悉标准 HTTP 请求(例如,使用原生 fetch API、Axios)。 | 您愿意设置和管理 GraphQL 客户端库(例如,Apollo Client)以实现缓存、突变和实时订阅等高级功能。 |
| 实时数据 | 您的应用程序不需要实时更新。如果需要,您需要使用单独的技术,例如 WebSockets。 | 您需要内置对通过订阅进行实时数据流传输的支持,这对于实时聊天或通知等功能至关重要。 |
| 团队专业知识 | 您的团队更熟悉 REST 的标准约定,或者您已有 REST API。 | 您的团队有使用 GraphQL 基于模式的函数的经验或愿意学习这种函数,这函数的学习曲线可能更陡峭。 |
| Next.JS 集成 | 对于简单的内部 API,您可以使用 Next.JS 的原生 API 路由或服务器操作。您还可以连接到任何外部 REST API。 | 与 Apollo 等 GraphQL 客户端无缝集成,并且可以在服务器端和客户端进行,从而充分利用 Next.JS 的数据获取功能。 |
2.5.1 Strapi:Endpoint 介绍
在开始讲解端点(Endpoint)之前,我们还需理解下 Strapi 的复数 API (:pluralApiId) 与单数 API (:singularApiId) 机制。我们在创建一个页面的时候,可以在显示名称右侧看到 API ID 分为以上两类,如默认的 Article 分为 article 的单数 API 及 articles 的复数 API:

Strapi 为集合类型(Collection Types)和单一类型(Single Types)提供了不同的 API 端点:
- Collection type
| 函数 | URL | 描述 |
|---|---|---|
| GET | /api/:pluralApiId | 获取一个文档列表 |
| POST | /api/:pluralApiId | 创建一篇文档 |
| GET | /api/:pluralApiId/:documentId | 获取一篇文档 |
| PUT | /api/:pluralApiId/:documentId | 更新一篇文档 |
| DELETE | /api/:pluralApiId/:documentId | 删除一篇文档 |
- Single type
| 函数 | URL | 描述 |
|---|---|---|
| GET | /api/:singularApiId | 获取一篇文档 |
| PUT | /api/:singularApiId | 更新或创建一篇文档 |
| DELETE | /api/:singularApiId | 删除一篇文档 |
现在我们已经知道了 Global 作为一项 Single type 仅能使用三种端点,这点在先前提到的设置中的 Users & Permission Plugin 页面中也有体现。
除此以外,我们还可以用 /api/upload 端点进行文件上传。
2.5.2 Strapi:REST API 简介
理解完端点,我们便可以开始用 REST API 语法进行接口测试。为了方便举例,我们需要回到 Collection type 的 Article 页面,确认 ID 为 2, 3, 4 的文档为已发布状态,1 与 5 则为归档状态:

然后回到 Insomnia ,对 /api/articles 接口进行测试:

我们可以手动添加查询参数或直接编辑,比如直接在 /api/articles 后面添加 ?populate=* 做测试:

获取了几百行的 JSON;数据没问题,但实在太多太杂,现在就让我们来讲讲如何用 REST API 获取想要的数据,并实现排序。首先我们来看一下几种 API 参数:
| 操作符 | 类型 | 描述 |
|---|---|---|
filters | 对象 | 过滤响应内容 |
locale | 字符串 | 获取语言环境 |
status | 字符串 | 获取状态(存档或发布) |
populate | 字符串或对象 | 填充关联数据、组件或动态区域 |
fields | 数组 | 仅获取特定内容 |
sort | 字符串或数组 | 排序内容 |
pagination | 对象 | 分页内容 |
这里我们直接编辑一串包含了上述常用操作符的语句进行举例演示,你也可以 点击这里 快速预览 API 响应。
现在我们仅获取到想要的结果了:

如果你做到这里报错了,请确保 Settings - Users & Permissions Plugin - Roles - Public 中的 Permissions 已依照 上文小节 2.4 的末尾表格 正确设置,且 Article 正确包含了
title、slug、description、cover、author、category、blocks、seo字段;如果你做到这里不显示任何数据,则需要查看你是否设置了部分或全部 ID 大于 2 的 Article 的发布状态为已发布。
各类常见错误具体可于 Strapi 使用指南 - 故障排查 中自查解决。
语句如下所示,为了便于阅读,每个 & 字符前都做了换行处理:
代码已复制!
http://127.0.0.1:1337/api/articles ?status=published &filters[id][$gt]=2 &sort[1]=id:desc &fields[0]=title &fields[1]=slug &fields[2]=description &populate[cover][fields][0]=alternativeText &populate[cover][fields][1]=caption &populate[cover][fields][2]=url &populate[author][fields][0]=name &populate[category][fields][0]=name &populate[category][fields][1]=slug &populate=blocks &populate[seo]=*
这里逐条解释:
- 从端点末端加一个半角问号
?开始添加操作符 - 每一个操作符之间都需要用
&连接 - 使用
status=published选中仅发布的页面 - 使用
filters[id][$gt]=2筛选出 id 大于 2 的数据 - 使用
fields=value仅选择你想要的数据,避免获取不必要的数据 - 使用
fields[number]=value&fields[number]=value选择多项数据,其他数据同样不做获取 - 使用
populate[object][fields][number]=value在填充的同时限制仅需的数据 - 使用
populate=value或populate[value]=*获取某个对象的所有数据
2.5.3 Strapi:数据获取与性能优化
接下来让我们以主页为例,通过添加正确的参数以筛选、排序、限定范围,以此获取想要的数据并做好性能优化。
首先让我们从首页开始,从 字段列表文档 与 Strapi 中我们了解到 Home 一共有以下九项内容:
代码已复制!
Home ├── banner (required) ├── technologyStacks (required) ├── comparisons (required) ├── comparisonInformations (required) ├── guideChapters (required) ├── guideChapterContents (required) ├── playground (required) ├── playgroundContents (required) └── seo (required)
来看看直接访问首页会回传哪些数据:
http://127.0.0.1:1337/api/home

相信有很多宝子们看懵了,因为我们竟然只从该 API 获取了最基本的 id、documentId、createdAt、updatedAt、publishedAt 于 data 之中。那其他的数据都去哪了?请不要忘了他们都是组件,必须使用 populate 操作符使其填充。我们先用通配符 * 获取所有内容以做测试:
http://127.0.0.1:1337/api/home?populate=*

数据终于正常显示了,不过仔细检查会发现没有深度填充到第三级(如 comparisonInformations.contents.detailList 并未显示),且结果获取到很多不想要的。现在我们着手解决这一问题,首先请参考以下语句获取到 detailList:
代码已复制!
http://127.0.0.1:1337/api/home?populate=comparisonInformations.contents.detailList <!-- 也可以写成 --> http://127.0.0.1:1337/api/home?populate[comparisonInformations][populate][contents][populate]=detailList
深度填充语法说明:
- 点号语法:
populate=relation.subRelation简洁,适用于简单的深度填充
- 对象语法:
populate[relation][populate][subRelation]=*复杂,但可以精确控制每一层的填充内容

我们看到,comparisonInformations 和它的子级 contents 以及孙级 detailList 数据都通过深度填充顺利获取了,但是其他八个组件包括 banner、technologyStacks、comparisons、guideChapters、guideChapterContents、playground、playgroundContents、 seo 全都没有包含在内。我们来一并获取这些内容:
代码已复制!
http://127.0.0.1:1337/api/home ?populate[0]=introduction &populate[1]=frameworks &populate[2]=comparisons &populate[3]=comparisonInformations.contents &populate[4]=comparisonInformations.contents.detailList &populate[5]=comparisonInformations.contents.logo &populate[6]=guideChapters &populate[7]=guideChapterContents &populate[8]=guideChapters.image &populate[9]=playground &populate[10]=playgroundContents &populate[11]=seo

至此数据都正确获取了;接下来让我们管理下 Collection type,先从 Article 开始。
假设现在我们要为 博客文章 - 草稿箱 获取必要且特定的数据并做好筛排,此外非必要数据一条都不用,那么我们的目标可以拟定为:
- 页面的状态为 草稿
- 筛选 作者 为 Sarah Baker 的页面
- 按 最后更新时间 进行 倒序排列
- 仅获取一级数据中的
title、description、slug、updatedAt - 仅获取组件
cover的url、alternativeText与caption - 仅获取组件
author的name与email - 仅获取组件
category的name、slug与description
让我们看看对应的 API 参数,并与不限定 fields 的情况做个对比:
With fields
- API:
代码已复制!
http://127.0.0.1:1337/api/articles ?status=draft &filters[author][name][$eq]=Sarah+Baker &sort=updatedAt:desc &fields[0]=title &fields[1]=description &fields[2]=slug &fields[3]=updatedAt &populate[cover][fields][0]=url &populate[cover][fields][1]=alternativeText &populate[cover][fields][2]=caption &populate[author][fields][0]=name &populate[author][fields][1]=email &populate[category][fields][0]=name &populate[category][fields][1]=slug &populate[category][fields][2]=description
- Responsive:
JSON
代码已复制!
{ "data": [ { "updatedAt": "2025-09-11T15:37:40.832Z", "id": 4, "documentId": "hkqhzs275eidnm6klwrek2sk", "title": "Beautiful picture", "description": "Description of a beautiful picture", "slug": "beautiful-picture", "cover": { "id": 8, "documentId": "qyr7f468ys7ya6qomwagmvxh", "url": "/uploads/beautiful_picture_9d09d9bc5e.jpeg", "alternativeText": "An image uploaded to Strapi called beautiful-picture", "caption": "beautiful-picture" }, "author": { "id": 2, "documentId": "bjmcens0sqbxb8cn8muo4jbo", "name": "Sarah Baker", "email": null }, "category": { "id": 4, "documentId": "lvaftwg3r3ii08lg4db7tav4", "name": "nature", "slug": "nature", "description": "test" } }, { "updatedAt": "2025-09-06T09:03:23.697Z", "id": 3, "documentId": "n2bt6z6wdhejxhy6uvr0k9g4", "title": "A bug is becoming a meme on the internet", "description": "How a bug on MySQL is becoming a meme on the internet", "slug": "a-bug-is-becoming-a-meme-on-the-internet", "cover": { "id": 7, "documentId": "td0bioj5p41t991wyq2op25x", "url": "/uploads/a_bug_is_becoming_a_meme_on_the_internet_1e11e82df1.jpeg", "alternativeText": "An image uploaded to Strapi called a-bug-is-becoming-a-meme-on-the-internet", "caption": "a-bug-is-becoming-a-meme-on-the-internet" }, "author": { "id": 2, "documentId": "bjmcens0sqbxb8cn8muo4jbo", "name": "Sarah Baker", "email": null }, "category": { "id": 2, "documentId": "jjvkd5s5x8xva402vof2ry44", "name": "tech", "slug": "tech", "description": "test" } }, { "updatedAt": "2025-08-30T09:35:24.069Z", "id": 5, "documentId": "rkgn7c8g9xjkprlkge6u8hb9", "title": "What's inside a Black Hole", "description": "Maybe the answer is in this article, or not...", "slug": "what-s-inside-a-black-hole", "cover": { "id": 9, "documentId": "e6nph13wqepty55075xgm6cq", "url": "/uploads/what_s_inside_a_black_hole_fbde111d23.jpeg", "alternativeText": "An image uploaded to Strapi called what-s-inside-a-black-hole", "caption": "what-s-inside-a-black-hole" }, "author": { "id": 2, "documentId": "bjmcens0sqbxb8cn8muo4jbo", "name": "Sarah Baker", "email": null }, "category": { "id": 1, "documentId": "k33z8t7u5ynapq8nv2tkt1p9", "name": "news", "slug": "news", "description": "test" } } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 3 } } }
Without fields
- API:
代码已复制!
http://127.0.0.1:1337/api/articles ?status=draft &filters[author][name][$eq]=Sarah+Baker &sort=updatedAt:desc &populate=*
- Responsive:
JSON
代码已复制!
{ "data": [ { "updatedAt": "2025-09-11T15:37:40.832Z", "id": 4, "documentId": "hkqhzs275eidnm6klwrek2sk", "title": "Beautiful picture", "description": "Description of a beautiful picture", "slug": "beautiful-picture", "createdAt": "2025-08-30T09:35:23.993Z", "publishedAt": null, "cover": { "id": 8, "documentId": "qyr7f468ys7ya6qomwagmvxh", "name": "beautiful-picture", "alternativeText": "An image uploaded to Strapi called beautiful-picture", "caption": "beautiful-picture", "width": 3824, "height": 2548, "formats": { "large": { "ext": ".jpeg", "url": "/uploads/large_beautiful_picture_9d09d9bc5e.jpeg", "hash": "large_beautiful_picture_99fb88a15c", "mime": "image/jpeg", "name": "large_beautiful-picture", "path": null, "size": 83.36, "width": 1000, "height": 666, "sizeInBytes": 83355 }, "small": { "ext": ".jpeg", "url": "/uploads/small_beautiful_picture_9d09d9bc5e.jpeg", "hash": "small_beautiful_picture_99fb88a15c", "mime": "image/jpeg", "name": "small_beautiful-picture", "path": null, "size": 23.35, "width": 500, "height": 333, "sizeInBytes": 23351 }, "medium": { "ext": ".jpeg", "url": "/uploads/medium_beautiful_picture_9d09d9bc5e.jpeg", "hash": "medium_beautiful_picture_99fb88a15c", "mime": "image/jpeg", "name": "medium_beautiful-picture", "path": null, "size": 47.81, "width": 750, "height": 500, "sizeInBytes": 47812 }, "thumbnail": { "ext": ".jpeg", "url": "/uploads/thumbnail_beautiful_picture_9d09d9bc5e.jpeg", "hash": "thumbnail_beautiful_picture_99fb88a15c", "mime": "image/jpeg", "name": "thumbnail_beautiful-picture", "path": null, "size": 6.44, "width": 234, "height": 156, "sizeInBytes": 6436 } }, "hash": "beautiful_picture_99fb88a15c", "ext": ".jpeg", "mime": "image/jpeg", "size": 710.28, "url": "/uploads/beautiful_picture_9d09d9bc5e.jpeg", "previewUrl": null, "provider": "local", "provider_metadata": null, "createdAt": "2025-08-30T09:35:23.974Z", "updatedAt": "2025-08-30T09:35:23.974Z", "publishedAt": "2025-08-30T09:35:23.974Z" }, "author": { "id": 2, "documentId": "bjmcens0sqbxb8cn8muo4jbo", "name": "Sarah Baker", "createdAt": "2025-08-30T09:35:21.197Z", "updatedAt": "2025-08-30T09:35:21.197Z", "publishedAt": "2025-08-30T09:35:21.194Z", "email": null }, "category": { "id": 4, "documentId": "lvaftwg3r3ii08lg4db7tav4", "name": "nature", "slug": "nature", "description": "test", "createdAt": "2025-08-30T09:35:20.366Z", "updatedAt": "2025-09-11T17:34:41.403Z", "publishedAt": "2025-09-11T17:34:41.382Z" }, "blocks": [ { "__component": "shared.rich-text", "id": 7, "body": "## Probant \n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. \n\n## Abit sua\n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. " }, { "__component": "shared.quote", "id": 4, "title": "Thelonius Monk", "body": "You've got to dig it to dig it, you dig?" }, { "__component": "shared.media", "id": 4 }, { "__component": "shared.rich-text", "id": 8, "body": "## Spatiantia astra \n\nFoeda, medio silva *errandum*: onus formam munere. Mutata bibulis est auxiliare arces etiamnunc verbis virgineo Priamidas illa Thescelus, nam fit locis lucis auras. Exitus hospes gratulor ut pondere [speslimite](http://www.curas.io/figuram); quid habent, Avernales faciente de. Pervenit Ino sonabile supplex cognoscenti vires, Bacchumque errat miserarum venandi dignabere dedisti. Discrimina iuncosaque virgaque tot sine superest [fissus](http://quos.org/sitet.aspx). Non color esset potest non sumit, sed vix arserat. Nisi immo silva tantum pectusque quos pennis quisquam artus!" }, { "__component": "shared.slider", "id": 4 } ], "seo": { "id": 4, "metaTitle": "Next.JS & Strapi & Tailwind CSS 全套网建实战教学", "metaKeyword": "Next.JS,Strapi,Tailwind CSS,Rainy Design Studio,雨点设计,网建,前端,教程", "metaDescription": "使用 Next.JS 与 Tailwind CSS 快速搭建你的网站,并借由Strapi构建你的内容管理系统;本教程举了各种案例进行精心讲解,旨在让各位前端朋友们快速掌握这些技术栈并运用到实际工作中去。" } }, { "updatedAt": "2025-09-06T09:03:23.697Z", "id": 3, "documentId": "n2bt6z6wdhejxhy6uvr0k9g4", "title": "A bug is becoming a meme on the internet", "description": "How a bug on MySQL is becoming a meme on the internet", "slug": "a-bug-is-becoming-a-meme-on-the-internet", "createdAt": "2025-08-30T09:35:23.534Z", "publishedAt": null, "cover": { "id": 7, "documentId": "td0bioj5p41t991wyq2op25x", "name": "a-bug-is-becoming-a-meme-on-the-internet", "alternativeText": "An image uploaded to Strapi called a-bug-is-becoming-a-meme-on-the-internet", "caption": "a-bug-is-becoming-a-meme-on-the-internet", "width": 3628, "height": 2419, "formats": { "large": { "ext": ".jpeg", "url": "/uploads/large_a_bug_is_becoming_a_meme_on_the_internet_1e11e82df1.jpeg", "hash": "large_a_bug_is_becoming_a_meme_on_the_internet_89fca5bad1", "mime": "image/jpeg", "name": "large_a-bug-is-becoming-a-meme-on-the-internet", "path": null, "size": 50.97, "width": 1000, "height": 666, "sizeInBytes": 50972 }, "small": { "ext": ".jpeg", "url": "/uploads/small_a_bug_is_becoming_a_meme_on_the_internet_1e11e82df1.jpeg", "hash": "small_a_bug_is_becoming_a_meme_on_the_internet_89fca5bad1", "mime": "image/jpeg", "name": "small_a-bug-is-becoming-a-meme-on-the-internet", "path": null, "size": 19.25, "width": 500, "height": 333, "sizeInBytes": 19245 }, "medium": { "ext": ".jpeg", "url": "/uploads/medium_a_bug_is_becoming_a_meme_on_the_internet_1e11e82df1.jpeg", "hash": "medium_a_bug_is_becoming_a_meme_on_the_internet_89fca5bad1", "mime": "image/jpeg", "name": "medium_a-bug-is-becoming-a-meme-on-the-internet", "path": null, "size": 33.59, "width": 750, "height": 500, "sizeInBytes": 33590 }, "thumbnail": { "ext": ".jpeg", "url": "/uploads/thumbnail_a_bug_is_becoming_a_meme_on_the_internet_1e11e82df1.jpeg", "hash": "thumbnail_a_bug_is_becoming_a_meme_on_the_internet_89fca5bad1", "mime": "image/jpeg", "name": "thumbnail_a-bug-is-becoming-a-meme-on-the-internet", "path": null, "size": 6.73, "width": 234, "height": 156, "sizeInBytes": 6728 } }, "hash": "a_bug_is_becoming_a_meme_on_the_internet_89fca5bad1", "ext": ".jpeg", "mime": "image/jpeg", "size": 234.02, "url": "/uploads/a_bug_is_becoming_a_meme_on_the_internet_1e11e82df1.jpeg", "previewUrl": null, "provider": "local", "provider_metadata": null, "createdAt": "2025-08-30T09:35:23.522Z", "updatedAt": "2025-08-30T09:35:23.522Z", "publishedAt": "2025-08-30T09:35:23.522Z" }, "author": { "id": 2, "documentId": "bjmcens0sqbxb8cn8muo4jbo", "name": "Sarah Baker", "createdAt": "2025-08-30T09:35:21.197Z", "updatedAt": "2025-08-30T09:35:21.197Z", "publishedAt": "2025-08-30T09:35:21.194Z", "email": null }, "category": { "id": 2, "documentId": "jjvkd5s5x8xva402vof2ry44", "name": "tech", "slug": "tech", "description": "test", "createdAt": "2025-08-30T09:35:20.359Z", "updatedAt": "2025-09-11T17:34:29.293Z", "publishedAt": "2025-09-11T17:34:29.271Z" }, "blocks": [ { "__component": "shared.rich-text", "id": 5, "body": "## Probant \n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. \n\n## Abit sua\n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. " }, { "__component": "shared.quote", "id": 3, "title": "Thelonius Monk", "body": "You've got to dig it to dig it, you dig?" }, { "__component": "shared.media", "id": 3 }, { "__component": "shared.rich-text", "id": 6, "body": "## Spatiantia astra \n\nFoeda, medio silva *errandum*: onus formam munere. Mutata bibulis est auxiliare arces etiamnunc verbis virgineo Priamidas illa Thescelus, nam fit locis lucis auras. Exitus hospes gratulor ut pondere [speslimite](http://www.curas.io/figuram); quid habent, Avernales faciente de. Pervenit Ino sonabile supplex cognoscenti vires, Bacchumque errat miserarum venandi dignabere dedisti. Discrimina iuncosaque virgaque tot sine superest [fissus](http://quos.org/sitet.aspx). Non color esset potest non sumit, sed vix arserat. Nisi immo silva tantum pectusque quos pennis quisquam artus!" }, { "__component": "shared.slider", "id": 3 } ], "seo": { "id": 2, "metaTitle": "Next.JS & Strapi & Tailwind CSS 全套网建实战教学", "metaKeyword": "Next.JS,Strapi,Tailwind CSS,Rainy Design Studio,雨点设计,网建,前端,教程", "metaDescription": "使用 Next.JS 与 Tailwind CSS 快速搭建你的网站,并借由Strapi构建你的内容管理系统;本教程举了各种案例进行精心讲解,旨在让各位前端朋友们快速掌握这些技术栈并运用到实际工作中去。" } }, { "updatedAt": "2025-08-30T09:35:24.069Z", "id": 5, "documentId": "rkgn7c8g9xjkprlkge6u8hb9", "title": "What's inside a Black Hole", "description": "Maybe the answer is in this article, or not...", "slug": "what-s-inside-a-black-hole", "createdAt": "2025-08-30T09:35:24.069Z", "publishedAt": null, "cover": { "id": 9, "documentId": "e6nph13wqepty55075xgm6cq", "name": "what-s-inside-a-black-hole", "alternativeText": "An image uploaded to Strapi called what-s-inside-a-black-hole", "caption": "what-s-inside-a-black-hole", "width": 800, "height": 466, "formats": { "small": { "ext": ".jpeg", "url": "/uploads/small_what_s_inside_a_black_hole_fbde111d23.jpeg", "hash": "small_what_s_inside_a_black_hole_ec5138ea84", "mime": "image/jpeg", "name": "small_what-s-inside-a-black-hole", "path": null, "size": 3.87, "width": 500, "height": 291, "sizeInBytes": 3867 }, "medium": { "ext": ".jpeg", "url": "/uploads/medium_what_s_inside_a_black_hole_fbde111d23.jpeg", "hash": "medium_what_s_inside_a_black_hole_ec5138ea84", "mime": "image/jpeg", "name": "medium_what-s-inside-a-black-hole", "path": null, "size": 6.92, "width": 750, "height": 437, "sizeInBytes": 6923 }, "thumbnail": { "ext": ".jpeg", "url": "/uploads/thumbnail_what_s_inside_a_black_hole_fbde111d23.jpeg", "hash": "thumbnail_what_s_inside_a_black_hole_ec5138ea84", "mime": "image/jpeg", "name": "thumbnail_what-s-inside-a-black-hole", "path": null, "size": 1.56, "width": 245, "height": 143, "sizeInBytes": 1556 } }, "hash": "what_s_inside_a_black_hole_ec5138ea84", "ext": ".jpeg", "mime": "image/jpeg", "size": 7.5, "url": "/uploads/what_s_inside_a_black_hole_fbde111d23.jpeg", "previewUrl": null, "provider": "local", "provider_metadata": null, "createdAt": "2025-08-30T09:35:24.055Z", "updatedAt": "2025-08-30T09:35:24.055Z", "publishedAt": "2025-08-30T09:35:24.055Z" }, "author": { "id": 2, "documentId": "bjmcens0sqbxb8cn8muo4jbo", "name": "Sarah Baker", "createdAt": "2025-08-30T09:35:21.197Z", "updatedAt": "2025-08-30T09:35:21.197Z", "publishedAt": "2025-08-30T09:35:21.194Z", "email": null }, "category": { "id": 1, "documentId": "k33z8t7u5ynapq8nv2tkt1p9", "name": "news", "slug": "news", "description": "test", "createdAt": "2025-08-30T09:35:20.353Z", "updatedAt": "2025-09-11T17:34:49.909Z", "publishedAt": "2025-09-11T17:34:49.889Z" }, "blocks": [ { "__component": "shared.rich-text", "id": 9, "body": "## Probant \n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. \n\n## Abit sua\n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. " }, { "__component": "shared.quote", "id": 5, "title": "Thelonius Monk", "body": "You've got to dig it to dig it, you dig?" }, { "__component": "shared.media", "id": 5 }, { "__component": "shared.rich-text", "id": 10, "body": "## Spatiantia astra \n\nFoeda, medio silva *errandum*: onus formam munere. Mutata bibulis est auxiliare arces etiamnunc verbis virgineo Priamidas illa Thescelus, nam fit locis lucis auras. Exitus hospes gratulor ut pondere [speslimite](http://www.curas.io/figuram); quid habent, Avernales faciente de. Pervenit Ino sonabile supplex cognoscenti vires, Bacchumque errat miserarum venandi dignabere dedisti. Discrimina iuncosaque virgaque tot sine superest [fissus](http://quos.org/sitet.aspx). Non color esset potest non sumit, sed vix arserat. Nisi immo silva tantum pectusque quos pennis quisquam artus!" }, { "__component": "shared.slider", "id": 5 } ], "seo": null } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 3 } } }
可以看到后者足足有 388 行内容,且包括了没必要显示的 body ,这造成了大量的数据冗余,由此可见合理的数据筛选对于页面性能优化而言是非常有必要的。
+ 或 %20,特殊字符也需要相应编码。实际开发中建议使用 encodeURIComponent() 函数处理。至此我们已经掌握了 Strapi 的各项常用功能与使用方法,而关于接口数据的进一步处理如 Token、鉴权等将在后续课程于合适的时间点穿插讲述。
2.6 Strapi:章节知识点汇总
本章我们完成了 Strapi 的完整学习,从安装配置到数据管理,再到 API 调用与性能优化;现在我们把章节中的关键点汇总一下,以做知识巩固。
- 无头内容管理系统
- 英文:Headless Content Management System,简称 Headless CMS 或 无头 CMS
- 概念:有别于传统的、自带前台完整网站的管理系统,无头 CMS 仅提供数据管理、资源存储与访问 API ,使得网站的高度自定义与框架自选成为可能,但需要专业的开发人员进行操作
- Strapi 简介
- Strapi CMS:一个 MIT 开源免费、且截止本文截稿前活跃更新,在社区也广受欢迎的无头 CMS
- 适用场景:企业官网、小型商城、个人博客 等 低密集数据存储、主要由站长或管理员运营内容的网站
- 官网:strapi.io
- 数据库:PostgreSQL,MySQL,SQLite,MariaDB
- 运行环境:Node.JS
- 技术栈:Koa.JS、Knex.JS、React
- Strapi 功能
- 全部功能:主页、内容管理器、媒体资源库、内容类型管理器、插件市场、设置
- 主页:显示 Dashboard
- 内容管理器:管理你的合集类型(Collection types)与单页类型(Single types)
- 媒体资源库:管理你的静态资源
- 内容类型管理器:管理你的表单,包括合集类型、单页类型以及组件库中的表单
- 插件市场:查找并安装插件(需要在终止项目后于终端安装)
- 设置:管理 Strapi 的各项设置
- Strapi 页面构建流程
- 访问内容管理器;
- 创建全局组件与页面组件;
- 创建页面类型,引入组件及表单;
- 保存变动;
- 点击右上角工具栏按需更改视图;
- 保存变动。
- Strapi 内容管理流程
- 访问内容管理器;
- 如果是合集类型,可选设置管理合集列表的显示排序
- 增删改内容;
- 保存后发布或归档文档。
- Strapi API
- 打开设置;
- 找到 USERS & PERMISSIONS PLUGIN - Roles;
- 按需管理用户组权限,如为公开则进入 Public 项设置;
- 选择所需 GET 方法的页面,比如首页、博客、关于我们等,勾选 find、findOne;
- 选择所需 POST 方法的页面,比如邮件订阅、站内留言等,勾选 create;
- 保存变动。
现在让我们休息一下,然后开始下一章——使用 Next.js 进行网站搭建。
课程链接:Next.js:创建项目并管理目录

