Gatsby多内容类型配置求助:实现Markdown多模板与Frontmatter适配
嘿,我之前正好折腾过Gatsby里多内容类型+独立模板的场景,其实没你想的那么复杂,咱们一步步捋清楚:
第一步:整理内容目录结构
先把不同类型的Markdown文件分开存放,这样后续好区分。比如在项目根目录建个content文件夹,下面再拆两个子目录:
content/blog:专门放你的BlogPost类型Markdown文件,比如你给的示例:
title: My Post
date: "2017-09-21"This is my blog body
content/projects:放Project类型的Markdown文件,比如可以加自己需要的Frontmatter字段(比如techStack、demoUrl之类的)
第二步:配置文件系统源插件
在gatsby-config.js里,给gatsby-source-filesystem插件配置两个实例,分别读取上面的两个目录,重点是给每个实例加name字段,这个字段是后续区分内容类型的关键:
module.exports = { plugins: [ { resolve: `gatsby-source-filesystem`, options: { name: `blog`, // 标记这是博客内容 path: `${__dirname}/content/blog/`, }, }, { resolve: `gatsby-source-filesystem`, options: { name: `projects`, // 标记这是项目内容 path: `${__dirname}/content/projects/`, }, }, `gatsby-transformer-remark`, // 别忘了加这个解析Markdown的插件 ], }
第三步:生成Slug并创建页面
接下来在gatsby-node.js里做两件事:一是给每个Markdown文件生成访问路径(slug),二是根据内容类型匹配对应的模板并创建页面。
首先加生成slug的逻辑:
const { createFilePath } = require(`gatsby-source-filesystem`) exports.onCreateNode = ({ node, getNode, actions }) => { const { createNodeField } = actions // 只处理Markdown文件 if (node.internal.type === `MarkdownRemark`) { // 从文件路径生成slug const slug = createFilePath({ node, getNode, basePath: `content` }) createNodeField({ node, name: `slug`, value: slug, }) } }
然后加创建页面的逻辑,通过sourceInstanceName(就是刚才配置的name字段)判断内容类型,分配对应模板:
exports.createPages = async ({ graphql, actions }) => { const { createPage } = actions // 查询所有Markdown内容 const queryResult = await graphql(` query AllContentQuery { allMarkdownRemark { nodes { id fields { slug } sourceInstanceName # 拿到之前标记的内容类型 } } } `) // 遍历每个内容节点,创建对应页面 queryResult.data.allMarkdownRemark.nodes.forEach(node => { let templatePath // 根据类型选择模板 if (node.sourceInstanceName === `blog`) { templatePath = `./src/templates/blog-post.js` } else if (node.sourceInstanceName === `projects`) { templatePath = `./src/templates/project.js` } createPage({ path: node.fields.slug, // 页面访问路径 component: require.resolve(templatePath), // 对应的模板文件 context: { id: node.id, // 把节点ID传给模板,方便后续查询内容 }, }) }) }
第四步:创建独立模板文件
现在分别写两个模板,每个模板对应自己的内容字段。
比如src/templates/blog-post.js,专门渲染BlogPost内容:
import React from "react" import { graphql } from "gatsby" export default function BlogPostTemplate({ data }) { const post = data.markdownRemark return ( <div className="blog-post"> <h1>{post.frontmatter.title}</h1> <p className="post-date">{post.frontmatter.date}</p> {/* 渲染Markdown正文 */} <div dangerouslySetInnerHTML={{ __html: post.html }} /> </div> ) } // 通过ID查询对应的博客内容 export const query = graphql` query BlogPostQuery($id: String!) { markdownRemark(id: { eq: $id }) { html frontmatter { title date } } } `
再写src/templates/project.js,适配Project的Frontmatter字段(比如假设Project有title、techStack、description):
import React from "react" import { graphql } from "gatsby" export default function ProjectTemplate({ data }) { const project = data.markdownRemark return ( <div className="project"> <h1>{project.frontmatter.title}</h1> <div className="tech-stack"> <span>技术栈:</span> {project.frontmatter.techStack.join(", ")} </div> <div className="project-description"> <p>{project.frontmatter.description}</p> </div> <div dangerouslySetInnerHTML={{ __html: project.html }} /> </div> ) } export const query = graphql` query ProjectQuery($id: String!) { markdownRemark(id: { eq: $id }) { html frontmatter { title techStack description } } } `
最后测试一下
把你的Markdown文件放到对应的目录,运行gatsby develop,访问生成的slug路径(比如/blog/my-post/),就能看到不同类型的内容用各自的模板渲染啦!
如果不想用目录区分类型,也可以在每个Markdown的Frontmatter里加type: "blog"或者type: "project",然后在GraphQL查询里用frontmatter.type来判断,不过用目录的方式更直观,也方便管理。
内容的提问来源于stack exchange,提问作者dvreed77

