5.6 KiB
| title | sort | section-id | keywords | description | language |
|---|---|---|---|---|---|
| Routing | 100 | core-concepts | routing, file-based routing, dynamic routes, catch-all, nested routes, navigation | How Velox's file-based routing works, including dynamic segments, catch-all routes, and nested layouts | en |
Routing
Velox uses a file-based routing system. The file tree under the routes/ directory maps one-to-one to the URL structure of your application. There is no router configuration file — the filesystem is the configuration.
Basic Routes
Any .velox or .tsx file placed inside routes/ becomes a page route:
| File | URL |
|---|---|
routes/index.velox |
/ |
routes/about.velox |
/about |
routes/blog/index.velox |
/blog |
routes/blog/my-post.velox |
/blog/my-post |
routes/shop/shoes/index.velox |
/shop/shoes |
Dynamic Segments
Wrap a path segment in square brackets to make it dynamic:
routes/
└── blog/
└── [slug].velox → /blog/:slug
Inside the route, access the parameter via the params object in the server block:
---
const { slug } = params;
const post = await db.posts.findBySlug(slug);
if (!post) {
throw new NotFound();
}
export const meta = { title: post.title };
---
<article>
<h1>{post.title}</h1>
<div innerHTML={post.htmlContent} />
</article>
Multiple Dynamic Segments
You can nest multiple dynamic segments:
routes/
└── [category]/
└── [slug].velox → /:category/:slug
Optional Dynamic Segments
Wrap a segment in double square brackets to make it optional:
routes/
└── blog/
└── [[page]].velox → /blog and /blog/:page
Inside the route, params.page will be undefined when the segment is not present.
Catch-All Routes
Use the spread syntax [...rest] to match any number of path segments:
routes/
└── docs/
└── [...path].velox → /docs/ and /docs/a/b/c/d
params.path is an array of path segments: ['a', 'b', 'c', 'd'].
Optional Catch-All
[[...rest]] matches zero or more segments (i.e., the base path also matches):
routes/
└── [[...slug]].velox → / and /anything/at/all
API Routes
Files with a +server.ts or +server.js suffix define API endpoints. They export HTTP method handlers named GET, POST, PUT, PATCH, DELETE, or OPTIONS:
// routes/api/users+server.ts
import { defineHandler } from 'velox/server';
import { db } from '$lib/db';
export const GET = defineHandler(async (req) => {
const users = await db.users.findMany();
return Response.json(users);
});
export const POST = defineHandler(async (req) => {
const body = await req.json();
const user = await db.users.create({ data: body });
return Response.json(user, { status: 201 });
});
Route Groups
Wrap a directory name in parentheses to create a route group. Groups do not affect the URL — they are purely an organisational tool:
routes/
└── (marketing)/
├── index.velox → /
├── about.velox → /about
└── pricing.velox → /pricing
└── _layout.velox ← layout applied only to marketing routes
This is useful when you want different layouts for different sections of your site without adding URL segments.
Special Files
_layout.velox
A _layout.velox file wraps all sibling routes and nested route directories. Layouts receive a <slot /> where child content is injected:
// routes/blog/_layout.velox
---
import Sidebar from '../../components/Sidebar.tsx';
---
<div class="blog-layout">
<Sidebar />
<main>
<slot />
</main>
</div>
Layouts nest automatically — a deeply nested _layout.velox wraps its subtree, and the tree up to the root layout wraps the whole thing.
_error.velox
Renders when an unhandled error occurs or when a NotFound is thrown. Receives error and status as props.
_loading.velox
Shown as an immediate placeholder during route transitions (client-side navigation) while the next route is loading.
Programmatic Navigation
Use the useRouter hook for client-side navigation in interactive components:
import { useRouter } from 'velox/client';
export default function LoginButton() {
const router = useRouter();
async function handleLogin() {
await performLogin();
router.navigate('/dashboard');
}
return <button onClick={handleLogin}>Log in</button>;
}
The <Link> Component
For declarative navigation, use the <Link> component. It renders an <a> tag with client-side navigation and prefetching:
import { Link } from 'velox/client';
<Link href="/about">About Us</Link>
<Link href="/blog/my-post" prefetch="hover">My Post</Link>
The prefetch prop accepts "hover" (prefetch on hover), "viewport" (prefetch when link enters viewport), or false (no prefetch).
Redirects
Return a redirect from a server block or API handler:
import { redirect } from 'velox/server';
// In a server block
if (!user) {
throw redirect('/login', 302);
}
// In an API route
export const GET = defineHandler(async () => {
return redirect('/new-location', 301);
});
Route Metadata
Export meta from a route's server block to set page metadata:
export const meta = {
title: 'Page Title',
description: 'Page description for SEO',
openGraph: {
image: 'https://example.com/og.png',
},
robots: 'index, follow',
};
For dynamic metadata, use a function:
export const meta = () => ({
title: post.title,
description: post.excerpt,
});