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

基于GraphQL在Gatsby中从两类MDX文件生成独立页面的技术求助

解决Gatsby中多文件夹MDX内容独立生成页面的问题

核心思路

通过自定义GraphQL类型+路径过滤区分不同文件夹的MDX内容,从根源避免数据冲突和页面误生成,同时为未来扩展新内容类型(如猫咪目录)预留统一的实现逻辑。


步骤1:给MDX节点标记内容类型

在gatsby-node.js中添加节点处理逻辑,根据文件所属文件夹给MDX打专属类型标签:

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions;
  // 只处理MDX类型节点
  if (node.internal.type === 'Mdx') {
    const parentFile = getNode(node.parent);
    const relativePath = parentFile.relativePath;

    // 根据文件路径标记内容类型
    if (relativePath.startsWith('blog/')) {
      createNodeField({ node, name: 'contentType', value: 'blog' });
    } else if (relativePath.startsWith('news/')) {
      createNodeField({ node, name: 'contentType', value: 'news' });
    }
    // 未来扩展猫咪目录时,添加:
    // else if (relativePath.startsWith('cats/')) {
    //   createNodeField({ node, name: 'contentType', value: 'cat' });
    // }
  }
};

步骤2:编写独立的数据查询Hook

分别创建针对博客、新闻的查询Hook,只获取对应类型的内容,避免数据覆盖:

博客数据Hook(src/hooks/useBlogPosts.js)

import { useStaticQuery, graphql } from 'gatsby';

export const useBlogPosts = () => {
  const data = useStaticQuery(graphql`
    query {
      allMdx(filter: { fields: { contentType: { eq: "blog" } } }) {
        nodes {
          id
          frontmatter {
            header
            date(formatString: "YYYY-MM-DD")
          }
          fields {
            slug
          }
        }
      }
    }
  `);
  return data.allMdx.nodes;
};

新闻数据Hook(src/hooks/useNewsPosts.js)

import { useStaticQuery, graphql } from 'gatsby';

export const useNewsPosts = () => {
  const data = useStaticQuery(graphql`
    query {
      allMdx(filter: { fields: { contentType: { eq: "news" } } }) {
        nodes {
          id
          frontmatter {
            title
            date(formatString: "YYYY-MM-DD")
          }
          fields {
            slug
          }
        }
      }
    }
  `);
  return data.allMdx.nodes;
};

步骤3:生成独立的详情页与列表页

在gatsby-node.js中分别查询对应类型数据,生成专属页面:

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions;

  // 生成博客详情页
  const blogData = await graphql(`
    query {
      allMdx(filter: { fields: { contentType: { eq: "blog" } } }) {
        nodes {
          fields {
            slug
          }
        }
      }
    }
  `);
  blogData.data.allMdx.nodes.forEach(node => {
    createPage({
      path: `/blog/${node.fields.slug}`,
      component: require.resolve('./src/templates/blog-post.js'),
      context: { slug: node.fields.slug },
    });
  });

  // 生成新闻详情页
  const newsData = await graphql(`
    query {
      allMdx(filter: { fields: { contentType: { eq: "news" } } }) {
        nodes {
          fields {
            slug
          }
        }
      }
    }
  `);
  newsData.data.allMdx.nodes.forEach(node => {
    createPage({
      path: `/news/${node.fields.slug}`,
      component: require.resolve('./src/templates/news-post.js'),
      context: { slug: node.fields.slug },
    });
  });

  // 生成博客列表页
  createPage({
    path: '/blog',
    component: require.resolve('./src/templates/blog-list.js'),
  });

  // 生成新闻列表页
  createPage({
    path: '/news',
    component: require.resolve('./src/templates/news-list.js'),
  });
};

步骤4:编写对应模板文件

博客详情页模板(src/templates/blog-post.js)

import { graphql } from 'gatsby';
import { MDXRenderer } from 'gatsby-plugin-mdx';

export const query = graphql`
  query($slug: String!) {
    mdx(fields: { contentType: { eq: "blog" }, slug: { eq: $slug } }) {
      frontmatter {
        header
        date(formatString: "YYYY-MM-DD")
      }
      body
    }
  }
`;

const BlogPost = ({ data }) => {
  const post = data.mdx;
  return (
    <div>
      <h1>{post.frontmatter.header}</h1>
      <p className="date">{post.frontmatter.date}</p>
      <MDXRenderer>{post.body}</MDXRenderer>
    </div>
  );
};

export default BlogPost;

新闻详情页模板(src/templates/news-post.js)

import { graphql } from 'gatsby';
import { MDXRenderer } from 'gatsby-plugin-mdx';

export const query = graphql`
  query($slug: String!) {
    mdx(fields: { contentType: { eq: "news" }, slug: { eq: $slug } }) {
      frontmatter {
        title
        date(formatString: "YYYY-MM-DD")
      }
      body
    }
  }
`;

const NewsPost = ({ data }) => {
  const post = data.mdx;
  return (
    <div>
      <h1>{post.frontmatter.title}</h1>
      <p className="date">{post.frontmatter.date}</p>
      <MDXRenderer>{post.body}</MDXRenderer>
    </div>
  );
};

export default NewsPost;

列表页示例(博客列表src/templates/blog-list.js)

import { useBlogPosts } from '../hooks/useBlogPosts';

const BlogList = () => {
  const posts = useBlogPosts();
  return (
    <div className="blog-list">
      <h1>博客列表</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <a href={`/blog/${post.fields.slug}`}>{post.frontmatter.header}</a>
            <span>{post.frontmatter.date}</span>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default BlogList;

关键注意事项

  1. 路径匹配要准确:确保onCreateNode中判断的relativePath和你实际的文件夹结构一致(比如博客文件是否在content/blog目录下)
  2. 清理缓存:修改gatsby-node.js后,必须运行gatsby clean再重新构建,避免旧数据残留
  3. 扩展新类型:未来添加猫咪目录等内容时,只需重复上述流程:添加类型标记、编写Hook、生成页面、创建模板即可

内容的提问来源于stack exchange,提问作者Tecnika

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 05:55:15