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

NextJS中实现文件系统生成Markdown帖子的随机与标签筛选

问题描述

使用NextJS开发产品展示项目,通过File System Module(fs)读取Markdown文件生成静态帖子页面。当前遇到两个问题:

  1. 无法实现随机帖子组件:尝试用lodash的sampleSize实现,但未达到预期效果
  2. 无法实现按标签筛选帖子的功能

尝试的随机帖子代码:

const _ = require("lodash");
const randomizePost = data.map((post) => {
  const { title, path } = post;
  return (
    <PostList key={`${title}`} title={title} path={path}></PostList>
  );
});
let result = _.sampleSize(randomizePost, 4);
return <ul className="list-group post-list">{result}</ul>;
解决方案

一、修复随机帖子组件

你的问题出在先渲染组件再抽样,React元素是复杂对象,直接对其抽样可能导致渲染异常或性能问题。正确做法是先对原始帖子数据抽样,再渲染组件:

import _ from 'lodash';

// 假设data是从getStaticProps获取的所有帖子原始数据数组
const RandomPosts = ({ posts }) => {
  // 先对原始数据随机抽取4条
  const randomPosts = _.sampleSize(posts, 4);
  
  return (
    <ul className="list-group post-list">
      {randomPosts.map(post => (
        // 用path作为key(唯一且稳定),避免title重复导致的key冲突
        <PostList key={post.path} title={post.title} path={post.path} />
      ))}
    </ul>
  );
};

export default RandomPosts;

二、实现按标签筛选功能

要实现标签筛选,需要先建立标签-帖子的索引映射,步骤如下:

1. 在数据获取阶段提取标签并构建索引

在getStaticProps或专门的文件处理函数中,读取所有Markdown文件时,提取每个帖子的标签字段,然后整理成索引:

import fs from 'fs';
import path from 'path';
import matter from 'gray-matter'; // 假设用gray-matter解析Markdown FrontMatter

const postsDirectory = path.join(process.cwd(), 'posts');

export const getAllPostsWithTags = () => {
  // 获取所有Markdown文件名
  const fileNames = fs.readdirSync(postsDirectory);
  
  const allPostsData = fileNames.map(fileName => {
    // 移除文件扩展名
    const id = fileName.replace(/\.md$/, '');
    // 构建文件路径
    const fullPath = path.join(postsDirectory, fileName);
    // 读取文件内容
    const fileContents = fs.readFileSync(fullPath, 'utf8');
    // 解析FrontMatter(假设你的Markdown里有tags字段)
    const matterResult = matter(fileContents);
    
    return {
      id,
      path: `/posts/${id}`, // 帖子的静态路径
      title: matterResult.data.title,
      tags: matterResult.data.tags || [] // 提取标签,默认空数组
    };
  });

  // 构建标签索引:{ tagName: [post1, post2, ...] }
  const tagIndex = allPostsData.reduce((index, post) => {
    post.tags.forEach(tag => {
      if (!index[tag]) {
        index[tag] = [];
      }
      index[tag].push(post);
    });
    return index;
  }, {});

  return { allPostsData, tagIndex };
};

2. 在筛选组件中使用标签索引

在页面组件中,获取标签索引和所有帖子,然后根据选中的标签过滤帖子:

import { useState } from 'react';
import { getAllPostsWithTags } from '../lib/posts';

const TagFilterPage = ({ allPostsData, tagIndex }) => {
  // 获取所有唯一标签
  const allTags = Object.keys(tagIndex);
  // 选中的标签状态,默认显示全部
  const [selectedTag, setSelectedTag] = useState('all');

  // 根据选中标签筛选帖子
  const filteredPosts = selectedTag === 'all' 
    ? allPostsData 
    : tagIndex[selectedTag] || [];

  return (
    <div>
      {/* 标签选择器 */}
      <div className="tag-selector">
        <button 
          onClick={() => setSelectedTag('all')}
          className={selectedTag === 'all' ? 'active' : ''}
        >
          全部
        </button>
        {allTags.map(tag => (
          <button
            key={tag}
            onClick={() => setSelectedTag(tag)}
            className={selectedTag === tag ? 'active' : ''}
          >
            {tag}
          </button>
        ))}
      </div>

      {/* 筛选后的帖子列表 */}
      <ul className="list-group post-list">
        {filteredPosts.map(post => (
          <PostList key={post.path} title={post.title} path={post.path} />
        ))}
      </ul>
    </div>
  );
};

// 在getStaticProps中获取数据
export async function getStaticProps() {
  const { allPostsData, tagIndex } = getAllPostsWithTags();
  return {
    props: {
      allPostsData,
      tagIndex
    }
  };
}

export default TagFilterPage;

关键注意事项

  • 确保Markdown文件的FrontMatter中包含tags字段(比如tags: ["产品设计", "前端"])
  • 标签索引要在构建静态页面时生成(getStaticProps),避免客户端重复计算
  • 随机帖子组件中,始终基于原始数据抽样,而非已渲染的React元素

内容的提问来源于stack exchange,提问作者Deny Farras Febriyanto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 00:01:09