本文目录导读:
这是一个很专业的问题,全栈框架(如 Next.js、Nuxt.js、SvelteKit、Remix 等)通常基于文件系统约定路由(即文件名即路径),但在复杂业务中,常需要自定义路由规则以应对动态路径、参数校验、重定向或非标准 URL 映射。
不同框架的实现方式不同,以下是主流全栈框架的自定义路由规则策略:
Next.js (React)
核心方式: 通过 middleware.ts + 文件系统路由,或使用 rewrites / redirects 配置。
-
方法A:
next.config.js中的rewrites/redirects(静态映射) 适合:将/blog-2024映射到/blog/[year],或实现 URL 重写(不改变浏览器地址栏)与重定向。// next.config.js module.exports = { async rewrites() { return [ { source: '/api/:path*', destination: 'https://external-api.com/:path*' }, // 代理 { source: '/old-path', destination: '/new-path' }, // URL 重写 { source: '/user/:id', has: [{ type: 'query', key: 'lang' }], destination: '/user/:id?lang=:lang' } ]; }, async redirects() { return [ { source: '/legacy', destination: '/new-page', permanent: true }, ]; } }; -
方法B:
middleware.ts(动态逻辑) 适合:需要根据 Cookie、Header、用户状态等动态重写路由(如 A/B 测试、国际化前缀)。// middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { const { pathname } = request.nextUrl // 自定义规则:如果访问 /product/123,但用户是管理员,重定向到 /admin/product/123 if (pathname.startsWith('/product/') && request.cookies.get('role') === 'admin') { return NextResponse.redirect(new URL(`/admin${pathname}`, request.url)) } return NextResponse.next() }
Nuxt.js (Vue)
核心方式: pages/ 目录 + middleware/ + router.options.ts 或 app/router.options.ts。
-
方法A:
middleware(全局或局部)// middleware/custom-route.global.ts export default defineNuxtRouteMiddleware((to, from) => { if (to.path === '/legacy' && !to.query.token) { return navigateTo('/login') } }) -
方法B:自定义
router.options.ts(高阶自定义) 适合:完全自定义路由表,或添加通配符路由。// app/router.options.ts import type { RouterConfig } from '@nuxt/schema' export default <RouterConfig> { routes: (_routes) => [ // 插入自定义规则 { path: '/custom/:slug', component: () => import('~/pages/[...slug].vue'), name: 'custom' }, // 然后追加 Nuxt 自动生成的路由 ..._routes ] }
SvelteKit
核心方式: src/params/ + src/routes/ 的 [param] 约定 + hooks.server.ts。
-
方法:自定义参数匹配器(高级) 适合:限制参数格式(如数字、UUID)、转换参数。
// src/params/integer.ts export function match(value) { return /^\d+$/.test(value) } // 路由文件:src/routes/[id=integer]/+page.server.ts export async function load({ params }) { // params.id 保证是数字字符串 } -
方法:
src/hooks.server.ts中的handle适合:全局路由拦截、重定向。// src/hooks.server.ts export async function handle({ event, resolve }) { const url = new URL(event.request.url) if (url.pathname.startsWith('/app') && !event.cookies.get('session')) { return new Response(null, { status: 307, headers: { location: '/login' } }) } return await resolve(event) }
Remix (React)
核心方式: app/routes/ 文件系统 + resource routes + loader/action 中的条件判断。
由于 Remix 的 loader 运行在服务端,你可以直接在函数内实现复杂路由逻辑。
-
方法:在
loader中动态跳转// app/routes/$.tsx(Splat 路由,捕获所有未匹配路径) import { redirect } from '@remix-run/node' export async function loader({ request }) { const url = new URL(request.url) if (url.pathname.startsWith('/api')) { // 可在此动态分发到不同处理函数 return proxyRequest(url) } throw new Response('Not Found', { status: 404 }) }
自定义 Node.js 全栈(如 Express + Vite)
如果你是自己搭建全栈框架(非 Next/Nuxt),则可以完全控制路由。
// 自定义路由规则中间件
const customRouter = (req, res, next) => {
if (req.path === '/user/:id' && req.query.mode === 'admin') {
req.params.id = `admin-${req.params.id}` // 改写参数
req.url = `/user/${req.params.id}` // 重写 URL
}
next()
}
app.use(customRouter)
app.get('/user/:id', (req, res) => { /* ... */ })
不同场景的最佳实践
| 场景 | 推荐方法 |
|---|---|
静态 URL 重写(如 /old → /new) |
Next.js:rewrites 配置;Nuxt:redirect 配置;SvelteKit:hooks.server.ts |
| 参数验证与转换(如只允许数字 ID) | SvelteKit:src/params/;Next.js:middleware 中验证 |
国际化前缀(如 /en/page、/zh/page) |
Next.js:middleware 判断 Accept-Language 并 rewrite;Nuxt:i18n 模块 |
| A/B 测试(根据 Cookie 展示不同页面) | Next.js:middleware 中 rewrite 到不同路径 |
| 完全自定义路由表(不依赖文件系统) | Nuxt:router.options.ts;Next.js:自定义 server.js 使用 app.getPathRoutes 不适合,建议用 middleware 或 rewrites |
关键思路: 尽量利用框架提供的 中间件/钩子/配置 来完成路由自定义,而不是试图绕过文件系统路由引擎(除非迫不得已),大多数框架的设计哲学是“约定优于配置”,但同时也提供了足够的“逃生舱”来自定义规则。
如果需要针对某个具体框架的极端自定义(如动态注册路由、拦截所有请求),请告知你使用的框架,我可以提供更深入的代码示例。