Dev server continuously watches your pages/ directory for new or updated pages.
You never wire routes by hand: as page components are created, updated or deleted, a matching route configuration is written into lib/ for the native router to consume.
Same routing, both sides
Frontend routing follows the exact same directory-based pattern as API routing. If you know how api/ routes work, you already know how pages/ routes work:
api/users/[id]/index.ts -> /api/users/:id (backend handler)
pages/users/[id]/index.tsx -> /users/:id (frontend component)The parallel structure is intentional - an API endpoint and its corresponding page are always one folder apart. The same parameter syntax applies to both:
| Syntax | Type | Example |
|---|---|---|
[id] | Required | pages/users/[id]/ -> /users/123 |
{id} | Optional | pages/users/{id}/ -> /users or /users/123 |
{...path} | Splat | pages/docs/{...path}/ -> /docs/any/depth |
Static routes always take priority over dynamic ones. Optional parameters followed by static segments can cause ambiguity - see parameter details for gotchas and solutions.
Layouts
Layout files wrap groups of pages with shared UI - navigation, sidebars, auth shells - at any level of the route hierarchy:
pages/
dashboard/
layout.tsx - wraps all /dashboard/* pages
settings/
layout.tsx - wraps all /dashboard/settings/* pages
index.tsx
index.tsxLayouts stack outward-in and cannot be escaped by child routes. More on Layouts ›
Routes
There is no central route tree for you to register or maintain - no routeTree.gen.ts to import, no route config object to keep in sync.
Route definitions are written into lib/<folder>/ and the framework's own router consumes them; you reach them only through createRoutes() in your entry file, which the seeded boilerplate already wires up.
React, SolidJS and Vue each get a plain, framework-native route definition - the same object you would have hand-written for that router.
Svelte and MDX have no third-party router, so KosmoJS supplies the matcher and emits its own RawRoute shape instead.
Because the output is native, everything your router documents keeps working: lazy loading, nested layouts, navigation guards, loader/preload, error elements. Details ›
Lazy Loading
All page components are lazy-loaded by default.
Route code is excluded from the initial JavaScript bundle and fetched on demand when a user navigates to that path.
This keeps initial payloads small, accelerates application startup, and ensures users download only the code for routes they actually visit.
Data Loading on Navigation
Every framework integrates data fetching into the route lifecycle through a page-level loader/preload export.
React - when a page exports a loader function, React Router executes it at strategic moments: initial page load, link hover, and navigation initiation. Data is available before the component renders, eliminating loading spinners for route-level data.
SolidJS - when a page exports a preload function, SolidJS Router calls it on link hover and navigation intent. The preload result is cached and reused by createAsync inside the component (wrap the fetch in query() so both share one cache key), so no duplicate requests are made.
Vue - a page exports a loader (from a plain <script> block), and the router runs it before the route renders via a navigation guard. The component reads the result with useLoaderData() - no manual guards or onMounted needed.
Svelte - a page exports a loader (from its module <script> block); the router runs it before render and the component reads it with useLoaderData().
MDX - a page exports a loader; it runs before render and the page reads the result with the useLoaderData() hook.
Loader results are serialized during SSR and reused on hydration, so a request made on the server is not repeated on the client.