bash
// pages/index.ts
export default () => new Response("首页");
bash
// server.ts
import { FileSystemRouter } from "bun";
const r = new FileSystemRouter({ dir: "./pages" });
Bun.serve({
fetch: async (req) => {
const m = r.match(req);
return m ? (await import(m.filePath)).default(req, m) : new Response("404");
},
});
bun run server.ts 就跑起来了。访问 / 返回"首页"。再加个 pages/about.ts,/about 就有了。文件名,就是路由。

痛点:手写路由表,写了十几年
写电商、写 OA、写 CRM。php、java、js、nodejs、bunjs、ts、python,全用过。
每个项目,第一件事就是写路由表:
bash
// express
app.get("/", home);
app.get("/user/:id", user);
app.post("/user", createUser);
app.use("/api", apiRouter);
几十个路由,加一个写一行。改路径名,全局搜替换。拼错一个字母,线上 404。
Next.js 出来后,省事了。pages/about.ts 就是 /about。不用注册,不用配置。
但 Next.js 太重了。react、ssr、webpack、babel...就为个文件路由,整个全家桶拉起来。
bun 给了个轻量选项。
FileSystemRouter 是啥?
bun 内置模块,零依赖。
就是把"文件路径"映射成"URL"。
不绑定 react,不绑定 ssr。不绑定任何框架。
配合 Bun.serve 单独跑。
bash
import { FileSystemRouter } from "bun";
const router = new FileSystemRouter({
style: "nextjs",
dir: "./pages",
origin: "https://mydomain.com",
assetPrefix: "_next/static/",
fileExtensions: [".ts", ".tsx", ".js", ".jsx"],
});
style 固定 "nextjs",这是唯一支持的风格。dir 是扫描目录,必填。origin 用于拼 src 字段。assetPrefix 是静态资源前缀。fileExtensions 控制识别哪些后缀,默认是常见 web 后缀。
匹配规则:
-
•
/→pages/index.ts -
•
/about→pages/about.ts -
•
/user/123→pages/user/[id].ts -
•
/blog/a/b→pages/blog/[...slug].ts
跟 Next.js 一模一样。
动态路由怎么写?
跟 Next.js 一样,方括号包参数。
目录:
bash
pages/
index.ts
user/
[id].ts
blog/
[...slug].ts
[id].ts 是单个参数。[...slug].ts 是通配,吃多级路径。
代码:
bash
// pages/user/[id].ts
export default (_req, params) => {
return Response.json({
id: params.id,
name: `用户${params.id}`,
});
};
params.id 直接拿到 123。
通配:
bash
// pages/blog/[...slug].ts
export default (_req, params) => {
return Response.json({
slug: params.slug, // "a/b/c"
});
};
/blog/a/b/c,params.slug 就是 "a/b/c"。
可选通配(双层方括号):
bash
// pages/[[...slug]].ts
export default (_req, params) => {
return Response.json({
slug: params.slug ?? "", // 没匹配上时是 undefined
});
};
/foo/bar 走它,/ 也走它。
新增文件要 reload
路由是在初始化时扫一次。后面加文件、改文件名,必须手动 reload:
bash
import { FileSystemRouter } from "bun";
const router = new FileSystemRouter({
style: "nextjs",
dir: "./pages",
});
// 加了 pages/new.ts 后,调一下:
router.reload();
Bun.serve({
fetch: async (req) => {
const m = router.match(req);
return m ? (await import(m.filePath)).default(req, m) : new Response("404");
},
});
自己写个文件 watcher 监听 pages 目录,调 reload() 就行。
完整跑起来
目录结构:
bash
api/
index.ts
user/
[id].ts
api/index.ts:
bash
export default () => Response.json({
name: "api",
version: "1.0.0",
});
api/user/[id].ts:
bash
export default (_req, params) => Response.json({
id: params.id,
orders: [],
});
server.ts:
bash
import { FileSystemRouter } from "bun";
const r = new FileSystemRouter({
style: "nextjs",
dir: "./api",
});
Bun.serve({
port: 3000,
fetch: async (req) => {
const m = r.match(req);
if (!m) return new Response("404", { status: 404 });
const mod = await import(m.filePath);
return mod.default(req, m);
},
});
bun run server.ts,搞定。/api 返回版本信息。/api/user/123 返回用户信息。
零配置,零路由表。
跟 Next.js 比,差啥?
差远了。
Next.js 包含:
-
• 文件路由
-
• react ssr
-
• 静态生成
-
• 图片优化
-
• middleware
-
• api routes
-
• 完整 dev 工具链
FileSystemRouter 只有:
- • 文件路由
就这一项。
所以别拿它当 Next.js 替代品。
正确用法:
-
• api 服务
-
• bff 层
-
• 边缘函数
-
• 小工具后台
这些场景,不需要 react。一个 bun runtime 全搞定。
启动速度差几百倍。冷启动几毫秒。next.js dev 启动几十秒。
跟 Bun.serve HTML 路由配合
Bun.serve 自己也支持 HTML 路由:
bash
Bun.serve({
routes: {
"/": new Response("首页"),
"/health": new Response("ok"),
},
});
这是手动写死。FileSystemRouter 是扫文件。
两个能配合:
bash
Bun.serve({
port: 3000,
routes: {
"/health": new Response("ok"),
},
fetch: async (req) => {
const m = router.match(req);
if (!m) return new Response("404", { status: 404 });
const mod = await import(m.filePath);
return mod.default(req, m);
},
});
固定路由走 routes。文件路由走 fetch。两个不冲突。
写代码小细节
详细参数看 bun 官方文档。
工具而已,对号入座
FileSystemRouter 不是什么银弹。就是一个文件路由的小工具。
不替代 next.js。不替代 nuxt。不替代任何前端框架。
用在合适的场景:
-
• 写个 api 服务
-
• 写个 bff 中间层
-
• 写个边缘函数
-
• 写个内部工具
启动快,配置少,文件即路由。
要 ssr、要 react、要生态链,老老实实 next.js。要轻量、要简单、要快,FileSystemRouter 够用。
工具而已,对号入座。