next-virtual-routes
v0.4.0
Published
React Router v7 virtual file routes on Next.js.
Downloads
214
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-routes
Then, 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 }
|