You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 12:57:16