最近闲在家里没事干,就想着自己搭建一个网站,加上Next.js最近挺火,就结合了WordPress现成的内容管理系统,建立一个关于旅游攻略的网站。
这边就不具体展开讲解WordPress如何创建文件创建页面等操作,大家具体可看官方文档,操作还算是简单。
项目github地址:github.com/Jessie-jzn/…
搭建Next.js环境
- 使用 create-next-app创建新的 Next.js 应用程序,它会自动为你设置所有内容
npx create-next-app wordpress-next
安装完成后,进入我们刚才创建的文件夹wordpress-next并运行
cd wordpress-next
npm run dev
运行 npm run dev 或 yarn dev 来启动开发服务器,访问地址为 http://localhost:3000。 编辑 pages/index.js 文件并在浏览器中查看更新。
目录大概如下
将WordPress API添加到Next.js中
在根目录下创建一个文件.env文件,将你的WordPress api地址放进去
// .env
WORDPRESS_API_URL="你的WordPress api地址"
# Only required if you want to enable preview mode
WORDPRESS_AUTH_REFRESH_TOKEN="revalidate"
WORDPRESS_PREVIEW_SECRET="preview"
如何获取WordPress api地址?
如下图所示,进入WordPress管理后台,点击GraphQL中的setting,在Endpoint只可以看到一个url,那就是api地址
Next.js项目与WordPress连接
- 在项目的根目录下创建
lib文件夹,创建api.ts文件 - 在
api.ts文件中配置fetchAPI函数
// api.ts
const API_URL: string = process.env.WORDPRESS_API_URL
async function fetchAPI(query: string = '', variables: Record<string, any> = {}): Promise<any> {
try {
const headers: HeadersInit = { 'Content-Type': 'application/json' };
if (process.env.WORDPRESS_AUTH_REFRESH_TOKEN) {
headers['Authorization'] = `Bearer ${process.env.WORDPRESS_AUTH_REFRESH_TOKEN}`;
}
const res = await fetch(API_URL, {
headers,
method: 'POST',
body: JSON.stringify({
query,
variables,
}),
});
const json: ApiResponse = await res.json();
if (json.errors) {
console.error(json.errors);
throw new Error('获取 API 数据失败');
}
return json.data;
} catch (error: any) {
console.error('获取 API 数据时出错:', error);
throw new Error('获取 API 数据失败');
}
}
在WPGraphQL中创建查询
点击运行按钮查看获取的数据是否是我们所要的
获取所有文章列表
在api.ts文件中创建getAllPosts函数,请求WPGraphQL接口获取数据
// api.ts
export async function getAllPosts(): Promise<any[]> {
const data = await fetchAPI(
`query getAllPostQuery {
posts {
edges {
node {
title
excerpt
slug
date
id
content
categories {
edges {
node {
name
}
}
}
featuredImage {
node {
sourceUrl
title
}
}
uri
}
}
}
}
`,
{
variables: {
onlyEnabled: true,
preview: false,
},
}
);
return data?.posts?.edges;
}
在页面中创建getStaticProps函数拿到静态数据,注入到页面的 props 中
// pages/index.tsx
export const getStaticProps: GetStaticProps = async () => {
const edges = await getAllPosts();
return {
props: { edges },
revalidate: 10,
};
};
在页面的 props 拿到数据,正常使用
import Layout from '../components/Layout/index';
import { GetStaticProps } from 'next';
import { getAllPosts } from '../lib/api';
import PostCard from '../components/post-card';
interface WorkProps {
edges: any[]; // 根据实际情况定义 edges 的类型
}
const Index = ({ edges }: WorkProps) => {
return (
<Layout>
<div className="relative px-8">
<div className="max-w-screen-xl mx-auto my-12 md:mt-18 lg:mt-20">
<h1 className="font-display text-secondary-500 text-4xl font-black tracking-wide">
{/* {id} */}
</h1>
</div>
</div>
<div className="relative px-8 mb-12">
<div className="max-w-screen-xl mx-auto">
<div className="grid sm:gap-8 sm:grid-cols-2 lg:grid-cols-3">
{edges.map((edgeItem, index) => (
<PostCard key={index} edgeItem={edgeItem} />
))}
</div>
</div>
</div>
</Layout>
);
};
export default Index;
此刻页面如下:
获取单篇文章详细信息
- 在
api文件中写获取单篇文章数据的getSinglePost函数
// api.ts
export async function getSinglePost(id: string, preview: boolean): Promise<any> {
const data = await fetchAPI(
`query getSinglePost($id: ID!) {
post(id: $id) {
title
categories {
edges {
node {
name
}
}
}
excerpt
content
slug
id
author {
node {
name
firstName
lastName
}
}
}
}
`,
{
id,
variables: {
onlyEnabled: !preview,
preview,
},
}
);
return data?.post;
}
- 在需要展示数据的
pages/posts/[id].tsx中写getStaticProps函数,这里的文件目录是:
完整的代码如下:
// pages/posts/[id].tsx
import Link from 'next/link';
import Layout from '../../components/Layout/index';
import { GetStaticProps, GetStaticPaths } from 'next';
import { getSinglePost, getAllPostsWithId } from '../../lib/api';
import Image from 'next/image';
import { formatTimestampToDate } from '../../lib/util';
interface PostProps {
post: {
title: string;
date: string;
content: string;
featuredImage: {
node: {
sourceUrl: string;
};
};
};
}
export default function Post({ post }: PostProps) {
return (
<Layout>
<div className="relative px-8 mt-6">
<div className="max-w-screen-xl mx-auto">
<div className="h-64 md:h-96 lg:h-[480px] relative overflow-hidden rounded-lg">
<Image
src={post.featuredImage?.node?.sourceUrl}
className="object-cover object-center rounded-t-lg w-full"
layout="fill"
alt={post.featuredImage?.node?.sourceUrl}
/>
</div>
<div className="max-w-3xl mx-auto mt-4">
<div className="border-b-2 border-primary-500 w-8"></div>
<h1 className="font-display text-4xl font-bold my-6 text-secondary-500">
{post.title}
</h1>
<div className="mt-4 uppercase text-gray-600 italic font-semibold text-xs">
{formatTimestampToDate(post.date)}
</div>
<div
className="prose max-w-full mb-20"
dangerouslySetInnerHTML={{ __html: post?.content }}
/>
</div>
</div>
</div>
</Layout>
);
}
export const getStaticProps: GetStaticProps = async ({
params,
preview = false,
}) => {
const post = await getSinglePost(params?.id as string, preview);
return {
props: {
post: post,
},
revalidate: 10,
};
};
export const getStaticPaths: GetStaticPaths = async () => {
try {
const allPostsData = await getAllPostsWithId();
// 使用文章数据来生成路径
// @ts-ignore
const paths = allPostsData.edges.map(({ node }) => `/posts/${node.id}`);
return {
paths: paths,
fallback: true,
};
} catch (error) {
console.error('获取静态路径时出错:', error);
return {
paths: [],
fallback: true,
}
}
};