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

Remix框架下如何自动生成所有文章路由链接?

Remix博客自动生成文章列表的方案

你可以不用自己从零写扫描脚本,有几个开箱即用的方式实现自动生成文章列表:

1. 在Loader中动态扫描MDX文件

直接在首页路由的loader函数里,用Node.js的glob包扫描路由目录下的MDX文件,提取元数据和路由路径:

// app/routes/_index.tsx
import { glob } from "glob";
import { useLoaderData } from "@remix-run/react";
import type { LoaderFunctionArgs } from "@remix-run/node";

export async function loader({}: LoaderFunctionArgs) {
  // 扫描routes下的MDX文件,排除首页和非文章类路由文件
  const files = await glob("app/routes/**/*.mdx", { 
    ignore: ["app/routes/_index.tsx", "app/routes/*.tsx"] 
  });

  const posts = await Promise.all(
    files.map(async (filePath) => {
      // 转换文件路径为路由路径,比如app/routes/posts/hello.mdx -> /posts/hello
      const routePath = filePath
        .replace("app/routes", "")
        .replace(".mdx", "")
        .replace(/\/index$/, "");
      
      // 动态导入MDX文件获取元数据
      const postModule = await import(`../${filePath.slice(4)}`);
      return {
        path: routePath || "/",
        meta: postModule.meta || {},
      };
    })
  );

  // 按元数据中的创建日期倒序排列
  const sortedPosts = posts.sort((a, b) => 
    new Date(b.meta.createdAt).getTime() - new Date(a.meta.createdAt).getTime()
  );

  return { posts: sortedPosts };
}

export default function Index() {
  const { posts } = useLoaderData<typeof loader>();
  return (
    <div className="blog-index">
      <h1>我的博客</h1>
      <ul className="post-list">
        {posts.map((post) => (
          <li key={post.path} className="post-item">
            <a href={post.path}>{post.meta.title}</a>
            <p className="post-excerpt">{post.meta.description}</p>
            <small className="post-date">
              {new Date(post.meta.createdAt).toLocaleDateString()}
            </small>
          </li>
        ))}
      </ul>
    </div>
  );
}

注意:需要先安装依赖包npm install glob @types/glob,并确保tsconfig.json允许动态导入MDX文件。

2. 构建时生成静态目录文件(你提到的方案)

如果偏好构建时生成静态文件,可在package.json中添加前置脚本:

{
  "scripts": {
    "generate-toc": "node scripts/generate-toc.js",
    "build": "npm run generate-toc && remix build",
    "dev": "npm run generate-toc && remix dev"
  }
}

脚本示例(scripts/generate-toc.js):

const fs = require("fs");
const path = require("path");
const glob = require("glob");

async function generateToc() {
  const files = await glob("app/routes/**/*.mdx", { ignore: ["app/routes/_index.tsx"] });
  const posts = await Promise.all(
    files.map(async (file) => {
      const module = await import(path.resolve(__dirname, "..", file));
      const routePath = file
        .replace("app/routes", "")
        .replace(".mdx", "")
        .replace(/\/index$/, "");
      return { path: routePath, ...module.meta };
    })
  );
  const sortedPosts = posts.sort((a, b) => 
    new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
  );
  const content = `export const posts = ${JSON.stringify(sortedPosts, null, 2)} as const;`;
  fs.writeFileSync(path.resolve(__dirname, "..", "app/data/TableOfContents.ts"), content);
}

generateToc().catch(console.error);

之后在首页路由中直接导入posts变量使用即可。

关于文档提到的扩展性

正如你所说,文章数量不多时,每次修改重新部署完全不是问题,上述方案都能满足“不用手动添加链接”的核心需求,直到文章规模达到需要数据库存储的程度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 06:01:25