参考方案:Crash Course: Build a Simple Headless WordPress App with Next.js & WPGraphQL — Builders
目录
1. 本地部署 wordpress
在本地部署好 wordpress,详情参考点两下鼠标完成建站:WordPress 本地建站 — 编年史家的茶会。
设置固定链接
跳转至 wp 后端,设置固定链接。
如果设置为其他的固定链接,后续有些地方不能按教程来,需要自己改一下。
本文后续默认已经选中了 postname
,所以这一项必须要加上去。
安装 WPGraphQL 并启用调试模式
安装 WPGraphQL 插件,启用后打开 GraphQL > Settings
菜单并选中标有 Enable GraphQL Debug Mode
的选项,然后单击 Save Changes
。
搜不到就搜这个 WPGraphQL – WordPress 插件 | WordPress.org China 简体中文
![]()
导入测试内容
如果是新创建的网站,可以通过导入测试内容来作为占位符。
- 测试内容的文件:crash-course-headless-wp-next-wpgraphql/crashcoursesite.WordPress.2022–04-01.xml at main · JEverhart383/crash-course-headless-wp-next-wpgraphql · GitHub
- 导入的方法:工具/导入/WordPress,运行,读取文件即可。
2. 让 Next. Js 应用在本地运行
这里基于速成课程的 GitHub 存储库中的文件。
该存储库主要基于使用
create-next-app
CLI 工具创建的默认 Next.js 模板 ,以及 WP Engine DevRel 团队提供的一些额外的脚手架和样式。
要将存储库克隆到本地机器,您可以运行以下命令:
git clone https://github.com/JEverhart383/crash-course-headless-wp-next-wpgraphql.git
如果不知道这个干嘛用的话可以先去看一下 git 的基础教程,1–2 h 就可以开始用了。
项目完成克隆后,运行以下命令切换到该目录并使用 npm
安装项目的所有依赖项。依赖项下载完成后,您可以使用 npm run dev
在浏览器中运行该项目。
cd crash-course-headless-wp-next-wpgraphql
npm install
npm run dev
当应用程序在本地运行时,您应该能够在浏览器中通过 http://localhost:3000
查看该应用程序,并且该应用程序将随着项目目录中文件的更改而重新编译和更新。
此时所有帖子都是来自本地 JSON 文件的虚拟数据,你能在 http://localhost:3000
中看到的就是你要制作的前端页面。
下一步,我们将 Apollo Client 连接到 WordPress 和 WPGraphQL,以显示您网站的实际帖子数据。
3. 使用 Apollo Client 连接到 WordPress
使用来自您的 WordPress 网站的数据替换我们的模拟帖子数据的第一步是配置 Apollo Client 以通过 WPGraphQL 与您的网站一起使用。
虽然从技术上讲,你可以仅使用 Fetch API 将查询直接 POST
到 GraphQL 服务器,但使用 Apollo Client 之类的 GraphQL 客户端还有很多额外的好处 ,这些好处超出了本文的讨论范围。以下许多步骤也包含在 Apollo Client 文档的入门页面上,因此,如果你有兴趣了解有关该库的更多信息,请务必参考该页面。
创建 .env 文件
要开始此过程,我们要做的第一件事是通过环境变量将 WordPress 站点的 URL 提供给我们的应用程序。默认情况下,Next.js 将在项目的根目录中查找名为 .env.local
的文件来加载任何环境变量 。
初始的根目录如下:
在这创建一个名为 .env.local
的文件。
注意:它的后缀就是 local。
图中闪电黑底图标的文件是因为我的记事本默认用了 sublime text 这个软件,换个记事本软件阅读不会有什么影响,请无视。
![]()
用记事本或者其他相同的功能的软件打开文件并粘贴以下行,将“ https://headlesswp.local ”替换为您的 WordPress 网站的 URL。
NEXT_PUBLIC_WORDPRESS_API_URL=https://headlesswp.local
通过添加此环境变量,您可以通过 process.env
对象在应用程序中访问其值。
在更改环境变量时,最好停止并启动本地开发服务器,因为它们通常仅在应用程序启动时加载。不过您可以通过按 ctrl
+ c
,然后再次运行 npm run dev
来重启本地开发服务器来应用改变~。
此时打开你应该看到的是一片空白。
配置 Apollo 客户端
现在我们的 Next.js 应用程序可以访问我们的 WordPress 网站的 URL,我们可以创建用于请求数据的 Apollo Client 实例。
此时已经通过 WordPress 暴露了 GraphQL 接口(通过
https://你的域名/graphql
)。Apollo Client 就是前端用于访问这个接口、发送查询请求的工具。
现在要做的就是配置这个工具。
在空白的 /lib/apollo.js
文件中,添加以下代码:
import { ApolloClient, InMemoryCache } from "@apollo/client";
export const client = new ApolloClient({
uri: `${process.env.NEXT_PUBLIC_WORDPRESS_API_URL}/graphql`,
cache: new InMemoryCache(),
});
这里的第一行代码从 @apollo/client
包导入 ApolloClient
和 InMemoryCache
。
ApolloClient
:Apollo 提供的客户端构造器,用来配置访问服务器的方式。InMemoryCache
: Apollo 默认的缓存策略。它会缓存你之前请求的数据,避免重复发送请求,加快页面速度。
然后,我们在下面创建一个新的 ApolloClient
实例并将其导出为名为 client
的变量。
当我们创建新客户端时,我们传入一个具有 uri
和 cache
属性的配置对象。uri 属性应将 uri
客户端指向服务器的 /graphql
端点,并将 cache
属性分配给新的 InMemoryCache
实例。Apollo Client 的缓存功能通过将查询结果存储在内存中来帮助加快应用程序的速度。
uri
: 你的 GraphQL 服务器地址,也就是 WordPress 后端/graphql
接口。- 用了环境变量:
process.env.NEXT_PUBLIC_WORDPRESS_API_URL
,这样更灵活,方便在本地/线上用不同的地址。
- 用了环境变量:
在我们的客户端完全配置之后,下一步是使用 ApolloProvider
组件使我们的客户端在您的应用程序中可用。
使用 ApolloProvider 包装应用
Apollo 是基于 React Context 实现的,你必须用
<ApolloProvider>
包裹整个应用,才能在各个组件中使用useQuery()
等 API。
即使我们在 /lib/apollo.js
中有一个有效的 ApolloClient
对象,我们仍然需要将其导入某个地方,并使其在我们的 React 应用程序的组件树中可用。为此,我们将对 Next.js 应用程序根目录中的代码进行一些更改,该代码包含在 /pages/_app.js
文件中。
在此文件中,添加以下导入语句。这些语句引入了我们在上一步中创建的 client
和 Apollo Client 提供的 ApolloProvider
组件。
import { ApolloProvider } from "@apollo/client/react";
import { client } from "../lib/apollo";
ApolloProvider
:一个 React 组件,作用是“把 Apollo Client 的配置注入到所有子组件”。client={client}
:传入我们刚刚在/lib/apollo.js
中配置的 Apollo 实例。
接下来,将基础页面 <Component>
包装到 ApolloProvider
组件内部,并将配置的 client
作为 prop 传递:
function MyApp({ Component, pageProps }) {
return (
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
)
}
修改完成后如下所示:
import '../styles/index.css';
import { ApolloProvider } from "@apollo/client/react";
import { client } from "../lib/apollo";
function MyApp({ Component, pageProps }) {
return (
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
);
}
export default MyApp;
使用 ApolloProvider
包装您的应用可让您在应用的任何位置使用 Apollo Client 提供的 React 钩子 ,例如 useQuery()
、 useLazyQuery()
、 useMutation()
等。虽然我们不会在此应用中利用这些钩子,但如果您在 React 项目中使用 Apollo Client,我建议您使用 ApolloProvider
包装您的应用,以便在需要时可以这样做。
ApolloProvider
是 Apollo 提供的一个 Context 提供器(Provider)组件。- 它的作用是:让 React 组件树中的所有组件都可以访问 Apollo Client 的能力,比如你可以在任意组件中使用这些钩子函数:
useQuery()
:发送 GraphQL 查询。useMutation()
:发送 GraphQL 变更(比如新增、删除数据)。useLazyQuery()
:只有用户点击按钮时才发送请求的查询钩子。
4. 使索引界面动态化
现在,我们的组件树中已经有一个完全配置好的数据获取客户端,我们可以开始使本教程网站的页面动态化。首先,在编辑器中打开 /pages/index.js
文件。
我用的是 vscode,所以是这个界面
![]()
Next.js 使用一种称为基于页面的路由的路由方法,这意味着通常情况下,你的网站或应用程序的路由将与你的 /pages
文件夹的文件结构相对应。
你在
/pages
文件夹里创建什么文件,Next.js 就会自动创建一个同名的访问路径(URL)。
类似于这样
文件 | 路由 URL |
---|---|
/pages/index.js |
/ (网站首页) |
/pages/about.js |
/about |
/pages/blog.js |
/blog |
不需要手动写任何路由配置文件,Next.js 会自动帮你完成。/pages/index.js
就是你网站的首页页面。
在这种情况下,目录根目录中的 index.js
文件对应于你的网站根目录,该目录应在 /
URL 处可用。在下一步中,我们将研究如何创建动态路由路径,但请务必阅读有关路由的 Next.js 文档以了解更多详细信息 。
页面组件
当 Next.js 处理基于页面的路由时,它期望给定路由的默认导出是 React 组件,通常称为“页面组件”。让我们看一下 /pages/index.js
路由的默认导出:
export default function Home({ posts }) {
return (
<div className="container">
<Head>
<title>Headless WP Next Starter</title>
<link rel="icon" href="favicon.ico"></link>
</Head>
<main>
<h1 className="title">
Headless WordPress Next.js Starter
</h1>
<p className="description">
Get started by editing <code>pages/index.js</code>
</p>
<div className="grid">
{
posts.map((post) => {
return (
<PostCard key={post.uri} post={post}></PostCard>
)
})
}
</div>
</main>
<Footer></Footer>
</div>
)
}
在本例中,我们默认导出一个名为 HomePage
的组件, HomePage
组件接受一个 props 对象,我们期望该对象上存在一个 posts
属性。此组件的返回值是一个 JSX 元素,可以包含其他嵌套组件,例如 PostCard
和 Footer
。
虽然但是,你找不到 homepage 的,你只能找到 home。简而言之,这个 home 就是指这里的 homepage,别太在意细节。
我这虽然是搬运,但是也懒得改这一点。因为后续可能还要用到。大伙就将就着看吧。
在 HomePage
组件中,我们编写了一个表达式,该表达式遍历 posts
数组,将每个帖子的数据传递到 PostCard
组件中,然后返回该自定义组件。呈现到页面的结果如下所示,即数组中每个帖子的卡片:
目前这个示例还没套上 WP 中的内容,下一步我们将致力于使用从我们的 WordPress 网站获取的数据填充这些组件。
数据获取
现在我们已经大致了解了 HomePage
组件的工作原理,让我们看看该组件如何获取数据。首先,让我们在 /pages/index.js
文件顶部添加两个 import 语句:
import { client } from '../lib/apollo';
import { gql } from "@apollo/client";
在某些 React 开发风格中,尤其是使用客户端渲染的风格,每个组件都可以负责获取自己的数据。但是,由于我们使用 Next.js 静态生成页面,因此本示例的所有数据获取代码都将位于
getStaticProps()
函数内。
当前 index.js
文件中的 getStaticProps()
函数应如下所示:
export async function getStaticProps(){
const response = await getAllPosts()
const posts = response?.data?.posts?.nodes
return {
props: {
posts
}
}
}
为了使此页面动态化,我们将用从 WordPress 网站提取数据的 GraphQL 查询替换对 getAllPosts
函数的调用。
构建 GraphQL 查询
将我们的 Next.js 应用与 WordPress 网站集成的第一步是构建一个 GraphQL 查询,该查询返回我们构建其余功能所需的数据。构建 GraphQL 查询的最常见方法之一是使用 WPGraphQL 附带的内置 GraphiQL IDE 。
在 WordPress 管理侧边栏中,前往 GraphQL
> GraphiQL IDE
开始使用。
使用 GraphiQL IDE 的 QueryComposer
功能,开发人员可以手动从 GraphQL 方案中选择字段添加到查询中,然后实时针对站点的数据库运行该查询。
这对您非常有用,因为您的应用程序会解析来自 GraphQL 的响应,这样开发人员就知道如何准确遍历响应对象及其属性。
编写查询后,您可以点击播放图标(▶️)来执行它并在最右侧的窗格中查看返回的数据。
运行查询
回到 /pages/index.js
,用下面的代码替换 getStaticProps()
函数里面的代码。
在这里,我们将编写的查询传递给 gql
标记的模板文字 (“gql”后跟两个反引号),并将返回值分配给 GET_POSTS
变量。
export async function getStaticProps(){
// 将GraphQL查询粘贴到gql标记的模板文本中
const GET_POSTS = gql`
query AllPostsQuery {
posts {
nodes {
title
content
date
uri
}
}
}
`;
// 这里我们对客户端进行调用,并将查询字符串传递给配置对象的query属性
const response = await client.query({
query: GET_POSTS
});
// 一旦我们得到响应,我们需要遍历它来提取我们想要传递到主页的数据
const posts = response?.data?.posts?.nodes;
return {
props: {
posts
}
}
}
此代码示例对每行代码的作用进行了大量的注释,因此请仔细阅读每行代码,并确保在继续之前理解正在发生的事情。如果您保存并刷新浏览器窗口,我们的演示站点的索引页现在应如下所示(如果您从存储库安装了上述内容):
如果安装的是你自己的内容,这里显示的是你文章的名称。
![]()
你在回顾代码的时候可能会感到疑惑,搞不清楚在输出页面的代码中为什么直接调用了 posts
而非 prop.posts
。
Export default function Home ({ posts }) {
// 相当于 const posts = props.posts;
这是因为这一段用了语法糖,你可以将其修改为
Export default function Home (props) {
Const posts = props.posts;
效果是一样的。
从这里,您可以尝试向 GraphQL 查询添加其他字段,并努力在 PostCard
组件上显示该数据,该组件可以在 /components/PostCard.js
文件中找到。
在下一步中,我们将执行类似的操作,使用 Next.js 中的动态路由创建帖子详细信息页面。
使帖子页面动态化
在上一节中,我们重点介绍了如何使用 Apollo Client 和 WPGraphQL 将多篇博文拉入网站主页。本节将基于该示例,介绍如何使用自定义 URL 为每篇博文创建单独的页面。为此,我们将从动态路由的简短概念概述开始。
动态路由概述
动态路由是 Web 应用程序中的一个重要概念,对于构建任何实际规模的动态网站而言都是必需的,甚至 WordPress 核心也有通过永久链接(固定链接)构建动态路由路径的方法。借助动态路由,我们可以从 URL 结构中提取信息,并使用该信息生成自定义响应。
WordPress 通过设置「固定链接结构」如
https://yoursite.com/blog/%postname%
,实现了每篇文章都有唯一 URL 的效果。
虽然有 20 篇文章、20 个不同的链接,但我们不需要为每篇文章都手写一个 HTML 页面。
因为%postname%
就像是一个变量,WordPress 会根据 URL 自动识别是哪篇文章并动态渲染内容。
在 Next.js 中,也有类似机制:我们可以创建一个动态路由文件(如/pages/blog/[slug].js
),用一个组件就能动态处理所有文章的显示。
让我们看看以下例子:
/blog/getting-started-with-vue
/blog/react-overview
/blog/[title]
上面我们有一个非常典型的链接结构,其中有几篇帖子从 /blog
路径分支出来。当每个 URL 被访问时,接收请求的应用程序可以访问路由的 [title]
部分的值,并使用该值生成请求,方法是查询数据库以查找标题与 URL 中的值匹配的帖子。
Next.js 通过要求页面组件文件遵循命名约定来实现动态路由 。
如果您创建一个名为 /pages/[uri].js
的文件,则可以访问所请求路由的值并提取要在应用程序内部使用的 uri
。
让我们看看这在我们的 SlugPage
组件中是如何实际运作的,它是 /pages/[uri].js
文件的默认导出。
如果我们查看 getStaticProps()
函数内部的代码,我们可以看到此示例接受 params
对象作为参数:
export async function getStaticProps({ params }){
const response = await getPostByUri(params.uri)
const post = response?.data?.post
return {
props: {
post
}
}
}
在这种情况下, params
对象包含有关动态 URL 段的信息。如果我们访问 params.uri
属性,我们将获得特定请求的 URL 段的值,并将其传递给我们的数据获取代码。在后台, getPostByUri()
函数会过滤与请求 uri
匹配的帖子数组。
了解如何使用动态路由是构建 Web 应用程序和使用无头 WordPress 的核心概念,因此请务必查看上面链接的 Next.js 文档以获取有关此功能的更多信息。
预渲染静态路径
使用 getStaticProps()
进行静态生成时,Next.js 会在开始为我们的应用程序提供服务时生成页面的静态版本。
对于像 HomePage
这样的组件,Next.js 可以轻松做到这一点,因为它不需要用户以动态 URL 段的形式输入任何内容——主页对于每个用户都是相同的,并且仅在一个 URL 上可用。
当我们创建动态路由时(例如在 /pages/[uri].js
中所做的那样),Next.js 不可能在不知道 params.uri
的值的情况下预先生成这些页面。
为了解决这个问题,每当我们将 getStaticProps()
与 [uri]
等动态路由段一起使用时,我们还需要导出一个名为 getStaticPaths()
的函数,如下所示:
export async function getStaticPaths(){
const paths = ['/narwal','/kombucha', '/baby-unicorns']
return {
paths,
fallback: 'blocking'
}
}
paths
是你要提前静态生成的页面路径数组。fallback: 'blocking'
的意思是:- 如果用户访问了一个不在
paths
中的页面,Next.js 会先构建它(等 GraphQL 查完数据再渲染),然后缓存起来,给后续用户用。
- 如果用户访问了一个不在
此函数返回的对象上的一个属性应该是我们想要预渲染到静态资产的任何路径的数组。
这允许 Next.js 在用户访问这些页面之前处理这些资源。任何未使用此方法预渲染的动态路由将在第一个用户请求 URL 时生成,然后可供所有后续请求使用。这意味着在此函数中返回一个空的路径数组也是有效的:
export async function getStaticPaths(){
const paths = []
return {
paths,
fallback: 'blocking'
}
}
在此示例中,所有页面都将在第一个用户请求时生成,这意味着第一个请求通常会比后续请求慢得多,因为 Next.js 必须同时渲染和提供页面。
但是,此功能为开发人员优化应用程序提供了极大的灵活性。例如,您可以硬编码一个最热门路径数组来优化这些帖子,但也可以通过多种方式以编程方式创建该数组,例如获取最近十篇博客文章的 URI。
getStaticPaths()
的作用是告诉 Next.js 哪些动态页面应该在构建阶段预先生成。搭配fallback
参数,你可以灵活选择哪些页面在第一次访问时再动态生成。
即使你设置paths: []
,只要某个页面被首次访问后生成,它就会被缓存为静态资源,后续访问体验与预构建页面几乎没有区别。
添加 GraphQL 查询
要更新动态 SlugPage
组件中的数据提取代码,我们首先需要在文件顶部添加两个 import 语句:
import { client } from '../lib/apollo';
import { gql } from "@apollo/client";
有了这些新的导入,我们就可以开始在 GraphiQL IDE 中构建查询了。
打开 IDE,创建一个名为 GetPostByURI
的新查询,然后从架构对象列表中选择 post
。
如果您选中 $id
和 $idType
选项,GraphiQL 将帮助您开始参数化查询,以便我们可以在下一步中传入变量。将您在下面看到的字段添加到查询中,然后在查询编辑器窗口中复制代码。
回到 /pages/[uri].js
文件,我们现在可以替换 getStaticProps
函数中的全部代码以使用此查询:
export async function getStaticProps({ params }){
const GET_POST = gql`
query GetPostByURI($id: ID!) {
post(id: $id, idType: URI) {
title
content
date
author {
node {
firstName
lastName
}
}
}
}
`
// params 是由 Next.js 自动传入的参数,它包含了动态 URL 中的每个变量,比如 [uri].js 中你会拿到 params.uri,它的值就是用户访问的路径段。
const response = await client.query({
query: GET_POST,
variables: {
id: params.uri // 把 URL 中的 slug/uri 当成 $id 的值
}
})
const post = response?.data?.post //拿到的 post 对象
// 丢回去给页面组件
return {
props: {
post
}
}
}
要传递这些变量,请将第二个 variables
对象传递给 client.query
,其中每个变量都是与查询中的变量相对应的属性。在本例中, id: params.uri
将替换 GraphQL 查询中的 $id
。
有了我们的数据获取代码,刷新浏览器并访问您的某篇帖子的页面。如果您在前面的步骤中使用了 XML 导出文件,那么在浏览器中访问 /narwal
路径时应该会看到类似这样的内容。
我没用上述的导出文件,所以拿世界,你好来演示效果。
如果点击文章标题打开的还是 404,那大概率是因为你固定链接的后缀设置的不是只有 postname。
完成!🎉
恭喜您创建了一个无头 WordPress 网站!
还想要了解其他内容的话可以访问 Crash Course: Build a Simple Headless WordPress App with Next.js & WPGraphQL — Builders。
我教程是在这上面找的,这儿只是我自己一边学一边做下来的记录。