Gatsby通过gatsby-node配置blog与projects双路径动态路由方案
实现方案
官方starter的onCreateNode逻辑硬编码了content/blog作为slug生成的基准路径,所有Markdown文件都会被统一加上/blog前缀生成路由,也没有做内容分类处理,才会出现无法区分两类内容的问题。完全基于文件原生路径实现路由区分不需要修改任何Markdown文件的frontmatter,只需要调整gatsby-node.js的三处逻辑即可:
- 修改
onCreateNode钩子,自动识别Markdown文件所属的子目录,生成对应前缀的slug,同时标记内容类型
不要硬编码basePath为content/blog,先通过节点关系拿到当前Markdown文件的真实存储路径,判断是属于content/blog还是content/projects目录,分别生成/blog/xxx、/project/xxx格式的slug,同时新增contentType字段标记内容分类,方便后续页面生成时做区分。 - 调整
createPages的查询和页面生成逻辑
GraphQL查询时把新增的contentType字段一起查出来,拿到节点列表后按内容类型拆分成博客、项目两组,分别引用对应的模板生成页面,同类型的内容才做上下篇关联,避免博客和项目的导航串数据。 - 更新Schema定义,把新增的
contentType字段加到Fields类型里,保证构建时字段校验不报错。
修改后的完整gatsby-node.js代码如下:
const path = require(`path`) const { createFilePath } = require(`gatsby-source-filesystem`) exports.createPages = async ({ graphql, actions, reporter }) => { const { createPage } = actions // 分别定义两类内容的模板路径 const blogPost = path.resolve(`./src/templates/blog-post.js`) const projectPost = path.resolve(`./src/templates/project-post.js`) // 查询所有Markdown文件,带上内容类型字段 const result = await graphql( ` { allMarkdownRemark( sort: { fields: [frontmatter___date], order: ASC } limit: 1000 ) { nodes { id fields { slug contentType } } } } ` ) if (result.errors) { reporter.panicOnBuild( `加载内容文件出错`, result.errors ) return } const allNodes = result.data.allMarkdownRemark.nodes // 按内容类型拆分两组 const blogPosts = allNodes.filter(node => node.fields.contentType === 'blog') const projectPosts = allNodes.filter(node => node.fields.contentType === 'project') // 生成博客文章页面 if (blogPosts.length > 0) { blogPosts.forEach((post, index) => { const previousPostId = index === 0 ? null : blogPosts[index - 1].id const nextPostId = index === blogPosts.length - 1 ? null : blogPosts[index + 1].id createPage({ path: post.fields.slug, component: blogPost, context: { id: post.id, previousPostId, nextPostId, }, }) }) } // 生成项目内容页面 if (projectPosts.length > 0) { projectPosts.forEach((post, index) => { const previousProjectId = index === 0 ? null : projectPosts[index - 1].id const nextProjectId = index === projectPosts.length - 1 ? null : projectPosts[index + 1].id createPage({ path: post.fields.slug, component: projectPost, context: { id: post.id, previousProjectId, nextProjectId, }, }) }) } } exports.onCreateNode = ({ node, actions, getNode }) => { const { createNodeField } = actions if (node.internal.type === `MarkdownRemark`) { // 拿到当前md文件对应的源文件节点 const fileNode = getNode(node.parent) // 兼容跨系统路径分隔符,判断文件所属目录 const isBlog = fileNode.absolutePath.includes(`${path.sep}content${path.sep}blog${path.sep}`) const isProject = fileNode.absolutePath.includes(`${path.sep}content${path.sep}projects${path.sep}`) let slugPrefix = '' let contentType = '' let basePath = '' if (isBlog) { slugPrefix = '/blog' contentType = 'blog' basePath = 'content/blog' } else if (isProject) { slugPrefix = '/project' contentType = 'project' basePath = 'content/projects' } const fileSlug = createFilePath({ node, getNode, basePath }) // 写入对应路由slug createNodeField({ name: `slug`, node, value: `${slugPrefix}${fileSlug}`, }) // 写入内容类型标记 createNodeField({ name: `contentType`, node, value: contentType, }) } } exports.createSchemaCustomization = ({ actions }) => { const { createTypes } = actions createTypes(` type SiteSiteMetadata { author: Author siteUrl: String social: Social } type Author { name: String summary: String } type Social { twitter: String } type MarkdownRemark implements Node { frontmatter: Frontmatter fields: Fields } type Frontmatter { title: String description: String date: Date @dateformat } type Fields { slug: String contentType: String } `) }
注意:如果你的项目详情模板路径不是
./src/templates/project-post.js,替换成实际的模板文件路径即可。后续如果要新增其他分类的Markdown内容,只需要在onCreateNode里增加对应目录的判断分支,在createPages里补充对应分组的页面生成逻辑即可,不需要修改任何Markdown源文件。
内容的提问来源于stack exchange,提问作者Raymond Yung
相关产品推荐
相关产品推荐

