next-virtual-routes
v0.4.0
Published
React Router v7 virtual file routes on Next.js.
Maintainers
Readme
next-virtual-routes
React Router v7 virtual* file routes on Next.js.
*Not really 🤪
Features
- Programatically generate Next.js App Router files.
- Mix and match with file-based routing.
- Reusable file templates.
- Fully typesafe.
Installation
npm install next-virtual-routesThen, add the following to your next.config.ts file:
// next.config.ts
import { withRoutes } from "next-virtual-routes"
export default withRoutes({
routes: [
/* Routes config */
],
/* Next.js config */
})The routes property accepts the following values:
- An array of
route()calls. - A function that returns an array of
route()calls. - An async function that returns an array of
route()calls.
For advanced configuration, you can also pass an object:
// next.config.ts
import { withRoutes } from "next-virtual-routes"
export default withRoutes({
routes: {
formatter: "prettier",
strict: true,
config: [
/* Routes config */
],
},
/* Next.js config */
})A lower level function is also exposed for cases where you need more control.
// next.config.ts
import { generateRoutes } from "next-virtual-routes"
export default async () => {
await generateRoutes([
/* Routes config */
])
return {
/* Next.js config */
}
}Usage
Call route in your routes configuration to programatically create a route file.
Pass the file and a path template relative to your next.config.ts.
// next.config.ts
import { route, withRoutes } from "next-virtual-routes"
export default withRoutes({
routes: [route("blog/page.tsx", "src/templates/page.tsx")],
})Then, create the template.
// src/templates/page.tsx
export function Page() {
return "Hello world"
}This generates the /src/app/blog/page.tsx file with the following content:
// src/app/blog/page.tsx
export function Page() {
return "Hello world"
}[!WARNING] Always import files inside templates using path aliases to prevent errors.
Passing context to templates
You can optionally pass a serializable context object as a third parameter.
// next.config.ts
export default withRoutes({
routes: [
route("home/page.tsx", "src/templates/page.tsx", {
static: true,
}),
route("blog/page.tsx", "src/templates/page.tsx", {
static: false,
}),
],
})If you are using TypeScript, you can use declaration merging to add a type
to the context object.
// next.config.ts
declare module "next-virtual-routes" {
interface Context {
static: boolean
}
}
export default withRoutes({
routes: [
route("home/page.tsx", "src/templates/page.tsx", {
static: true,
}),
route("blog/page.tsx", "src/templates/page.tsx", {
static: false,
}),
],
})You can then access this data in your templates using the context global object.
// src/templates/page.tsx
export const dynamic = context.static ? "force-static" : "force-dynamic"
export function Page() {
return context.static ? "Static rendering" : "Dynamic rendering"
}Named exports with statically analyzable expressions are evaluated when applying the template. The previous template generates the following content:
// src/app/home/page.tsx
const context = {
static: true,
}
export const dynamic = "force-static"
export function Page() {
return context.static ? "Static rendering" : "Dynamic rendering"
}
// src/app/about/page.tsx
const context = {
static: false,
}
export const dynamic = "force-dynamic"
export function Page() {
return context.static ? "Static rendering" : "Dynamic rendering"
}This enables programmatic control of Route Segment configuration, Middleware matchers and more.
API
Functions
route
Programatically generates a route.
| Function | Type |
| ---------- | ---------- |
| route | (path: RouteFilePath, template: string, context?: Context or undefined) => Route |
Examples:
export default withRoutes({
routes: [route("blog/page.tsx", "src/templates/page.tsx")],
})Use declaration merging to add a type to the context object.
declare module "next-virtual-routes" {
interface Context {
static: boolean
}
}
export default withRoutes({
routes: [
route("home/page.tsx", "src/templates/page.tsx", {
static: true,
}),
route("blog/page.tsx", "src/templates/page.tsx", {
static: false,
}),
],
})prefix
Adds a path prefix to a set of routes.
| Function | Type |
| ---------- | ---------- |
| prefix | (prefix: string, children: Route[]) => Route[] |
Examples:
const routes = [
...prefix("blog", [
route("page.tsx", "src/templates/page.tsx"),
route("[...slug]/page.tsx", "src/templates/page.tsx"),
])
]context
Adds context to a set of routes. Nested context is deeply merged.
| Function | Type |
| ---------- | ---------- |
| context | (context: Context, children: Route[]) => Route[] |
Examples:
declare module "next-virtual-routes" { interface Context { render: "static" | "dynamic" } }
const routes = [
...context({ render: "static" }, [
route("page.tsx", "src/templates/page.tsx"),
route("page.tsx", "src/templates/page.tsx"),
])
]generateRoutes
TODO: document
| Function | Type |
| ---------- | ---------- |
| generateRoutes | (config: RoutesDefinition or RoutesPluginConfig) => Promise<void> |
withRoutes
TODO: document
| Function | Type |
| ---------- | ---------- |
| withRoutes | ({ routes, ...nextConfig }: NextConfigWithRoutesPlugin) => Promise<NextConfig> |
Interfaces
Context
TODO: document
| Property | Type | Description | | ---------- | ---------- | ---------- |
Types
Route
TODO: document
| Type | Type |
| ---------- | ---------- |
| Route | { path: string template: string context?: Context } |
RoutesDefinition
TODO: document
| Type | Type |
| ---------- | ---------- |
| RoutesDefinition | Route[] or (() => Route[] or Promise<Route[]>) |
RoutesPluginConfig
TODO: document
| Type | Type |
| ---------- | ---------- |
| RoutesPluginConfig | { config: RoutesDefinition banner?: string[] footer?: string[] cwd?: string log?: boolean cache?: boolean watch?: boolean cacheFile?: string clearAppDir?: boolean formatter?: "prettier" formatterConfigFile?: string } |
