Skip to content

carloitaben/next-virtual-routes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

22 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

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 }

LICENSE

MIT

About

πŸ€– React Router v7 virtual file routes on Next.js

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published