react-router/dev/routes is not Available @ Runtime

Y
Mon Jul 14 2025·18 min read

TL;DR: config 選項可以作為 function 提供使用者所需的 functions / env。

前情提要

我的 side project 是用 React Router v7,前身是 Remix,使用 app/routes.ts 定義 routes。

為了讓未來專案可以快速有個 solid base,所以這三天在處理 routes 跟 config,準備把現在這個已經有「Better Auth、Dashboard、TipTap Rich Editor、Email Service、Object Storage(我用 Cloudflare R2)」的專案,擴充一個「services」資料夾,未來其他專案的內容可以模組化的直接放在裡面,並只需要定義 config.tsx 就能在 Dashboard 顯示側邊按鈕,以及自定義任何 route。


先來看看原本的 config type。

export interface Service {
	// ...
	/**
	 * Routes specific to the service
	 * @example
	 * ```
	 * routes: [
	 * 		route('/new-shop', './routes/services/new-service/shop/layout.tsx', [
	 * 		index('./routes/services/new-service/shop/index.tsx'),
	 * 		route(
	 * 			':productId',
	 * 			'./routes/services/new-service/shop/product/route.tsx',
	 * 		),
	 * 	]),
	 * ]
	 * ```
	 */
	routes?: RouteConfig
	// ...
}

這邊 routes 請未來的我直接提供 rrv7 官方的 routes.ts 用法,所以必須直接 import { route, index } from "@react-router/dev/routes"

實際的 config.ts 看起來像是這樣:

import { route, index } from "@react-router/dev/routes"

export const config = {
	dashboard: {
        logo: '/logo-100.png',
    },
	routes: [
		route(
			'/example-shop',
			'./routes/services/example-service/shop/layout.tsx',
			[
				index('./routes/services/example-service/shop/index.tsx'),
				route(
					':productId',
					'./routes/services/example-service/shop/product/route.tsx',
				),
			],
		),
	],
	// ...
} satisfies Service

因為 config 會用到 route & index,而且會被其他 runtime 讀取(例如 /dashboard 讀取 service 的 logo),因此會被包進去最終的 bundle,造成在運行時(runtime)找不到位在 package.json devDependencies 的 react-router/dev

解決方法:將原本的 routes Array 轉換成 Function

import type {
	index,
	layout,
	prefix,
	route,
	RouteConfig,
} from '@react-router/dev/routes'

type RouteHelper = {
	index: typeof index
	route: typeof route
	layout: typeof layout
	prefix: typeof prefix
}

export interface Service {
	// ...
	/**
	 * Routes specific to the service
	 * @example
	 * ```
	 * routes: ({ route, index }) => [
	 * 		route('/new-shop', './routes/services/new-service/shop/layout.tsx', [
	 * 		index('./routes/services/new-service/shop/index.tsx'),
	 * 		route(
	 * 			':productId',
	 * 			'./routes/services/new-service/shop/product/route.tsx',
	 * 		),
	 * 	]),
	 * ]
	 * ```
	 */
	routes?: (helper: RouteHelper) => RouteConfig
	// ...
}

這樣 service config 的 routes 就會是一個很單純的,不需要任何 import 的 Function!

只在 build 時建立 routes

原本的方式,讓開發者不管怎樣都要 import react-routes/dev,現在則是只有 type,並且只有在真的會用到 routes 的地方(例如 app/routes.ts),才會 import,並在使用時丟入 { index, route, prefix, layout } 就能取得。

像是下面這個:

import {
	index,
	layout,
	prefix,
	route,
	type RouteConfig,
} from '@react-router/dev/routes'

export const servicesRoutes = () => {
	const modules = getServiceRoutesModules()
	const servicesRoutes: RouteConfig = []

	/**
	 * Automatically includes all service routes without manual imports
	 */
	for (const [path, service] of Object.entries(modules)) {
		try {
			if (!service.routes) continue
			// 丟入四個 route config functions,取得實際的 routes
			const routes = service.routes({
				index,
				route,
				prefix,
				layout,
			})

			if (Array.isArray(routes)) {
				servicesRoutes.push(...routes)
			}
		} catch (error) {
			console.error(`Failed to load routes from ${path}:`, error)
		}
	}

	return servicesRoutes
}

就這樣!其實很多大專案也是這樣處理的,像是 vite defineConfig,或是 better-auth 的 betterAuth()

如果對我的 Side Project 有興趣的話可以去 Papa GitHub resource 看看~

  • Papa
  • Open Source
  • react-router

Subscribe to new posts!

If you like topics like Tech, Software Development, or Travel. Welcome to subscribe for free to get some fresh ideas!