路由注入为何翻车?

dsh-routing-suite 一周涨 4600 星,但 hydration 翻车、钩子重复、首屏飙升、客户端报错的真实坑位怎么避?5 个真实问题 + 代码解法,让运行时路由注入真正落地到生产。

源仓库: yjh051108/dsh-routing-suite

项目概览

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 默认在 beforeEachbeforeResolve 之外又挂了一层 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 })

转换后保留所有 redirectloader 语义,再喂给 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