首先,让我们介绍要使用的堆栈。 Typescript是“是一种基于JavaScript的强烈键入编程语言,可在任何规模上为您提供更好的工具。”简单地说,打字稿增强了编码安全性并为网络启用更多OOP。
nextjs是一个框架,“使您可以通过扩展最新的React功能来创建全堆栈Web应用程序”,简单单词,NextJS是对广泛使用的ReactJS库的升级。
。Strapi是一个开源无头CM。 CMS代表“内容管理系统”,允许我们在没有编程知识的情况下在用户友好的界面中创建,管理,修改和发布内容。无头意味着没有内置的最终用户UI;我们可以使用我们想要的任何前端技术。
GraphQl是“ API的查询语言,也是用您现有数据来满足这些查询的运行时”,用简单的单词,这是一种用于查询数据并获取所需字段的语言。
注意:这是一个概述,这意味着我不会浏览一些方面,例如样式和最佳HTML标签;该博客旨在展示使用上述技术创建博客的功能。
初始化项目
现在,让我们使用命令行创建文件夹(因为它很酷),因此第一个命令是创建一个包含我们所有文件的文件夹。
mkdir my-personal-website
现在,让我们为NextJ和Strapi创建文件夹。
cd my-personal-website
mkdir frontend, strapi
初始化strapi
cd strapi
npx create-strapi-app@latest . --ts --quickstart
参数@latest
表示它将安装最新版本,.
表示我们要在当前文件夹中创建项目,然后— ts
表示它将使用Typescript,最后将其安装为默认设置;安装完成后,它将在地址/admin/auth/register-admin
上打开一个新窗口,要求您创建一个新的管理员帐户;继续创建一个新帐户。注意:使用— ts
是可选的,因为我不会更改本文中的源代码。
初始化nextjs
cd frontend
npx create-next-app@latest . --ts
最后,请记住要发动github存储库,但我不会在这篇文章中介绍它。
从Strapi开始
我喜欢从项目中的后端开始,所以我将从strapi开始,因为它被认为是后端。
安装GraphQL
使用Strapi的一个优点是它与GraphQl的简单集成。要安装GraphQL插件,我们需要运行以下命令。
npm run strapi install graphql
如果您遇到_Duplicate "graphql" modules
错误,请不要担心;很容易修复;您需要做的就是删除 node_modules 文件夹和 package-lock.json ,然后我们需要重新安装软件包。
rm node_modules
rm package-lock.json
npm install
npm run strapi dev
成功安装后,我们现在可以访问url /graphql
。
创建博客文章集合
在url http://localhost:1337/admin/
上打开strapi主页,然后登录后,转到 content-type builder 页面,单击 创建新的新收集类型 按钮,然后在 中显示名称 字段,用适当的名称填写;我将选择 博客文章 ,然后单击继续。
现在,我们正在为新系列选择领域。您可以根据需要自定义它;为了简单起见,我将添加三个字段,a 文本 标题的字段,另一个 text 字段对于描述,最后是 丰富的文本 字段,下一步单击保存。
现在我们有了一个集合,我们需要用数据填充它,转到 content Manager ,现在我们可以看到新创建的收藏集,单击它,然后 创建新条目 按钮,然后用所需的任何数据填充它,然后单击“保存”并发布。
可访问性选项1:可公开访问
在我们可以通过GraphQl访问数据之前,我们需要使其可访问;您可以使其公开访问或仅适用于经过身份验证的用户。由于我没有存储敏感数据,因此我会选择一个公开访问的集合。
要公开访问它,转到 设置, 然后角色,单击 public ,向下滚动到您创建的集合(在我的情况下是博客帖),单击它,然后tick find 和 findOne。 强>
可访问性选项2:仅身份验证
但是,如果您选择身份验证的方式,这就是方法。有两种方法可以实现这一目标;第一个是在设置页面中创建 api令牌 ,然后我们需要通过将其添加到标题中,以 /api/auth/local
带有包含标识符的JSON主体,该标识符可以是 user 收集中新创建的用户的用户名或电子邮件和密码,不是管理员帐户我们在初始化strapi中创建了。
{
"identifier": "email or username",
"password": "password"
}
如果请求成功,您将获得包含 jwt 字段的响应,现在我们需要做同样的事情,请通过标头通过标头发送每个请求在 授权中
现在访问它,转到 设置, 然后角色,单击 authenticated
测试GraphQL端点
要访问GraphQl到/graphql
,现在,我们可以在左侧看到输入字段;在这里,我们将查询查询我们的strapi应用程序,这是查询的一个示例:
query {
blogPosts {
data {
id
attributes {
title
description
body
}
}
}
}
,就我而言,响应是:
"data": {
"blogPosts": {
"data": [
{
"id": "1",
"attributes": {
"title": "test",
"description": "test",
"body": "test"
}
}
]
}
}
}
测试休息端点
我们需要做的就是向/api/blog-posts/
发送GET请求。响应的一个例子是:
{
"data": [
{
"id": 1,
"attributes": {
"title": "test",
"description": "test",
"body": "test",
"createdAt": "2023-01-30T20:28:28.141Z",
"updatedAt": "2023-01-30T20:28:29.027Z",
"publishedAt": "2023-01-30T20:28:29.025Z"
}
}
],
"meta": {
"pagination": {
"page": 1,
"pageSize": 25,
"pageCount": 1,
"total": 1
}
}
}
我们已经完成了Strapi部分,并将继续进行NextJS。
从nextjs开始
在 页面内创建一个新目录 目录,并将其命名 blog ;接下来,创建一个新的index.tsx
文件并初始化基本的React组件。
import {NextPage} from "next";
interface Props {
}
const About: NextPage<Props> = (Props) => {
return (
<p>About Page</p>
)
}
export default About;
现在我们要访问blog
并查看大约页面。
创建接口
我们将需要一些接口;让我们首先创建一个包含我们所有基本模块的文件夹 common ,然后创建另一个称为 type 的文件夹包含我们所有类型和接口,然后创建一个新的打字稿,我将命名 bloginterfaces ;现在,让第一个接口(将要访问IBlogIdentification
),它将包含我们的标识字段;在我们的情况下,这是博客文章的ID。
export interface IBlogIdentification {
id: string
}
接下来,我们需要一个接口来保存博客字段的标题,描述和身体;命名IBlogFields
。
export interface IBlogFields {
title: string,
description: string,
body: string
}
接下来,我们需要一个从GraphQl查询中收到的博客属性的接口;命名IBlogAttributes
。
export interface IBlogAttributes {
attributes: Partial<IBlogFields> & Pick<IBlogFields, 'title'>
}
我已经使用了部分和选择实用程序类型来使所有IBlogFields
字段可选。
最后,我们将需要一个接口来处理整个博客条目;命名IBlog
。
export interface IBlog extends IBlogIdentification, IBlogAttributes{}
现在让我们在 博客 page上更新道具接口。
interface Props {
blogs: IBlog[]
}
GraphQl有效
在我们开始获取数据之前,我们需要进行GraphQl工作。首先,我们需要安装两个软件包才能与GraphQl进行交互。我们将需要graphql
和@apollo/client packages
。
npm i graphql, @apollo/client
第二,我们需要编写查询;我们将需要三个查询,第一个列出博客,第二个是获取一个博客,第三个是获取博客ID列表”;在 common 文件夹中,创建一个新文件夹并将其称为graphql,然后创建一个新文件并调用queries.tsx
,然后添加以下查询。
export const LIST_BLOG = gql(`query {
blogPosts {
data {
id
attributes {
title
}
}
}
}`)
export const SINGLE_BLOG = gql(`query ($blogId: string!) {
blogPosts(filters: {id: {eq: $blogId}}) {
data {
id
attributes {
title
description
body
}
}
}
}`)
export const LIST_ID = gql(`query {
blogPosts {
data {
id
}
}
}`)
我们需要的第三件事是存储Strapi的地址,最方便的方法是将其存储在 env 变量中; NextJS已经带有内置环境变量模块,因此无需安装dotenv
,我们需要做的就是转到next-config.js
,并添加以下导出。
module.exports = {
env: {
STRAPI_ADDRESS: "http://127.0.0.1:1337/graphql"
},
nextConfig,
}
,我们需要的最后一件事是一个ApolloClient
是要与graphql server连接,在 graphql 文件夹中,创建一个新文件并将其命名为client.tsx
,然后添加以下代码。
import {ApolloClient, InMemoryCache, NormalizedCacheObject} from "@apollo/client";
const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({
uri: process.env.STRAPI_ADDRESS,
cache: new InMemoryCache(),
});
export default client;
注意:缓存是ApolloClient
缓存结果的一种方式,这是一个很大的话题,并且不在此帖子的范围内。可以找到更多信息here。
获取数据
现在我们可以开始实施获取过程;让我们从创建getStaticProps
函数开始:
export const getStaticProps: GetStaticProps<Props> = async () => {
}
我们将使用getStaticProps
,因为它将在构建时间预渲染页面,并且数据不会始终如一地更改;无需每次向我们的CMS发送请求。
现在我们可以开始查询strapi;让我们获取博客列表。
const {data} = await client.query({query: LIST_BLOG})
const blogs: IBlog[] = data.blogPosts.data;
return {
props: {
blogs
}
}
显示博客列表
现在我们可以检索博客列表,我们可以开始显示它们;我们需要新的文件夹。为了使它更轻松,我将在我的项目结构下方展示一个演示。
common
--elements
----blog
------BlogTitle.tsx
modules
--blog
----BlogCardList.tsx
我们需要两个文件,BlogTitle
将呈现博客的标题,而BlogCardList
将使用BlogTitle
渲染博客列表,让我们从BlogTitle
开始。
interface Props {
title: string
isClickable: boolean
blogId?: string
}
const BlogTitle: NextPage<Props> = ({title, isClickable, blogId}) => {
if(isClickable && blogId) {
return (
<Link href={`/blog/${blogId}`}>{title}</Link>
)
} else {
return (
<p>{title}</p>
)
}
}
注意:因为我在商业版中知道,我将在多个位置使用博客标题,并且它将具有更多的道具和功能,所以我选择使博客标题成为单独的组件;但是,您可以选择不。
现在到纸牌列表。
interface Props {
blogs: IBlog[]
}
const BlogCardList: NextPage<Props> = ({blogs}) => {
return (
<div>
{blogs.map((blog, i) => {
return (
<div key={i}>
<BlogTitle
title={blog.attributes.title}
isClickable={true}
blogId={blog.id}
/>
</div>
);
})}
</div>
)
}
export default BlogCardList;
现在要显示列表,我们需要将BlogCardList
添加到大约页面。
const Blog: NextPage<Props> = ({blogs}) => {
return (
<BlogCardList blogs={blogs}/>
)
}
现在,如果我们转到/blog
,我们应该看到博客标题的列表。
显示一篇博客文章
让我们开始创建单个博客文章页面,创建一个新文件,然后在 博客内将其称为[id].tsx
目录
import {NextPage} from "next";
import {IBlog} from "@/common/types/BlogInterfaces";
interface Props {
blog: IBlog
}
const client = new ApolloClient({
uri: process.env.STRAPI_ADDRESS,
cache: new InMemoryCache()
});
const Blog: NextPage<Props> = ({blog}) => {
return (
<p>Blog Here<p/>
)
}
export default Blog;
我们将使用NextJS的getStaticPaths
和getStaticProps
功能,可以找到更多信息here。
export const getStaticPaths: GetStaticPaths = async () => {
const {data} = await client.query({query: LIST_ID })
const ids: IBlogIdentification[] = data.blogPosts.data;
const paths = ids.map(id => {
return {params: {...id}}
})
return {
paths,
fallback: true
}
}
export const getStaticProps: GetStaticProps<Props> = async ({params}) => {
console.log()
const {data} = await client.query({query: SINGLE_BLOG, variables: {blogId: params!.id}})
const blog: IBlog = data.blogPosts.data[0];
return {
props: {
blog
}
}
}
现在,我们可以访问组件内的博客数据。
const Blog: NextPage<Props> = ({blog}) => {
console.log(blog)
return (
<div>
<p>{blog.id}</p>
<p>{blog.attributes.title}</p>
<p>{blog.attributes.description}</p>
<p>{blog.attributes.body}</p>
</div>
)
}
现在我们的博客功能正常;我们可以检索并显示博客列表和单个博客文章。