Routing
Claim a URL in VitNode - plugin manifest routes for reusable packages, and host app routes for site-specific pages.
VitNode uses TanStack Start for routing. Routes are divided into three tiers:
| Tier | Declaration | Location | Purpose |
|---|---|---|---|
| Plugin Routes | routes/manifest.ts | plugins/*/src/routes/manifest.ts | Recommended. Reusable across any VitNode install. |
| Application Routes | File-based routes | apps/web/src/routes/** | Site-specific pages owned directly by your app. |
| Core Routes | Code-based routes | Built into @vitnode/core | System routes (/login, /admin/*, /search). |
1. Create a Page in a Plugin (Recommended)
Plugins declare routes as serializable data. The build system mounts them into the route tree with SSR and automatic code splitting.
Declare the Route in routes/manifest.ts
import type { PluginRouteDefinition } from "@vitnode/core/routing"
export const routes: PluginRouteDefinition[] = [
{
entry: "routes/blog-page",
id: "blog",
path: "/blog",
},
]Create the Page Component
const BlogPage = () => (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold">Blog Overview</h1>
</div>
)
export default BlogPage2. Create a Page in Your Application
When creating a page that belongs only to your host site, add a file in apps/web/src/routes/:
Choose a Pathless Shell
_main/for public pages (with header, navigation, and footer)._admin/for administrative screens.
Create the Route File
import { createFileRoute } from "@tanstack/react-router"
import { pageHead } from "#/lib/page-head"
export const Route = createFileRoute("/_main/about")({
head: () =>
pageHead({
title: "About Us",
description: "Learn more about our team and mission.",
}),
component: AboutPage,
})
function AboutPage() {
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold">About Us</h1>
</div>
)
}Dynamic Segments & Parameters
Dynamic parameters differ slightly between manifests and route files:
| Context | Syntax | Example | Access Parameter |
|---|---|---|---|
| Plugin Manifest | :param | path: "/blog/:slug" | params.slug in load / props |
| App Route File | $param | _main/blog.$slug.tsx | Route.useParams().slug |
VitNode compiles :slug into TanStack Router's $slug syntax automatically.
Plugin Route Lifecycle (definePluginRoute)
To load data, define metadata, or customize breadcrumbs in a plugin route module, export route:
import type { PluginRoutePageProps } from "@vitnode/core/routing"
import { definePluginRoute } from "@vitnode/core/routing"
interface Post {
title: string
body: string
}
export const route = definePluginRoute({
load: async ({ params }) => {
return await fetchPostBySlug(params.slug)
},
head: ({ loaderData }) => ({
title: loaderData?.title,
}),
})
const PostPage = ({ loaderData }: PluginRoutePageProps<Post>) => (
<article className="container mx-auto p-4">
<h1 className="text-3xl font-bold">{loaderData.title}</h1>
<p>{loaderData.body}</p>
</article>
)
export default PostPageDeclare load above head
TypeScript infers loaderData in head and the page component from what load returns. Always declare load before head.