如何在Next.js中像Gatsby那样配置GraphQL及相关插件?
在Next.js中实现类Gatsby的GraphQL数据查询方案
能不能在Next.js中实现类似Gatsby的GraphQL配置?
Next.js本身没有内置Gatsby那种自动将文件转化为GraphQL可查询节点的机制,但可以通过两种方式实现类似的数据查询能力:
- 手动搭建GraphQL服务:在Next.js的API路由中,用GraphQL Yoga、Apollo Server等工具搭建自己的GraphQL服务,手动读取本地MDX文件、解析frontmatter,将数据暴露为GraphQL可查询的字段。
- 结合静态生成能力直接获取数据:如果不需要GraphQL,也可以在
getStaticProps/getStaticPaths中直接用Node.js工具读取并解析MDX文件,这种方式更轻量化,也是Next.js推荐的静态站点数据获取方式。
Gatsby插件的Next.js替代方案
Gatsby的gatsby-source-filesystem、gatsby-transformer-remark等插件,在Next.js生态中可以用以下工具替代:
- 替代gatsby-source-filesystem:用Node.js原生
fs模块或glob库遍历本地文件系统,读取MDX/Markdown文件的路径和内容。 - 替代gatsby-transformer-remark/gatsby-plugin-mdx:用
gray-matter提取MDX文件的frontmatter元数据,用@next/mdx或next-mdx-remote解析MDX内容并渲染。
示例1:无GraphQL的MDX数据获取(轻量化方案)
先封装一个读取MDX文件的工具函数:
// lib/mdx.js import fs from 'fs' import path from 'path' import matter from 'gray-matter' import { serialize } from 'next-mdx-remote/serialize' // 定义MDX文件存放目录 const MDX_DIR = path.join(process.cwd(), 'content') // 根据slug获取单个MDX文件的元数据和内容 export async function getMdxPost(slug) { const filePath = path.join(MDX_DIR, `${slug}.mdx`) const rawContent = fs.readFileSync(filePath, 'utf-8') // 分离frontmatter和MDX内容 const { data: frontmatter, content } = matter(rawContent) // 序列化MDX内容用于渲染 const mdxSource = await serialize(content) return { frontmatter, mdxSource } } // 获取所有MDX文件的frontmatter元数据 export async function getAllMdxFrontmatter() { const fileNames = fs.readdirSync(MDX_DIR) return fileNames.map(fileName => { const slug = fileName.replace(/\.mdx$/, '') const filePath = path.join(MDX_DIR, fileName) const rawContent = fs.readFileSync(filePath, 'utf-8') const { data: frontmatter } = matter(rawContent) return { slug, ...frontmatter } }) }
然后在页面中使用:
// pages/posts/[slug].js import { MDXRemote } from 'next-mdx-remote' import { getMdxPost, getAllMdxFrontmatter } from '../../lib/mdx' // 生成静态路径 export async function getStaticPaths() { const posts = await getAllMdxFrontmatter() return { paths: posts.map(post => ({ params: { slug: post.slug } })), fallback: false } } // 获取页面数据 export async function getStaticProps({ params }) { const post = await getMdxPost(params.slug) return { props: post } } // 渲染页面 export default function PostPage({ frontmatter, mdxSource }) { return ( <article> <h1>{frontmatter.title}</h1> <div className="post-meta"> <span>{frontmatter.date}</span> <span>{frontmatter.country}</span> </div> <div className="post-tags"> {frontmatter.tags.map(tag => <span key={tag}>{tag}</span>)} </div> <MDXRemote {...mdxSource} /> </article> ) }
示例2:手动搭建GraphQL服务实现类Gatsby查询
如果一定要用GraphQL查询,可以在Next.js中搭建GraphQL服务:
首先安装依赖:npm install graphql-yoga gray-matter
然后创建API路由:
// pages/api/graphql.js import { createYoga, createSchema } from 'graphql-yoga' import fs from 'fs' import path from 'path' import matter from 'gray-matter' const MDX_DIR = path.join(process.cwd(), 'content') // 定义GraphQL Schema const schema = createSchema({ typeDefs: ` type Frontmatter { tags: [String] slug: String! title: String! date: String! country: String categories: [String] } type Mdx { frontmatter: Frontmatter! } type Query { # 根据slug查询单个MDX文件 mdx(slug: String!): Mdx # 查询所有MDX文件的元数据 allMdx: [Mdx] } `, // 实现解析器 resolvers: { Query: { mdx: (_, { slug }) => { const filePath = path.join(MDX_DIR, `${slug}.mdx`) const rawContent = fs.readFileSync(filePath, 'utf-8') const { data: frontmatter } = matter(rawContent) return { frontmatter } }, allMdx: () => { const fileNames = fs.readdirSync(MDX_DIR) return fileNames.map(fileName => { const slug = fileName.replace(/\.mdx$/, '') const filePath = path.join(MDX_DIR, fileName) const rawContent = fs.readFileSync(filePath, 'utf-8') const { data: frontmatter } = matter(rawContent) return { frontmatter: { ...frontmatter, slug } } }) } } } }) // 创建GraphQL Yoga实例 export default createYoga({ schema, graphqlEndpoint: '/api/graphql' }) // 禁用Next.js默认的body解析,交给GraphQL Yoga处理 export const config = { api: { bodyParser: false } }
之后就可以用Apollo Client等工具在页面中发起GraphQL查询,比如:
// 先在_app.js中配置Apollo Client import { ApolloProvider, ApolloClient, InMemoryCache } from '@apollo/client' const client = new ApolloClient({ uri: '/api/graphql', cache: new InMemoryCache() }) export default function App({ Component, pageProps }) { return ( <ApolloProvider client={client}> <Component {...pageProps} /> </ApolloProvider> ) }
然后在页面中查询数据:
// pages/posts/[slug].js import { useQuery, gql } from '@apollo/client' import { useRouter } from 'next/router' const GET_MDX_POST = gql` query GetMdxPost($slug: String!) { mdx(slug: $slug) { frontmatter { tags slug title date country categories } } } ` export default function PostPage() { const router = useRouter() const { slug } = router.query const { loading, error, data } = useQuery(GET_MDX_POST, { variables: { slug }, skip: !slug }) if (loading) return <div>加载中...</div> if (error) return <div>出错了:{error.message}</div> if (!data?.mdx) return <div>文章不存在</div> const { frontmatter } = data.mdx return ( <article> <h1>{frontmatter.title}</h1> {/* 渲染其他元数据和内容 */} </article> ) }
内容的提问来源于stack exchange,提问作者AngelusMortifer
相关产品推荐
相关产品推荐

