使用 Next.js 将 WordPress 用作无头 CMS
在“使用 Next.js 将 WordPress 用作无头 CMS”的第一部分中,我们学习了设置 WordPress 实例的基础知识,以便我们能够使用 Next.js 框架通过 GraphQL 访问帖子、页面和自定义内容。我们还使用该工具创建了一个新的 Next.js 应用create-next-app
。
对于本系列的第二部分,我们将以此为基础,将各个点连接起来,通过 WPGraphQL 插件从 WordPress 提供内容数据,并在我们的 Next.js 项目中访问它。
如果你喜欢这篇文章,你也会喜欢我在 Twitter 上发布的其他有用内容。 在 Twitter 上关注我 @kendalmintcode 并跟我打个招呼。
清理新的 Next.js 项目
注意: 在第一部分中,我们使用该
create-next-app
工具创建了一个新的 Next.js 应用程序。我建议您先完成第一部分中的步骤,完成设置后再继续本部分。
Next.js开箱即create-next-app
用,提供了很多实用功能,可以作为 10 的入门工具。然而,我们可以移除一些不必要的功能,简化构建过程,避免可能出现的混淆。
要删除的文件
在 VS Code(或您最喜欢的 IDE)中打开第一部分的项目并删除以下文件和文件夹:
- /页面/api
- /pages/api/hello.js
要编辑的文件
接下来,我们需要修改该/pages/index.js
文件。这是我们应用的主入口,也就是主页。目前,它塞满了 Next.js 指南、链接以及其他有用但不需要的标记,所以让我们清理一下。
打开/pages/index.js
并找到<main>
组件中的元素。将开头<main>
和结尾之间的所有内容替换</main>
为以下内容:
<h1 className={styles.title}>Welcome to our demo blog!</h1>
<p>
You can find more articles on the{' '}
<Link href='/blog'>
<a>blog articles page</a>
</Link>
</p>
如果你使用过 React Router,那么你可能对我们链接到页面的这种相当独特的方式很熟悉/blog
。Next.js 使用与 React Router 类似的内部路由组件来链接到内部页面,它看起来如下所示:
<Link href='/blog'>
<a>blog articles page</a>
</Link>
您可以在此处阅读有关 Next.js Link 元素的更多信息,但其本质是您需要声明<Link>
组件并添加一个href="/link-to-your-page"
属性,其中包含要链接到的位置的路径。最后,您需要添加一个<a>
锚元素,并使用您想要用于链接的任何名称。
注意:您应该将任何类名或其他典型的锚属性添加到<a>
标签而不是组件<Link>
中。
最后一件事就是导入Link
组件。在文件顶部添加以下内容/pages/index.js
:
import Link from 'next/link';
完成后,整个/pages/index.js
文件应如下所示:
import Head from 'next/head';
import Link from 'next/link';
import styles from '../styles/Home.module.css';
export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<link rel='icon' href='/favicon.ico' />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Welcome to our demo blog!</h1>
<p>
You can find more articles on the{' '}
<Link href='/blog'>
<a>blog articles page</a>
</Link>
</p>
</main>
<footer className={styles.footer}>
<a
href='https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app'
target='_blank'
rel='noopener noreferrer'
>
Powered by{' '}
<img src='/vercel.svg' alt='Vercel Logo' className={styles.logo} />
</a>
</footer>
</div>
);
}
要添加的文件
当然,我们还需要一些文件,我们将在本文的过程中逐步构建它们。这些文件将负责处理博客文章的路由和数据,并与 WordPress 后端进行交互。
添加以下文件夹和文件:
- 文件夹
/lib
- 将其放在项目根目录下。它将保存所有实用程序文件,特别是与 WordPress 通信的 API 文件。 - 文件
/lib/api.js
——这将处理我们的 GraphQL 查询和数据提取。 - 文件夹
/pages/blog
- 这里没有什么特别的,只是一个用来保存我们博客页面的文件夹。 - 文件
/pages/blog/index.js
- 当人们访问这样的路线时,https://somedomain.co.uk/blog/
该页面将满足该请求。 - 文件
/pages/blog/[slug].js
- 与上述类似,这个看起来相当奇怪的页面将处理单独的博客页面,例如像https://yourdomain.com/blog/an-interesting-article/.
- 文件
/styles/Blog.module.css
——这是一个标准 CSS 文件,用于保存我们博客列表项的样式。 - 文件
/.env.local
- 保存的环境变量文件 - 文件
/styles/Blog.module.css
- 模块化
这个奇怪的文件名[slug].js
看起来真的很陌生,但它是 Next.js 确定文件夹内动态路由的方式。
我们接下来会讲到这一点。
Next.js 中的动态路由
在我们开始构建新页面之前,快速强调一下 Next.js 中的动态路由的工作原理会很有帮助。
开箱即用,无需做任何花哨的事情,Next.js 就会尝试将您抛出的任何路由与它在项目文件夹.js
下找到的文件进行匹配。/pages
例如:
/
将匹配/pages/index.js
/blog/
将匹配/pages/blog.js
或/pages/blog/index.js
/contact/thanks
将匹配/pages/contact/thanks.js
然而,当涉及到动态路由(例如博客文章或产品页面)时,我们可能有一个物理页面文件充当某种模板,处理未知数量的路由。
为此,Next.js 将匹配格式为 的文件名[param]
。因此,在上面的例子中,我们有文件路径/pages/blog/[slug].js
,Next.js 将调用[slug].js
以下路由的页面:
/blog/my-awesome-blog-post
/blog/another-great-post-title
/blog/some-final-title-here
- ...等等。
[
您可以在和字符之间随意命名这个动态路由文件]
,但您将在文件内部引用此名称(您很快就会看到),因此最好给它起一个有意义的名字。在我们的例子中,“slug”是 WordPress 使用的术语,所以我们就保留它。
值得查看有关动态路由的官方 Next.js 文档,以熟悉将其应用到您的应用程序/网站的语法和约定。
使用 api.js 文件获取数据
现在来看看本文的真正重点:获取数据!
在这样的项目中,构建文件没有固定的正确方法,但我倾向于按照从依赖程度最低到依赖程度最高的顺序构建。在我们的例子中,数据获取不依赖于任何其他组件,但 UI 层依赖于此,所以从这里开始比较合理。
处理环境变量
有些东西,比如可能在不同环境之间发生变化的全局变量,最好存储在(有趣的是)环境变量文件中,通常作为.env
项目根目录中的文件创建。
由于我们已经创建了一个这样的文件,让我们用 WordPress GraphQL URL 填充它。打开文件/.env.local
并添加以下行:
WP_API_URL=http://demo.robkendal.co.uk/graphql/
注意:您可以像上面一样使用我的 URL,但请注意:a) 它可能会在未经通知的情况下被删除;b) 您可能无法获得想要的结果。最好还是使用您自己的 WordPress 实例。无论哪种方式,您都需要以以下格式添加 URL:
http://your-domain-here.com/graphql/
Next.js 内置了对环境变量文件的支持。您只需.env.local
在文件根目录下添加一个文件,并添加所需的内容即可。与往常一样,Next 团队提供了关于环境变量的精彩文档供您参考。
添加通用获取函数
打开/lib/api.js
文件,让我们开始添加数据获取的魔法。首先要添加通用的获取函数,用于处理与 WordPress GraphQL 端点的通信。
在文件的顶部,我们将引用刚刚添加到.env
文件中的 API URL,后面是fetchAPI
函数。
const API_URL = process.env.WP_API_URL;
async function fetchAPI(query, { variables } = {}) {
// Set up some headers to tell the fetch call
// that this is an application/json type
const headers = { 'Content-Type': 'application/json' };
// build out the fetch() call using the API_URL
// environment variable pulled in at the start
// Note the merging of the query and variables
const res = await fetch(API_URL, {
method: 'POST',
headers,
body: JSON.stringify({ query, variables })
});
// error handling work
const json = await res.json();
if (json.errors) {
console.log(json.errors);
console.log('error details', query, variables);
throw new Error('Failed to fetch API');
}
return json.data;
}
这是一个异步函数,因为我们需要等待调用fetch()
完成。其余注释应该足以引导您浏览该文件。
信不信由你,这是我们 API 文件中最复杂的函数。虽然不是最长的,但它包含更多活动部件。接下来我们将要定义的函数主要概述了该fetchAPI()
函数将处理的 GraphQL 查询。
添加获取博客文章列表的功能
从现在开始,我们将定义我们的 GraphQL 查询,它将塑造我们想要从 WordPress 返回的数据。
快速提示:定义 GraphQL 查询的最佳方法是在 WordPress 实例上启动 GraphiQL 插件,编写查询,测试结果,然后将其复制到 API 文件中。这样,您就能知道它是否有效,并有望消除过程中的错误。
就查询而言,这非常简单。我们查看所有帖子,抓取前 20 条结果(为了简洁起见),并按日期降序排列。
除了我们在本系列第一部分中定义的extraPostInfo
ACF 自定义字段之外,其余数据都是标准的 WordPress 数据,例如标题、id 和帖子的 slug。
// Notice the 'export' keyword here. We'll be calling this function
// directly in our blog/index.js page, so it needs to be exported
export async function getAllPosts(preview) {
const data = await fetchAPI(
`
query AllPosts {
posts(first: 20, where: { orderby: { field: DATE, order: DESC}}) {
edges {
node {
id
date
title
slug
extraPostInfo {
authorExcerpt
thumbImage {
mediaItemUrl
}
}
}
}
}
}
`
);
return data?.posts;
}
一旦查询返回,我们就使用可选的链式运算符来返回posts
数组,undefined
如果数组不可用的话。
您可以看到这是一个非常简单的函数。这里只有两个实际操作:1 调用fetchAPI()
我们之前定义的函数;2 返回数据。此函数的最大部分是 Next.js 将传递给 WordPress 以检索我们的 Posts 数据的 GraphQL 查询。
GraphQL 是一种表面上很容易掌握的技术,但如果你想执行包含多个部分和片段的更复杂的查询,则需要相当深入的了解。我建议你浏览一下GraphQL 官方文档,并在你的 WordPress GraphQL 实践环境中尝试一些查询。
这是我在 GraphiQL 中构建的相同查询的样子,以及它返回的结果:
添加获取所有博客文章 slug 的功能
从 WordPress 获取了包含一些特定数据的博客文章列表后,现在我们想要获取所有可能的文章列表,但仅获取每篇文章的 slug。
此功能getAllPostsWithSlug()
将用于我们的个人博客文章页面,目前位于/blog/[slug].js
。
当我们讲到前端组件时,我会更详细地讲解这一点,但现在,我们只需要理解我们需要获取一个匹配的 slug 值列表,以便 Next.js 能够与单个 slug 值(即你正在访问的那个)进行匹配。这就是这个函数的作用所在。
仍然在/lib/api.js
文件中,定义一个新的导出异步函数,getAllPostsWithSlug()
并按如下方式填充它:
export async function getAllPostsWithSlug() {
const data = await fetchAPI(
`
{
posts(first: 10000) {
edges {
node {
slug
}
}
}
}
`);
return data?.posts;
}
随着你构建的查询越来越多,它们会变得越来越常见和熟悉。你也会注意到一个模式:我们定义一个内容类型(例如posts
),添加一个可选的过滤器(例如(first: 10000)
),然后在其中查找edges
和node
(例如单个内容类型项)以及该内容类型的属性(例如slug
)。
添加获取个人博客文章数据的功能
下一个 GraphQL 查询将用于从单个 Post 条目中提取数据。它会在[slug].js
页面上查看单篇博客文章时被调用。
在最后一个查询下,定义一个名为 的新导出异步函数getPost()
。它应该如下所示:
export async function getPost(slug) {
const data = await fetchAPI(
`
fragment PostFields on Post {
title
excerpt
slug
date
featuredImage {
node {
sourceUrl
}
}
}
query PostBySlug($id: ID!, $idType: PostIdType!) {
post(id: $id, idType: $idType) {
...PostFields
content
}
}
`,
{
variables: {
id: slug,
idType: 'SLUG'
}
}
);
return data;
}
这是我们文件中最长的查询api.js
,它看起来有点不同,所以让我们检查一下。
GraphQL 片段
最开始的部分称为片段,它用fragment
查询中的关键字修饰。
`
fragment PostFields on Post {
title
excerpt
slug
date
featuredImage {
node {
sourceUrl
}
}
}
//...rest of query
`
GraphQL 片段使我们能够将更大、更复杂的查询分解为更小、可重复使用的部分。
例如,您的调用中可能有几个查询,但它们都使用相同的 Post 数据。您不必在每个查询中定义相同的字段,而是可以定义一个片段,然后使用扩展运算符语法将这些字段提取到每个单独的查询中。
我们已经在PostBySlug
定义的查询中完成了此操作:
`
query PostBySlug($id: ID!, $idType: PostIdType!) {
post(id: $id, idType: $idType) {
...PostFields
content
}
}
`
注意...PostFields
我们引用的片段。您也可以删除该片段并像这样定义查询:
`
query PostBySlug($id: ID!, $idType: PostIdType!) {
post(id: $id, idType: $idType) {
title
excerpt
slug
date
featuredImage {
node {
sourceUrl
}
}
content
}
}
`
GraphQL 变量
我们的查询中另一个有趣的事情是使用变量来过滤我们想要获取数据的特定帖子。
现在重点关注查询的主要部分,这部分:
`
query PostBySlug($id: ID!, $idType: PostIdType!) {
post(id: $id, idType: $idType) {
...PostFields
content
}
}
`,
{
variables: {
id: slug,
idType: 'SLUG'
}
};
您可以看到用“\$”美元符号定义的 GraphQL 变量。在第一行中,query PostBySlug($id: ID!, $idType: PostIdType!)
我们定义了查询名称、要传入的变量及其类型。
变量类型由 GraphQL 模式决定。您可以在 WordPress GraphiQL 资源管理器中查看该模式,但这有点超出了本文的讨论范围。
接下来,我们传递这些变量占位符来使用 过滤单个特定的 Post 项目post(id: $id, idType: $idType)
。
当然,现在我们需要实际传入变量值,这就是该方法的第二个参数的来源fetchAPI()
。我们传入一个普通的 JavaScript 对象,该对象具有一个variables
包含所有 GraphQL 变量及其值的属性。
在这种情况下,对于id
我们使用slug
传递给包含函数的参数getPost(slug)
,而对于idType
我们使用的是 的简单字符串值SLUG
。
我们在 WordPress GraphiQL 中定义、测试和验证了所有查询,然后将其放到前端组件和页面上。
使用 GraphQL 列出 WordPress 中的博客文章
现在到了激动人心的部分:构建博客列表页面!Next.js 是基于 React 构建的,所以这里应该不会有太多不寻常的地方。
打开/pages/blog/index.js
文件并从顶部的导入开始:
import Head from 'next/head';
import Link from 'next/link';
// data
import { getAllPosts } from '../../lib/api';
// styles
import styles from '../../styles/Home.module.css';
import blogStyles from '../../styles/Blog.module.css';
您可以看到,我们从 Next.js 中提取了Head
和组件(稍后会详细介绍),然后是数据处理函数。紧接着,我们添加了两个样式模块文件。Link
Head
getAllPosts
这些本质上是模块化的组件级 CSS 文件,Next.js 开箱即用。我们稍后也会讨论这些。
太棒了,导入完成。接下来是概述主要的 Blog 组件:
const Blog = ({ allPosts: { edges } }) => (
<div className={styles.container}>
<Head>
<title>Blog articles page</title>
<link rel='icon' href='/favicon.ico' />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Latest blog articles</h1>
<hr />
<section>
{edges.map(({ node }) => (
<div className={blogStyles.listitem} key={node.id}>
<div className={blogStyles.listitem__thumbnail}>
<figure>
<img
src={node.extraPostInfo.thumbImage.mediaItemUrl}
alt={node.title}
/>
</figure>
</div>
<div className={blogStyles.listitem__content}>
<h2>{node.title}</h2>
<p>{node.extraPostInfo.authorExcerpt}</p>
<Link href={`/blog/${node.slug}`}>
<a>Read more ></a>
</Link>
</div>
</div>
))}
</section>
</main>
</div>
);
您将看到我们allPosts
使用解构语法引用了一个特定的 prop。它将是我们之前定义的 WordPress GraphQL 查询返回的所有可用 Posts 的集合。
该prop 将通过我们将在本文后面定义的函数allPosts
自动提供给我们的 Blog 组件。getStaticProps
该<Head></Head>
组件允许我们为此页面定义元数据,并且是内置的 Next.js 功能,稍后会详细介绍。
同样,className={styles.main}
语法也是我们在 Next.js 中引用 CSS 模块样式的方式。同样,我们稍后会介绍这一点。
Blog 组件的主要部分是以 开头的循环{edges.map(({ node }) =>
。这不是最好的命名结构,但我们实际上edges
是一个项目数组node
,每个项目node
代表一个 WordPress 帖子项目。
API 函数返回的每个节点的getAllPosts()
结构类似于此:
{
"node": {
"id": "cG9zdDoyOA==",
"date": "2020-07-09T07:18:42",
"title": "A third post with an interesting name",
"slug": "a-third-post-with-an-interesting-name",
"extraPostInfo": {
"authorExcerpt": "some excerpt details here",
"thumbImage": {
"mediaItemUrl": "http://demo.robkendal.co.uk/wp-content/uploads/2020/07/v7jgc6a3zn951.jpg"
}
}
}
},
了解了这些信息后,我们就可以更轻松地提取所需的相关内容并将其注入到我们的 React JSX 中,如下所示:
{
edges.map(({ node }) => (
<div className={blogStyles.listitem} key={node.id}>
<div className={blogStyles.listitem__thumbnail}>
<figure>
<img
src={node.extraPostInfo.thumbImage.mediaItemUrl}
alt={node.title}
/>
</figure>
</div>
<div className={blogStyles.listitem__content}>
<h2>{node.title}</h2>
<p>{node.extraPostInfo.authorExcerpt}</p>
<Link href={`/blog/${node.slug}`}>
<a>Read more ></a>
</Link>
</div>
</div>
))
}
使用 Next.js Head 的元数据
如果您之前使用 React 搭建过网站,那么您可能遇到过需要向页面添加元数据的情况。如果您遇到过这种情况,那么您也很可能遇到过React Helmet。React Helmet 是一种非常直接的向页面注入元数据的方法。
Next.js 提供了一个类似的选项,可以轻松嵌入其中。它提供了一个名为的组件<Head>
,您将在页面顶部看到它被导入,/pages/blog/index.js
如下所示:
import Head from 'next/head';
使用它甚至更容易。再次查看组件的顶部Blog
:
<head>
<title>Blog articles page</title>
<link rel="icon" href="/favicon.ico" />
</head>
您在开始和结束<Head></Head>
标签之间添加的任何内容都会被神奇地传输到<head>
静态输出.html
文件中。
.module.css
使用文件设置模块样式
Next.js 提供了一系列内置 CSS 支持。其中最令人印象深刻的是模块化、组件级的 CSS 支持。
您可以通过创建具有命名约定的文件来定义组件 CSS 文件,[name].module.css
并将其导入到您想要使用的组件或页面中。
然后,要应用组件级样式,您可以将它们附加到元素,就像 JavaScript 对象一样,例如className={styles.class}
。
更完整的示例可能如下所示:
import someStyles from 'componentName.module.css';
export default function MyComponent() {
return (
<main className={someStyles.aclassname}>
...rest of content here
</main>
)
}
当组件在页面上呈现时,这会将唯一的类名应用于该组件,并将其范围限定在该组件内,以免发生类冲突。
当然,您不必遵循此约定。您可以像平常一样加载样式表。但是,为了避免冲突,Next.js 规定您必须从一个名为 的特殊文件执行此操作
_app.js
,该文件默认位于/pages/
目录下。
了解了这些背景知识后,我们可以/styles/Blog.module.css
为博客列表填充一些基本样式。打开文件并复制以下内容:
.listitem {
padding: 0.5em 0 1em;
margin: 1em auto 0.5em;
display: flex;
max-width: 60%;
border-bottom: 1px solid hsl(0, 0%, 89%);
}
.listitem__thumbnail img {
max-width: 10em;
}
.listitem__content h2 {
margin-top: 0;
}
.article {
max-width: 75%;
margin: 1em auto;
}
.postmeta {
text-align: center;
font-size: 1.5rem;
}
.article img {
max-width: 60%;
height: auto;
}
这些样式并非必须设置,您可以随意修改。不过,它们确实能避免看起来太过夸张。
这里要做的最后一件事是快速向文件添加一些链接样式/styles/global.css
,因此打开它并添加以下样式:
a {
color: #0070f3;
text-decoration: none;
}
a:hover,
a:focus,
a:active {
text-decoration: underline;
}
静态生成和处理外部数据
Next.js在构建时获取数据并将其嵌入到页面中非常出色。它提供了两种主要的数据获取方式:
- 通过以下方式在构建时获取数据
getStaticProps()
- 这称为静态生成。 - 通过以下方式在渲染时获取数据
getServerSideProps()
- 这称为服务器端渲染或 SSR
还有第三种混合方式,即使用静态生成的前端,在页面渲染完成后再调用数据。这被称为客户端渲染。这并非严格意义上的极端情况,但它融合了上述两种主要方法,例如可用于仪表盘页面。
大多数时候,您会希望努力使用静态生成getStaticProps()
,因为它为最终用户提供了最佳性能,并真正利用了整个 Jamstack 静态站点生成方法。
如果我们使用 WordPress,这一点尤其关键,因为 WordPress 本身就是一个开箱即用的服务器端渲染网站。使用 Next.js 将 WordPress 与其自身的前端分离,部分原因就是为了移除服务器渲染环节,并静态生成我们的前端网站。
如果您不确定采用哪种方法,您可以提出以下问题:“此页面可以在用户请求之前预渲染吗?”如果您的答案是“是”,那么静态生成是正确的选择。
使用 Next.js 访问外部数据getStaticProps()
现在我们对 Next.js 及其通过外部数据获取的静态生成有了更清晰的了解,我们可以在页面getStaticProps
中实现 Next 的方法/blog/index.js
。
getStaticProps
在我们的 Blog 组件的默认导出下面添加以下实现:
export async function getStaticProps() {
const allPosts = await getAllPosts();
return {
props: {
allPosts
}
};
}
这到底有多简单?!此函数将在构建时被 Next.js 调用,从 WordPress 获取数据,并将其传递到props
主 Blog 组件中。
/lib/api.js
您完全可以在这里毫无问题地完成所有数据获取。但是,出于以下几个原因,我们将许多繁琐的工作抽象到我们的代码中:
- 它减少了我们组件的长度。
- 它将数据处理责任从组件中抽象出来(组件的工作实际上并不是获取数据)。
- 它清理了我们的组件,使它们更易于维护和可读。
- 它减少了重复,特别是围绕主要
fetchAPI()
功能的重复。
重要的是要记住,getStaticProps()
必须以完全相同的方式命名。它还必须返回一个props: {}
对象。
getStaticProps()
您可以在官方 Next.js 文档中阅读有关静态生成的更多信息。
检查输出
让我们启动网站,看看目前为止的效果如何。打开控制台并输入:
yarn dev
这将启动本地开发服务器,您将看到 Next.js 能够多快地构建我们的页面并让我们的本地站点准备好预览http://localhost:3000
。
您应该看到如下视图:
如果您点击标题下方的“博客文章页面”链接,您应该会看到如下所示的页面:
当然,如果您使用自己的 WordPress 实例作为无头 CMS,并且数据和属性有所不同,那么它的外观可能会非常不同。不过您明白我的意思!
处理博客文章等动态路由
太好了!我们已经完成了这么多,快完成了。现在,我们需要完成整个流程,处理当有人点击博客列表页面上的“阅读更多 >”链接时的路由。
现在,如果您单击它们,您可能会看到错误或 404 页面或一些不太理想的结果。
到目前为止,我们一直在处理已知的静态路由——提前明确定义并具有固定端点的页面。
但是,在我们的博客详细信息页面(即处理单个博客文章内容的页面)中,我们有未知数量的 URL(即“slug”),我们也事先不知道。
这就是Next.js动态路由的用武之地。我们已经在本文前面介绍了它的具体实现,我建议您查看Next.js 官方关于动态路由的优秀文档。
动态路由的基本流程
为了处理博客文章页面的动态路由,我们需要做四件事:
- 定义一个动态页面来处理路线(我们已经用完成了
/pages/blog/[slug].js
)。 - 在此页面内创建并导出一个默认组件来实际处理数据并显示一些输出。
getStaticProps
像我们在列表页面中那样,从 Next.js 中实现该函数。这将处理单篇博客文章的数据获取。- 从 Next.js实现该
getStaticPaths
函数。这是我们用于动态页面的另一个特殊函数,它获取路由可能匹配的列表,以便在构建时创建正确的 HTML 页面。
现在让我们填写博客文章页面。
构建博客详细信息页面
打开动态博客文章页面并粘贴以下代码,我们接下来将进行介绍。
(注意:由于 Dev 的语法高亮功能失效了,我不得不将这里的代码切换到图像……对此深表歉意)
让我们分解每个部分,以便您了解发生了什么以及为什么发生。
博客文章页面的导入
我们在此文件中还有一些导入,如下所示:
这里没有什么陌生的东西:我们正在从文件中导入数据获取实用程序api.js
,并使用我们之前讨论过的 CSS 模块方法导入一些样式。
我们还从 Next.js 本身引入了Head
和Link
组件,以便我们可以更新元数据并提供返回主文章列表页面的链接。
我们引入的新导入是useRouter
来自next/router
库的。正如我们所见,Next.js 提供了自己的内置路由器功能,其中大部分功能在后台处理,您无需参与。但是,偶尔您需要使用路由功能,这时就需要用到它了useRouter
。
我们将使用它来处理路由回退情况。
官方文档中有大量关于 Next 路由器功能的高质量文档。
Next.js 路由器和日期格式
接下来,我们在组件的开头有一些小逻辑Post
:
该formatDate
功能应该非常清晰,只是将 WordPress 提供给我们的相当丑陋的日期字符串转换为更易于人类阅读的格式。
这里最有意思的部分是useRouter()
Hook。我们将 Hook 的一个实例定义useRouter
到变量中,router
然后,我们可以在这行代码中进行简单的错误处理转义,if (!router.isFallback && !postData?.slug)
。
这里发生的情况是,我们正在查看isFallback
属性以确定正在呈现的此页面是否是后备版本(我们稍后会介绍这一点),如果不是,但我们也没有 slug,那么这意味着我们将无法为该路线呈现页面。
我们不会显示可怕的错误页面,而是返回一个包含错误消息的简单段落。
注意:这可能需要针对生产环境的更充实的解决方案,但这是我们在找不到动态路由时处理错误的基础。
在主要内容中提供后备
如果页面尚未完全生成,那么我们可以选择在完成运行和生成页面时提供后备页面或内容。getStaticProps
这就是我们主要组件方法中发生的事情return
:
如果我们的router
对象有一个isFallback
设置为的属性true
,那么我们将显示一个带有加载消息的简单标题,直到getStaticProps
完成并且我们的内容准备好。
找到合适的文章getStaticPaths()
定义好 Blog 主组件后,我们需要将 Next 的方法添加getStaticPaths
为导出的异步函数。它将在构建时运行,并为每个找到的博客文章创建一个静态 HTML 页面。
在文件底部添加以下代码/pages/blog/[slug].js
:
首先,我们getAllPostsWithSlug()
从api.js
文件中调用 。这将返回一组相当笨重的 JSON 数据,其中包含 WordPress 帖子的 slug 作为node
项目,并包装在一个edges
数组中。
这很好,但我们需要我们的 slug 与我们网站的博客文章 URL 格式相匹配/blog/some-blog-article-slug
。
为了实现这一点,我们可以运行一个map
函数来生成与此首选格式匹配的 URL 字符串数组。
最后,我们还添加了一个属性,Next.js 会自动将其注入到其路由器中,并通过我们之前查看的 Hookfallback: true
使其可用。useRouter
通过 WordPress 和 GraphQL 获取文章数据getStaticProps()
这个数据获取难题的最后一部分是getStaticProps
向博客文章页面添加与博客列表页面相同的功能。
我们将对其进行稍微的修改,以便我们显然可以获取单个帖子数据,而不是博客文章列表,因此请在文件末尾添加以下内容/pages/blog/[slug].js
:
这里的主要补充是,我们正在提取从Next.js 提供给该方法的params
默认对象中解构出来的参数。context
getStaticProps
该params
对象包含使用动态路由的页面的路由参数。在我们的例子中,由于我们的动态路由是,因此我们可以像您在此处看到的[slug]
那样引用此参数。params.slug
类似地,如果我们调用我们的页面[id]
,我们将通过引用此参数params.id
。
在本地运行网站
好了,一切就绪后,让我们再次启动开发服务器并进行测试。打开终端并输入开发服务器命令:
yarn dev
导航到http://localhost:3000
并查看博客列表页面。现在,当您点击其中一个“阅读更多 >”链接时,您将被带到一个动态路由的博客文章页面,该页面应如下所示:
同样,您的结果可能会根据您选择的样式和您从哪里提取数据而有所不同。
本系列的后续内容
接下来,在第三部分中,我们将创建 XML RSS 提要作为部署过程的一部分,以便我们可以在网络上发布和联合我们的帖子。
这是几乎所有博客都具备的常见功能,但在使用 Next.js 时,它并不像你想象的那么简单。不过别担心,第三部分会详细介绍。
如果你喜欢这篇文章,你也会喜欢我在 Twitter 上发布的其他有用内容。 在 Twitter 上关注我 @kendalmintcode 并跟我打个招呼。
文章来源:https://dev.to/kendalmintcode/using-wordpress-as-a-headless-cms-with-next-js-2h5p