Universal Router logo

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 的中间件模式。它的路由表是一个简单的对象数组,每个路由对象包含 pathaction 和可选的 children。路由匹配使用 path-to-regexp 库(与 Express 相同的匹配引擎),支持动态参数、通配符、可选参数等模式。

与其他路由库不同,Universal Router 不关心如何渲染 UI——它只负责 URL 匹配和路由解析。你可以将匹配结果传给任何渲染引擎(React、Vue、纯 DOM),这使得它特别适合跨框架场景、微前端架构和同构/通用应用。虽然社区规模较小,但其简洁的 API 和框架无关的设计在特定场景中非常有价值。

基本信息

快速上手

安装

# 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),可以:

  • 返回一个值(字符串、组件、对象等)表示匹配成功
  • 返回 undefinednull 表示不处理,传递给下一个路由
  • 抛出错误表示处理失败
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 RouterReact Router v7Vue Router 4Express 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 路由需求,从一开始就选择框架专属路由库

学习资源

2026 年现状

最新版本

Universal Router v10.0.x 是当前稳定版本(2025年),保持低频维护更新。

发展趋势

  • 维护模式:更新频率很低,以 bug 修复和依赖更新为主
  • 稳定 API:API 已非常稳定,很少有大变更
  • Niche 定位:明确定位为框架无关的轻量路由方案
  • 依赖更新:主要工作是保持 path-to-regexp 等依赖的更新

社区活跃度

  • GitHub Stars:~2.8k
  • npm 周下载量:~30万+
  • 贡献者:20+
  • 更新频率:每年 1-2 次更新

未来路线图

  • 依赖版本维护
  • 可能的 TypeScript 类型改进
  • 不会有大的功能扩展
  • 保持框架无关的极简定位