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

Gatsby博客相关文章加载后重复显示问题求助

Gatsby相关文章区块加载后重复显示问题修复

问题描述

基于Gatsby搭建的博客,文章页的相关文章区块初始显示正常,但页面加载约30秒后,部分或全部相关文章出现重复。推测当某篇文章与当前文章匹配多个标签时,会在列表中重复显示(例如当前文章带有tag-1、tag-2、tag-3,另一篇文章同时带有tag-1和tag-2,就会重复出现在相关列表中)。

相关代码

import React from "react";
import { Link, StaticQuery, graphql } from "gatsby";

const RelatedArticles = ({ posts }) => {
  return (
    <div>
      {posts.map(({ node }) => {
        const title = node.frontmatter.title;
        return (
          <div key={node.fields.slug}>
            <div>
              <Link to={`/blog${node.fields.slug}`}>
                {title}
              </Link>
            </div>
            <small>{node.frontmatter.date}</small>
          </div>
        );
      })}
    </div>
  );
};

export const relatedArticlesQuery = graphql`
  query {
    allMdx(sort: { fields: [frontmatter___date], order: DESC }) {
      edges {
        node {
          excerpt
          fields {
            slug
          }
          frontmatter {
            date(formatString: "MMMM DD, YYYY")
            title
            tags
          }
        }
      }
    }
  }
`;

const RelatedPosts = (props) => {
  const related = [];

  return (
    <StaticQuery
      query={relatedArticlesQuery}
      render={(data) => {
        const {
            allMdx: { edges },
        } = data;

        // iterate through article vertex
        for (const vertex of edges) {
          // handle base case
          if (related.length >= props.limit) {
            continue;
          }

          // abstract tags
          const {
            frontmatter: { tags },
          } = vertex.node;

          // handle case where there are no tags or prop tags
          if (!tags || !props.tags) {
            continue;
          }

          const formattedTags = tags

          // ensure tags match and article is not the same as current
          if (
            props.tags.some((item) => formattedTags.includes(item)) &&
            props.title !== vertex.node.frontmatter.title
          ) {
            related.push(vertex);
          }
        }

        // render posts
        return (
          <div className="related">
            <h3>
              Вам может понравиться
            </h3>
            <RelatedArticles posts={related} />
          </div>
        );
      }}
    />
  );
};

export default RelatedPosts

博客文章模板代码

const BlogPost = ({ data , children }) => {
    const image = getImage(data.mdx.frontmatter.featuredImage)
    const tags = data.mdx.frontmatter.tags
    const categories = data.mdx.frontmatter.categories
    return (
        <Layout pageTitle={data.mdx.frontmatter.title}>
            <ul>{tags && tags
                .map(tag => tag.trim())
                .filter(tag => tag.length > 0)
                .map(tag => <li key={tag}><Link to={`/blog/tag/${_.kebabCase(transliterate(tag))}`}>{tag}</Link></li>)}
            </ul>
            <ul>{categories && categories
            .map(category => category.trim())
                .filter(category => category.length > 0)
                .map(category => <li key={category}><Link to={`/blog/category/${_.kebabCase(transliterate(category))}`}>{category}</Link></li>)}
            </ul>
            <p>{data.mdx.frontmatter.date}</p>
            <GatsbyImage
                image={image}
            />
            {children}
            <RelatedPosts tags={data.mdx.frontmatter.tags} limit={6} title={data.mdx.frontmatter.title} />
        </Layout>
    )
}

export const query = graphql`
    query ($id: String) {
        mdx(id: {eq: $id}) {
            frontmatter {
                title
                date(formatString: "DD MMMM YYYY")
                featuredImage {
                    childImageSharp {
                        gatsbyImageData
                    }
                }
                tags
                categories
                type

            }
            excerpt
            body
        }

    }
`

export const Head = ({ data }) => (
    <Seo
        title={data.mdx.frontmatter.title}
        description={data.mdx.excerpt}
    />
)

export default BlogPost

问题分析

  1. 数组作用域错误:related数组定义在StaticQuery的render回调外部,当render回调被多次执行(比如Gatsby的服务器端渲染+客户端hydration,或者数据更新),数组不会被重置,会持续累加符合条件的文章,导致重复。
  2. 缺少去重逻辑:即使单篇文章因多标签匹配被多次遍历(原逻辑中不会,但如果数据异常或逻辑调整可能出现),没有机制阻止重复添加同一篇文章。

修复方案

修改RelatedPosts组件,将related数组移到render回调内部,并添加基于文章slug的去重逻辑:

const RelatedPosts = (props) => {
  return (
    <StaticQuery
      query={relatedArticlesQuery}
      render={(data) => {
        const { allMdx: { edges } } = data;
        const related = [];
        const addedSlugs = new Set(); // 用于记录已添加的文章slug,避免重复

        for (const vertex of edges) {
          if (related.length >= props.limit) {
            break; // 达到数量限制后直接退出循环,比continue更高效
          }

          const { node } = vertex;
          const { tags, title } = node.frontmatter;
          const slug = node.fields.slug;

          if (!tags || !props.tags) {
            continue;
          }

          // 跳过当前文章,以及已经添加过的文章
          if (title === props.title || addedSlugs.has(slug)) {
            continue;
          }

          // 检查是否有匹配的标签
          const hasMatchingTag = props.tags.some(item => tags.includes(item));
          if (hasMatchingTag) {
            related.push(vertex);
            addedSlugs.add(slug);
          }
        }

        return (
          <div className="related">
            <h3>Вам может понравиться</h3>
            <RelatedArticles posts={related} />
          </div>
        );
      }}
    />
  );
};

修复要点

  • 将related数组和addedSlugs集合移到render回调内部,确保每次回调执行时都从空开始,避免累加。
  • 使用Set记录已添加的文章slug,彻底阻止同一篇文章被重复添加,无论是否因多标签匹配或回调多次执行。
  • 将continue改为break,当达到limit数量时直接退出循环,提升性能。
  • 简化变量提取逻辑,让代码更清晰。

内容的提问来源于stack exchange,提问作者Лео

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 10:36:53