Universal Router
框架无关的通用路由库,可在任何 JavaScript 环境(浏览器、Node.js、React、Vue 等)中使用。
Universal Router 是轻量级框架无关路由库,采用 Koa/Express 风格的中间件模式,只负责 URL 匹配和路由解析而不关心 UI 渲染,适合跨框架、微前端和同构应用场景。
Universal Router
框架无关的通用路由库,可在任何 JavaScript 环境(浏览器、Node.js、React、Vue 等)中使用。
技术简介说明
Universal Router 是一个轻量级的、框架无关的路由库,由 Konstantin Tarkushev(Kriasoft)创建。它的核心设计理念是"路由只是中间件"——将路由视为一系列函数组合,每个函数接收上下文并决定是否处理请求。这种设计使其可以在任何 JavaScript 环境中使用:浏览器、Node.js、React、Vue、Angular,甚至 Web Workers。
Universal Router 的灵感来自 Koa.js 和 Express.js 的中间件模式。它的路由表是一个简单的对象数组,每个路由对象包含 path、action 和可选的 children。路由匹配使用 path-to-regexp 库(与 Express 相同的匹配引擎),支持动态参数、通配符、可选参数等模式。
与其他路由库不同,Universal Router 不关心如何渲染 UI——它只负责 URL 匹配和路由解析。你可以将匹配结果传给任何渲染引擎(React、Vue、纯 DOM),这使得它特别适合跨框架场景、微前端架构和同构/通用应用。虽然社区规模较小,但其简洁的 API 和框架无关的设计在特定场景中非常有价值。
基本信息
- 官网:https://github.com/kriasoft/universal-router
- GitHub:https://github.com/kriasoft/universal-router
- License:MIT
- 最新版本:v10.0.2(2025年)
- 主要维护者/公司:Konstantin Tarkushev(Kriasoft)
快速上手
安装
# npm
npm install universal-router
# pnpm
pnpm add universal-router
# yarn
yarn add universal-router基础配置
Universal Router 的配置非常简单——定义路由表,创建路由器实例,调用 resolve 方法。
import Router from 'universal-router'
const routes = [
{
path: '/',
action: () => '<h1>首页</h1>',
},
{
path: '/about',
action: () => '<h1>关于</h1>',
},
{
path: '/user/:name',
action: (context) => `<h1>你好,${context.params.name}!</h1>`,
},
]
const router = new Router(routes)
// 解析路由
router.resolve({ pathname: '/user/john' }).then((result) => {
console.log(result) // '<h1>你好,john!</h1>'
})最小示例
纯 JavaScript 示例:
import Router from 'universal-router'
const router = new Router([
{
path: '/',
action: () => '<h1>首页</h1>',
},
{
path: '/page/:slug',
action: (context) => {
const { slug } = context.params
return `<h1>页面:${slug}</h1>`
},
},
{
path: '(.*)',
action: () => '<h1>404 - 页面未找到</h1>',
},
])
// 使用
async function render(pathname) {
try {
const html = await router.resolve({ pathname })
document.getElementById('app').innerHTML = html
} catch (error) {
console.error(error)
}
}
render('/page/hello')React 集成示例:
import { useState, useEffect } from 'react'
import Router from 'universal-router'
const router = new Router([
{
path: '/',
action: () => <h1>首页</h1>,
},
{
path: '/about',
action: () => <h1>关于</h1>,
},
])
function App() {
const [component, setComponent] = useState(null)
useEffect(() => {
router.resolve({ pathname: window.location.pathname })
.then(setComponent)
}, [])
return <div>{component}</div>
}核心概念与架构
中间件模式
Universal Router 的核心设计是中间件模式。每个路由的 action 函数就是一个中间件,它接收路由上下文(context),可以:
- 返回一个值(字符串、组件、对象等)表示匹配成功
- 返回
undefined或null表示不处理,传递给下一个路由 - 抛出错误表示处理失败
const routes = [
{
path: '/admin',
action: (context) => {
if (!context.user?.isAdmin) {
throw new Error('Unauthorized')
}
return '<h1>管理后台</h1>'
},
},
]路由上下文(Context)
路由解析时,每个路由的 action 接收一个上下文对象,包含:
path:匹配的路由路径模式pathname:当前 URL 路径params:路由参数(如/user/:id中的id)next():手动调用下一个路由- 以及其他自定义属性
router.resolve({
pathname: '/user/123',
customField: 'value', // 自定义字段可在 action 中访问
})嵌套路由
路由支持嵌套结构,子路由继承父路由的路径:
const routes = [
{
path: '/dashboard',
action: () => '<div class="dashboard">',
children: [
{ path: '/', action: () => '<h2>概览</h2>' },
{ path: '/stats', action: () => '<h2>统计</h2>' },
{ path: '/settings', action: () => '<h2>设置</h2>' },
],
},
]路径匹配
使用 path-to-regexp 进行路径匹配,支持:
- 命名参数:
/user/:id - 可选参数:
/file/:name? - 重复参数:
/files/:path+ - 通配符:
/static/(.*) - 正则约束:
/user/:id(\\d+)
异步路由
action 函数可以是异步的,支持 Promise 和 async/await:
const routes = [
{
path: '/post/:id',
action: async (context) => {
const response = await fetch(`/api/posts/${context.params.id}`)
const post = await response.json()
return { post }
},
},
]核心特性
- 框架无关:可在任何 JavaScript 环境和框架中使用
- 中间件模式:路由即函数,灵活的请求处理链
- 异步路由:action 支持 async/await,适合数据获取
- 嵌套路由:支持树形路由结构
- 路径匹配:基于
path-to-regexp,支持参数、通配符、正则约束 - TypeScript 支持:完整的类型定义
- 极小包体积:~4KB gzipped
- 同构/通用:浏览器和 Node.js 环境均可使用
- 路由上下文:丰富的上下文信息传递
- 错误处理:支持在 action 中抛出错误,在 resolve 中捕获
- 无 UI 假设:不关心如何渲染,只负责路由匹配
- 自定义解析:可自定义路由解析逻辑
生态图
关键依赖
- path-to-regexp:路径匹配引擎(与 Express 相同)
框架集成
Universal Router 可以与任何框架集成:
- React:手动集成,将 action 结果作为组件渲染
- Vue:手动集成,将 action 结果作为组件渲染
- 纯 DOM:直接操作 DOM 渲染 HTML
- 服务端:在 Node.js 中处理路由,配合 SSR
- Web Workers:在 Worker 中处理路由逻辑
相关工具
- universal-router-generator:路由路径生成器
- kriasoft/react-starter-kit:使用 Universal Router 的 React 启动模板
- kriasoft/node-stats:Universal Router 的示例项目
社区生态
- 相比 React Router 和 Vue Router,社区生态较小
- 主要用于特定场景:微前端、跨框架路由、同构应用
- 主要用户是 Kriasoft 的开源项目和模板
- GitHub 上有少量教程和示例
适用场景
- 跨框架/框架无关路由:需要在多个框架间共享路由逻辑
- 微前端架构:不同微前端可能使用不同框架,需要统一路由层
- 同构/通用应用:浏览器和 Node.js 共享路由逻辑
- 自定义路由需求:需要完全控制路由解析逻辑
- 服务端路由:在 Node.js 中处理 API 路由或 SSR 路由
- Web Workers:在 Worker 中处理路由匹配
- 学习路由原理:简洁的代码适合学习路由实现原理
- 嵌入式路由:在小型库或组件中嵌入路由能力
开发与工程化
开发流程
# 安装
npm install universal-router
# 直接在项目中使用,无需额外配置
npm run dev项目结构建议
src/
├── router/
│ ├── index.ts # 路由器实例
│ ├── routes.ts # 路由配置
│ ├── middleware/ # 路由中间件
│ │ ├── auth.ts # 鉴权中间件
│ │ └── logger.ts # 日志中间件
│ └── context.ts # 上下文类型定义
├── pages/ # 页面组件
└── app.ts # 应用入口
React 集成
import { useState, useEffect, useCallback } from 'react'
import Router from 'universal-router'
import type { Context } from 'universal-router'
// 定义路由上下文类型
interface AppContext extends Context {
user?: User
store?: Store
}
const router = new Router<AppContext, React.ReactNode>([
{
path: '/',
action: () => <HomePage />,
},
{
path: '/user/:id',
action: async ({ params, user }) => {
if (!user) throw new Error('Unauthorized')
const userData = await fetchUser(params.id)
return <UserPage user={userData} />
},
},
])
function App() {
const [page, setPage] = useState<React.ReactNode>(null)
const [user, setUser] = useState<User | null>(null)
const navigate = useCallback(async (pathname: string) => {
try {
const result = await router.resolve({
pathname,
user: user ?? undefined,
})
setPage(result)
} catch (error) {
setPage(<ErrorPage error={error} />)
}
}, [user])
useEffect(() => {
navigate(window.location.pathname)
}, [])
return <div>{page}</div>
}Node.js 服务端
import express from 'express'
import Router from 'universal-router'
const app = express()
const router = new Router([
{
path: '/api/users/:id',
action: async ({ params }) => {
const user = await db.users.findById(params.id)
return { user }
},
},
])
app.get('*', async (req, res) => {
try {
const result = await router.resolve({ pathname: req.path })
res.json(result)
} catch (error) {
res.status(500).json({ error: error.message })
}
})测试
import Router from 'universal-router'
const routes = [
{ path: '/', action: () => 'home' },
{ path: '/about', action: () => 'about' },
{ path: '(.*)', action: () => 'not-found' },
]
describe('Router', () => {
const router = new Router(routes)
it('matches home route', async () => {
const result = await router.resolve({ pathname: '/' })
expect(result).toBe('home')
})
it('matches about route', async () => {
const result = await router.resolve({ pathname: '/about' })
expect(result).toBe('about')
})
it('matches 404 for unknown route', async () => {
const result = await router.resolve({ pathname: '/unknown' })
expect(result).toBe('not-found')
})
})性能与安全
性能特点
- 轻量级:~4KB gzipped,对包体积影响小
- 高效匹配:基于
path-to-regexp的正则匹配 - 无 UI 开销:不涉及 DOM 操作或框架渲染
- 异步友好:支持并行数据获取
优化建议
- 路由表保持扁平,减少嵌套层级
- 将频繁匹配的路由放在前面
- 使用精确的路径模式,避免宽泛的通配符
- 缓存频繁访问的路由解析结果
- 在 Node.js 中使用连接池和缓存优化数据获取
安全注意事项
- 路径注入:
path-to-regexp的模式需要验证,避免 ReDoS - XSS 防护:action 中返回的 HTML 需要转义用户输入
- 开放重定向:路由重定向目标需要白名单验证
- 鉴权:在 action 中验证用户权限
- 速率限制:服务端路由需要配合速率限制
已知性能瓶颈
- 大量路由(200+)时的匹配开销
- 复杂的正则模式可能影响匹配速度
- 嵌套路由的递归匹配
- 异步 action 的串行执行(无内置并行机制)
技术对比
| 特性 | Universal Router | React Router v7 | Vue Router 4 | Express Router |
|---|---|---|---|---|
| 框架绑定 | 无(框架无关) | React 专属 | Vue 3 专属 | Express/Node.js |
| 包体积 | ~4KB | ~45KB | ~18KB | 内置 |
| UI 渲染 | 不涉及 | React 组件 | Vue 组件 | 不涉及 |
| 浏览器支持 | ✅ | ✅ | ✅ | ❌(Node.js) |
| Node.js 支持 | ✅ | ✅(SSR) | ⚠️ 有限 | ✅ |
| 路径匹配 | path-to-regexp | 自研 | 自研 | path-to-regexp |
| 嵌套路由 | ✅ | ✅ | ✅ | ✅ |
| 中间件 | ✅ action 函数 | ❌ | ❌ | ✅ |
| 异步路由 | ✅ | ✅ loader | ❌ | ✅ |
| TypeScript | ✅ | ✅ | ✅ | ✅ |
| 学习曲线 | 低 | 中等 | 低 | 低 |
| 社区规模 | 小型 | 极大 | 丰富 | 极大 |
最佳实践
路由组织
// ✅ 推荐:模块化路由配置
// routes/index.ts
import type { Route } from 'universal-router'
import { homeRoutes } from './home'
import { userRoutes } from './user'
import { adminRoutes } from './admin'
export const routes: Route[] = [
...homeRoutes,
...userRoutes,
...adminRoutes,
{
path: '(.*)',
action: () => ({ error: 'NOT_FOUND', message: '页面未找到' }),
},
]中间件模式
// ✅ 推荐:使用 action 链实现中间件
const authMiddleware = async (context: AppContext, next: () => Promise<any>) => {
if (!context.user) {
throw new Error('Unauthorized')
}
return next()
}
const routes: Route[] = [
{
path: '/dashboard',
action: async (context) => {
// 手动实现中间件链
if (!context.user) throw new Error('Unauthorized')
return <Dashboard user={context.user} />
},
},
]错误处理
// ✅ 推荐:统一错误处理
async function handleRoute(pathname: string) {
try {
const result = await router.resolve({ pathname })
return { status: 200, data: result }
} catch (error) {
if (error.message === 'Unauthorized') {
return { status: 401, data: { redirect: '/login' } }
}
if (error.message === 'Not Found') {
return { status: 404, data: { error: 'NOT_FOUND' } }
}
return { status: 500, data: { error: 'INTERNAL_ERROR' } }
}
}常见陷阱
- ❌ 不要假设 Universal Router 提供 UI 渲染——它只负责路由匹配
- ❌ 不要忘记处理 404 情况——添加通配符路由兜底
- ❌ 不要在 action 中直接操作 DOM——保持框架无关性
- ❌ 不要忽略异步错误——始终使用 try/catch 包裹 resolve 调用
- ❌ 不要在路由 action 中执行副作用——action 应该是纯函数或幂等的
技术局限与边界
已知限制
- 无 UI 集成:不提供任何 UI 渲染能力,需要自行集成
- 无内置守卫:路由鉴权需要在 action 中手动实现
- 无数据加载 API:没有 loader/action 模式
- 无预加载:没有路由预加载和预取功能
- 社区规模小:第三方资源和教程有限
- 无路由动画:不提供路由切换动画支持
- 无 DevTools:没有专门的开发者工具
不适用场景
- 需要完整 UI 路由方案的项目(使用 React Router / Vue Router)
- 需要类型安全路由路径的 TypeScript 项目
- 需要复杂路由守卫和鉴权的应用
- 需要 SSR/SSG 完整方案的项目
- 需要丰富社区生态和插件的项目
迁移注意事项
- Universal Router → React Router:需要将 action 函数重构为 loader/element 模式
- Universal Router → Vue Router:需要重构路由配置和渲染逻辑
- 迁移工作量取决于 action 函数的复杂度
- 建议:如果是纯 UI 路由需求,从一开始就选择框架专属路由库
学习资源
- GitHub 仓库(含文档):https://github.com/kriasoft/universal-router
- 示例:https://github.com/kriasoft/universal-router/tree/master/examples
- 教程:
- 社区资源:
- 相关项目:
2026 年现状
最新版本
Universal Router v10.0.x 是当前稳定版本(2025年),保持低频维护更新。
发展趋势
- 维护模式:更新频率很低,以 bug 修复和依赖更新为主
- 稳定 API:API 已非常稳定,很少有大变更
- Niche 定位:明确定位为框架无关的轻量路由方案
- 依赖更新:主要工作是保持
path-to-regexp等依赖的更新
社区活跃度
- GitHub Stars:~2.8k
- npm 周下载量:~30万+
- 贡献者:20+
- 更新频率:每年 1-2 次更新
未来路线图
- 依赖版本维护
- 可能的 TypeScript 类型改进
- 不会有大的功能扩展
- 保持框架无关的极简定位