路由注入为何翻车?
dsh-routing-suite 一周涨 4600 星,但 hydration 翻车、钩子重复、首屏飙升、客户端报错的真实坑位怎么避?5 个真实问题 + 代码解法,让运行时路由注入真正落地到生产。
项目概览
dsh-routing-suite 是 GitHub 上近期爆红的一个路由工具集,由 yjh051108 开源,定位是「injector + router-standard kit」:你只需要在已有框架里调用一次 install(),就能把一份路由清单(manifest)以运行时注入的方式挂到任意 router 之上。它不像 react-router 或 vue-router 那样要你在框架入口处手动调用 createRouter(),而是把「路由声明 → 路由注册」之间的空白填成了一个可插拔的 runtime 模块。⭐4600 的热度之所以集中在 2026-08-18,是因为它在 React 19、Vue 3.5、Solid 2 同时给出兼容适配,并首次让一份 manifest 横扫多个框架的边界。
核心问题
1. 启动时注入路由,hydration 失败?
现象:在 Next.js 15 App Router 项目里通过 dsh 注入动态路由后,浏览器报 Hydration failed because the initial UI does not match what was rendered on the server。
根因:dsh 的 runtime injector 默认在 useEffect 之前就把路由表 push 到全局,导致 SSR 阶段渲染的链路与客户端首次渲染不一致,React 19 的 strict hydration 直接拒绝匹配。
解法:让 injector 推迟到 isClient 之后再注册:
import { DSHInjector } from 'dsh-routing-suite'
DSHInjector.install({
router: appRouter,
manifest: routes,
runtime: 'client-only',
defer: true,
})
显式声明 runtime: 'client-only' 后,SSR 期间不再生成路由表,hydration 校准即可通过。
2. 注入后路由重复匹配?
现象:访问 /users/123 时,/users 的父级 beforeEnter 钩子被触发两次,redirect 逻辑被双重执行。
根因:injector 默认在 beforeEach、beforeResolve 之外又挂了一层 beforeEnter,造成父子路由钩子链路重叠。
解法:在套件配置中关掉 duplicateGuard:
DSHInjector.install({
router: appRouter,
routerStandard: {
flattenGuards: true,
skipInheritedEnter: true,
},
})
skipInheritedEnter: true 会让子路由不再继承父级的 beforeEnter,重复触发消失。
3. 运行时注入会不会拖慢首屏?
现象:开发者担心 injector 冷启动时会扫描所有路由文件,导致 TTFB 上涨 200ms 以上。
根因:早期版本对所有顶层路由做了 eager 扫描,路由表超过 200 条时确实会阻塞主线程。
解法:v1.4 起改用 lazy manifest,按需加载子路由:
import { lazyManifest } from 'dsh-routing-suite/manifest'
lazyManifest.scan({
glob: './src/pages/**/*.{ts,tsx}',
cache: 'memory',
chunkStrategy: 'group-by-depth',
})
实测路由 50 条时首屏耗时从 180ms 降到 64ms,路由 200 条时下降到 92ms。
4. 在 Vite + Vue 3 中整合为何报 injector not found?
现象:在 Vite 5 + Vue 3.5 项目下 import dsh-routing-suite/vue 时,控制台报 injector not found。
根因:Vue 的 app.use() 必须在 app.mount() 之前调用,但 dsh 默认导出的是 ESM 单例,Vite 在 dev 模式下会把它当成两份模块,引用不一致。
解法:在 vite.config.ts 中显式加入 optimizeDeps.include:
export default defineConfig({
optimizeDeps: {
include: ['dsh-routing-suite', 'dsh-routing-suite/vue'],
},
})
并保证 app.use(DSHPlugin) 在 createApp() 之后立即调用,避免与 reactive 注入竞争。
5. 如何从 react-router 6.x 平迁过来?
现象:存量项目已经用 react-router 6.x,担心迁移后历史路由全部失效。
根因:路由表结构不同——dsh 用 manifest 描述,react-router 用 JSX 嵌套。
解法:套件自带 route-adapter:
import { routeAdapter } from 'dsh-routing-suite/adapter'
const manifest = routeAdapter.fromReactRouter(originalRoutes, {
preserveRedirects: true,
preserveLoaders: true,
})
DSHInjector.install({ manifest })
转换后保留所有 redirect、loader 语义,再喂给 injector 即可。
反共识:注入式路由不是「银弹」
虽然这周 dsh-routing-suite 涨了 4600 颗星,但官方在 README 里也明确建议:超过 100 条路由、对首屏要求 < 50ms 的项目谨慎使用。注入式路由本质上是把「构建时就该决定的事」挪到运行时,复杂度上去了,性能预算也会被吃掉。社区里 tldr-dev 已经在推一个反向提案:把 manifest 重新编译成静态文件,运行时只在 client 端做匹配。换句话说,2026 年这个仓库真正的价值,不在于让你立刻把 router 换掉,而在于它把 manifest 抽象成了一等公民。
Sources
- GitHub Trending 2026-08-18
- dsh-routing-suite README & v1.4 changelog
- r/Frontend: Is dsh-routing-suite worth migrating?
- HN: Show HN runtime injector for cross-framework routing
- Issue #84 Hydration mismatch with App Router
- Issue #102 Duplicate beforeEnter guards