Add velox-docs: config, nav, theme, search.json, and pages

This commit is contained in:
Kristian Benestad 2026-05-20 18:18:05 +07:00
parent 7463f9d9ca
commit 25884d2a09
29 changed files with 6728 additions and 0 deletions

8
velox-docs/config.yml Normal file
View file

@ -0,0 +1,8 @@
# mdcms v0.3 | DO NOT REMOVE THIS COMMENT
sitename: Velox Framework
sitedescription: The high-performance TypeScript web framework
navigation: sidebar
nav-position: left
search: true
footer: "© 2026 Velox Contributors. Licensed under MIT."
theme: theme.yml

227
velox-docs/nav.yml Normal file
View file

@ -0,0 +1,227 @@
# nav.yml — generated by mdcms.py
sections:
- code: getting-started
defaultname: Getting Started
sort: 100
pagesvisibility: visible
- code: core-concepts
defaultname: Core Concepts
sort: 200
pagesvisibility: visible
- code: api-reference
defaultname: API Reference
sort: 300
pagesvisibility: visible
- code: guides
defaultname: Guides
sort: 400
pagesvisibility: visible
- code: deployment
defaultname: Deployment
sort: 500
pagesvisibility: visible
pages:
- file: pages/index.md
title: Introduction
section-id: getting-started
sort: 100
variants: [en]
titles:
en: Introduction
- file: pages/installation.md
title: Installation
section-id: getting-started
sort: 110
variants: [en]
titles:
en: Installation
- file: pages/quick-start.md
title: Quick Start
section-id: getting-started
sort: 120
variants: [en]
titles:
en: Quick Start
- file: pages/project-structure.md
title: Project Structure
section-id: getting-started
sort: 130
variants: [en]
titles:
en: Project Structure
- file: pages/configuration.md
title: Configuration
section-id: getting-started
sort: 140
variants: [en]
titles:
en: Configuration
- file: pages/routing.md
title: Routing
section-id: core-concepts
sort: 100
variants: [en]
titles:
en: Routing
- file: pages/components.md
title: Components
section-id: core-concepts
sort: 110
variants: [en]
titles:
en: Components
- file: pages/state-management.md
title: State Management
section-id: core-concepts
sort: 120
variants: [en]
titles:
en: State Management
- file: pages/data-fetching.md
title: Data Fetching
section-id: core-concepts
sort: 130
variants: [en]
titles:
en: Data Fetching
- file: pages/middleware.md
title: Middleware
section-id: core-concepts
sort: 140
variants: [en]
titles:
en: Middleware
- file: pages/layouts.md
title: Layouts
section-id: core-concepts
sort: 150
variants: [en]
titles:
en: Layouts
- file: pages/api-router.md
title: Router API
section-id: api-reference
sort: 100
variants: [en]
titles:
en: Router API
- file: pages/api-components.md
title: Component API
section-id: api-reference
sort: 110
variants: [en]
titles:
en: Component API
- file: pages/api-server.md
title: Server API
section-id: api-reference
sort: 120
variants: [en]
titles:
en: Server API
- file: pages/api-config.md
title: Config API
section-id: api-reference
sort: 130
variants: [en]
titles:
en: Config API
- file: pages/api-hooks.md
title: Hooks API
section-id: api-reference
sort: 140
variants: [en]
titles:
en: Hooks API
- file: pages/guide-auth.md
title: Authentication
section-id: guides
sort: 100
variants: [en]
titles:
en: Authentication
- file: pages/guide-database.md
title: Database Integration
section-id: guides
sort: 110
variants: [en]
titles:
en: Database Integration
- file: pages/guide-testing.md
title: Testing
section-id: guides
sort: 120
variants: [en]
titles:
en: Testing
- file: pages/guide-i18n.md
title: Internationalisation
section-id: guides
sort: 130
variants: [en]
titles:
en: Internationalisation
- file: pages/guide-performance.md
title: Performance
section-id: guides
sort: 140
variants: [en]
titles:
en: Performance
- file: pages/deploy-vercel.md
title: Deploy to Vercel
section-id: deployment
sort: 100
variants: [en]
titles:
en: Deploy to Vercel
- file: pages/deploy-cloudflare.md
title: Deploy to Cloudflare
section-id: deployment
sort: 110
variants: [en]
titles:
en: Deploy to Cloudflare
- file: pages/deploy-docker.md
title: Docker
section-id: deployment
sort: 120
variants: [en]
titles:
en: Docker
- file: pages/deploy-self-hosted.md
title: Self-Hosted
section-id: deployment
sort: 130
variants: [en]
titles:
en: Self-Hosted

View file

@ -0,0 +1,270 @@
---
title: Component API
sort: 110
section-id: api-reference
keywords: defineComponent, ref, computed, watch, component API, reactive
description: Complete reference for the Velox component API — defineComponent, ref, computed, and watch
language: en
---
# Component API
This page documents the core component primitives exported from `velox/client`.
## `signal(initialValue)`
Creates a reactive signal — a value container that triggers DOM updates when changed.
```typescript
import { signal } from 'velox/client';
const count = signal(0);
count.value; // read: 0
count.value = 5; // write: triggers reactive updates
count.peek(); // read without tracking (no reactive subscription)
```
### Type Signature
```typescript
function signal<T>(initialValue: T): Signal<T>;
interface Signal<T> {
value: T;
peek(): T;
subscribe(listener: (value: T) => void): () => void;
}
```
## `computed(fn)`
Creates a derived reactive value. Re-evaluates lazily when accessed and a dependency has changed.
```typescript
import { signal, computed } from 'velox/client';
const price = signal(100);
const taxRate = signal(0.2);
const total = computed(() => price.value * (1 + taxRate.value));
total.value; // 120
price.value = 200;
total.value; // 240
```
Computed signals are read-only — attempting to set `.value` throws.
## `effect(fn)`
Registers a reactive side effect. Runs immediately, then again whenever its signal dependencies change.
```typescript
import { signal, effect } from 'velox/client';
const query = signal('');
const dispose = effect(() => {
console.log('query =', query.value);
// runs now, then on every change to query.value
});
// Clean up manually (effects inside components clean up on unmount):
dispose();
```
Return a cleanup function from the effect to run before the next execution or on disposal:
```typescript
effect(() => {
const controller = new AbortController();
fetchData(query.value, controller.signal);
return () => controller.abort();
});
```
## `batch(fn)`
Groups multiple signal writes into a single reactive update pass:
```typescript
import { batch } from 'velox/client';
batch(() => {
a.value = 1;
b.value = 2;
c.value = 3;
// Only one round of re-renders happens
});
```
## `untrack(fn)`
Execute a function without tracking its signal reads as dependencies:
```typescript
import { signal, effect, untrack } from 'velox/client';
const a = signal(1);
const b = signal(2);
effect(() => {
// Only subscribes to `a`, not `b`
const result = a.value + untrack(() => b.value);
console.log(result);
});
```
## Lifecycle Hooks
Lifecycle hooks must be called synchronously during component initialisation (similar to React rules of hooks, but without the runtime check overhead).
### `onMount(fn)`
Called after the component's DOM is inserted into the document:
```typescript
import { onMount } from 'velox/client';
onMount(() => {
// safe to access DOM, start timers, etc.
const input = document.querySelector('#my-input') as HTMLInputElement;
input.focus();
});
```
### `onCleanup(fn)`
Called before the component unmounts, or before an effect runs again. Use to cancel subscriptions, abort requests, and clear timers:
```typescript
import { onMount, onCleanup } from 'velox/client';
onMount(() => {
const timer = setInterval(tick, 1000);
onCleanup(() => clearInterval(timer));
});
```
### `onDestroy(fn)`
Like `onCleanup`, but only runs when the component is permanently destroyed (not before re-renders):
```typescript
import { onDestroy } from 'velox/client';
onDestroy(() => {
analytics.trackLeave(route);
});
```
## `createContext` / `useContext`
Create a context for dependency injection without prop drilling:
```typescript
import { createContext, useContext } from 'velox/client';
// Create context with a default value
const ThemeContext = createContext<'light' | 'dark'>('light');
// Provide a value to a subtree
<ThemeContext.Provider value="dark">
<App />
</ThemeContext.Provider>
// Consume in any descendant
function Button() {
const theme = useContext(ThemeContext);
return <button class={`btn--${theme}`}>Click</button>;
}
```
## `ref()`
Create a DOM element reference:
```typescript
import { ref, onMount } from 'velox/client';
export default function TextInput() {
const inputRef = ref<HTMLInputElement>();
onMount(() => {
inputRef.current?.focus();
});
return <input ref={inputRef} type="text" />;
}
```
## `defineComponent(options)`
The explicit component definition API — useful when you need named components for debugging or when defining components programmatically:
```typescript
import { defineComponent, signal } from 'velox/client';
const Counter = defineComponent({
name: 'Counter',
props: {
initialValue: { type: Number, default: 0 },
step: { type: Number, default: 1 },
},
setup(props) {
const count = signal(props.initialValue);
const increment = () => { count.value += props.step; };
const decrement = () => { count.value -= props.step; };
return { count, increment, decrement };
},
render({ count, increment, decrement }) {
return (
<div class="counter">
<button onClick={decrement}></button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
},
});
export default Counter;
```
## `lazy(loader)`
Lazily load a component — its code is only downloaded when the component is first rendered:
```typescript
import { lazy } from 'velox/client';
const HeavyChart = lazy(() => import('./HeavyChart.tsx'));
// Use in JSX — shows nothing while loading by default
<HeavyChart data={data} />
// With a Suspense boundary for a loading state
import { Suspense } from 'velox';
<Suspense fallback={<Skeleton />}>
<HeavyChart data={data} />
</Suspense>
```
## `memo(component)`
Wrap a component in `memo` to skip re-renders when props have not changed (shallow equality):
```typescript
import { memo } from 'velox/client';
const ExpensiveList = memo(function ExpensiveList({ items }: { items: string[] }) {
return <ul>{items.map(i => <li>{i}</li>)}</ul>;
});
```
Accepts a custom equality function as the second argument:
```typescript
const MyComponent = memo(Component, (prev, next) => prev.id === next.id);
```

View file

@ -0,0 +1,276 @@
---
title: Config API
sort: 130
section-id: api-reference
keywords: config API, velox.config.ts, defineConfig, types, TypeScript reference
description: Full TypeScript type reference for velox.config.ts and all configuration options
language: en
---
# Config API
This page provides the complete TypeScript type reference for `velox.config.ts`. All types are exported from the `velox` package.
## `defineConfig(config)`
The primary export. Accepts a `VeloxConfig` object and returns it with full type checking:
```typescript
import { defineConfig } from 'velox';
export default defineConfig({
// VeloxConfig object
});
```
You can also pass a function for dynamic configuration:
```typescript
export default defineConfig(async (env) => {
const secrets = await loadSecrets();
return {
app: {
name: 'My App',
baseUrl: secrets.BASE_URL,
},
};
});
```
## `VeloxConfig`
```typescript
interface VeloxConfig {
app?: AppConfig;
server?: ServerConfig;
build?: BuildConfig;
routes?: RoutesConfig;
assets?: AssetsConfig;
css?: CSSConfig;
i18n?: I18nConfig;
middleware?: MiddlewareConfig[];
plugins?: VeloxPlugin[];
experimental?: ExperimentalConfig;
errorHandler?: ErrorHandler;
}
```
## `AppConfig`
```typescript
interface AppConfig {
/** Application display name. Used in page titles and error pages. */
name: string;
/** Canonical base URL. Required in production. Example: 'https://example.com' */
baseUrl: string;
/** Default locale for i18n. Default: 'en' */
defaultLocale?: string;
/** Whether to append trailing slashes to all routes. Default: false */
trailingSlash?: boolean;
/** Custom 404 page route. Default: '_error' */
notFoundPage?: string;
}
```
## `ServerConfig`
```typescript
interface ServerConfig {
/** TCP port. Default: 3700 */
port?: number;
/** Bind hostname. Default: 'localhost' */
host?: string;
/** HTTPS configuration for the development server */
https?: {
cert: string; // path to PEM certificate
key: string; // path to PEM private key
};
/** CORS policy */
cors?: {
origin: string | string[] | ((origin: string) => boolean);
credentials?: boolean;
methods?: string[];
allowedHeaders?: string[];
exposedHeaders?: string[];
maxAge?: number;
};
/** Compression. Default: true in production */
compress?: boolean;
/** Trust proxy headers (X-Forwarded-For, etc.). Default: false */
trustProxy?: boolean | number;
}
```
## `BuildConfig`
```typescript
interface BuildConfig {
/**
* Deployment target.
* - 'node': Outputs a Node.js server (default)
* - 'edge': Outputs a Web-API-compatible edge bundle
* - 'static': Full static export — no server required
*/
target: 'node' | 'edge' | 'static';
/** Output directory. Default: '.velox/output' */
outDir?: string;
/** Source map generation. Default: false in production */
sourcemap?: boolean | 'external' | 'inline';
/** Minify output. Default: true in production */
minify?: boolean;
/** Enable code splitting. Default: true */
splitting?: boolean;
/** Emit a bundle analysis HTML report */
analyze?: boolean;
/**
* Routes to explicitly pre-render as static HTML.
* Supports globs. e.g. ['/blog/*', '/about']
*/
prerender?: string[];
/** External dependencies (not bundled). */
external?: string[];
/** Environment variables to inline into the client bundle */
define?: Record<string, string>;
}
```
## `RoutesConfig`
```typescript
interface RoutesConfig {
/** Routes directory relative to project root. Default: 'routes' */
dir?: string;
/** File extensions treated as routes. Default: ['.velox', '.tsx'] */
extensions?: string[];
/** Glob patterns to exclude from routing */
exclude?: string[];
/** Prefix all generated routes with this base path */
base?: string;
}
```
## `AssetsConfig`
```typescript
interface AssetsConfig {
/** Static assets directory. Default: 'public' */
publicDir?: string;
imageOptimisation?: {
enabled: boolean;
/** Output formats to generate. Default: ['webp'] */
formats?: ('webp' | 'avif' | 'jpeg' | 'png')[];
/** JPEG/WebP/AVIF quality 1100. Default: 80 */
quality?: number;
/** Maximum width in pixels before downscaling */
maxWidth?: number;
};
fonts?: {
/** Emit <link rel="preload"> for fonts. Default: true */
preload?: boolean;
/** Unicode range subsets. Example: ['latin', 'latin-ext'] */
subsets?: string[];
};
}
```
## `I18nConfig`
```typescript
interface I18nConfig {
/** Supported locale codes. Example: ['en', 'fr', 'de'] */
locales: string[];
/** Default locale. Default: 'en' */
defaultLocale: string;
/** Strategy for locale in URL. Default: 'prefix-except-default' */
routing?: 'prefix' | 'prefix-except-default' | 'domain';
/** Domain mapping for 'domain' routing strategy */
domains?: Record<string, string>;
/** Path to translation files. Default: 'messages' */
messagesDir?: string;
}
```
## `MiddlewareConfig`
```typescript
interface MiddlewareConfig {
/** Route path glob to match. Use '*' for global middleware */
path: string;
/** Path to the middleware module (relative to project root) */
handler: string;
/** Execution order (lower runs first). Default: 0 */
order?: number;
}
```
## `ExperimentalConfig`
```typescript
interface ExperimentalConfig {
/** Enable CSS View Transitions for route changes */
viewTransitions?: boolean;
/** Enable fine-grained island hydration */
partialHydration?: boolean;
/** Enable compatibility shim for React components */
reactCompat?: boolean;
/** Enable server actions (form + RPC) */
serverActions?: boolean;
}
```
## Plugin API
```typescript
interface VeloxPlugin {
name: string;
setup?(build: VeloxBuild): void | Promise<void>;
transform?(code: string, id: string): string | { code: string; map?: string } | null;
resolveId?(id: string, importer?: string): string | null;
load?(id: string): string | null;
}
```
Create a plugin:
```typescript
function myPlugin(): VeloxPlugin {
return {
name: 'my-plugin',
transform(code, id) {
if (!id.endsWith('.myext')) return null;
return { code: transformMyExtension(code) };
},
};
}
```

View file

@ -0,0 +1,222 @@
---
title: Hooks API
sort: 140
section-id: api-reference
keywords: hooks, useRequest, useSession, useCookies, useEnv, server hooks
description: Reference for Velox server-side hooks — useRequest, useSession, useCookies, and useEnv
language: en
---
# Hooks API
Velox provides server-side hooks for accessing request context, sessions, cookies, and environment variables within route server blocks and middleware. These hooks are only available in server-side contexts.
## `useRequest()`
Returns the current `VeloxRequest` object:
```typescript
import { useRequest } from 'velox/server';
const request = useRequest();
const method = request.method;
const pathname = new URL(request.url).pathname;
const userAgent = request.headers.get('User-Agent');
```
This is equivalent to the `request` variable that is automatically available in server blocks, but `useRequest()` is useful in helper functions that are called from a server block without threading `request` through manually.
### Full Request Object Reference
```typescript
interface VeloxRequest extends Request {
params: Record<string, string>; // route dynamic params
query: URLSearchParams; // parsed query string
cookies: RequestCookies; // parsed cookies
context: Map<string, unknown>; // middleware-set values
ip: string | null; // client IP address
geo: GeoInfo | null; // geographic info (if available)
}
```
## `useSession()`
Reads and writes the server-managed session. Sessions are stored server-side (in memory, Redis, or a database depending on your `session.store` configuration) and identified by a signed cookie.
```typescript
import { useSession } from 'velox/server';
const session = await useSession<{ userId: string; role: string }>();
// Read
const userId = session.data.userId;
const role = session.data.role;
// Write — persists changes to the session store
await session.set('userId', '123');
await session.set('role', 'admin');
// Update multiple at once
await session.update({ userId: '123', role: 'admin' });
// Destroy the session (logout)
await session.destroy();
// Regenerate session ID (after privilege change — prevents fixation)
await session.regenerate();
```
### Session Configuration
Configure the session store in `velox.config.ts`:
```typescript
import { defineConfig } from 'velox';
import { RedisSessionStore } from '@velox/session-redis';
export default defineConfig({
session: {
secret: process.env.SESSION_SECRET!,
cookieName: 'velox.session',
maxAge: 60 * 60 * 24 * 7, // 7 days
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
store: new RedisSessionStore({
url: process.env.REDIS_URL!,
}),
},
});
```
## `useCookies()`
Read and write cookies. Returns a `CookieJar` object:
```typescript
import { useCookies } from 'velox/server';
const cookies = useCookies();
// Read
const theme = cookies.get('theme')?.value ?? 'light';
const hasConsented = cookies.get('consent')?.value === 'true';
// Write (adds Set-Cookie header to the response)
cookies.set('theme', 'dark', {
path: '/',
maxAge: 365 * 24 * 60 * 60, // 1 year
sameSite: 'lax',
});
// Delete
cookies.delete('old-cookie', { path: '/' });
```
### Cookie Options
| Option | Type | Description |
|--------|------|-------------|
| `domain` | `string` | Cookie domain |
| `path` | `string` | Cookie path. Default: `/` |
| `maxAge` | `number` | Expiry in seconds |
| `expires` | `Date` | Expiry as a date |
| `httpOnly` | `boolean` | Prevent JavaScript access |
| `secure` | `boolean` | HTTPS only |
| `sameSite` | `'strict' \| 'lax' \| 'none'` | SameSite policy |
## `useEnv(key, defaultValue?)`
Type-safe environment variable access:
```typescript
import { useEnv } from 'velox/server';
const dbUrl = useEnv('DATABASE_URL'); // throws if not set
const port = useEnv('PORT', '3700'); // returns default if not set
const debug = useEnv.boolean('DEBUG', false); // parse as boolean
const timeout = useEnv.number('TIMEOUT', 30); // parse as number
```
### `useEnv` Methods
| Method | Description |
|--------|-------------|
| `useEnv(key)` | Return string value, throw if missing |
| `useEnv(key, default)` | Return string value or default |
| `useEnv.boolean(key, default?)` | Parse as boolean (`'true'` / `'1'` = `true`) |
| `useEnv.number(key, default?)` | Parse as float |
| `useEnv.json(key, default?)` | Parse as JSON |
| `useEnv.url(key, default?)` | Validate and return as URL string |
## `useHeaders()`
Access and modify response headers from within a server block or middleware:
```typescript
import { useHeaders } from 'velox/server';
const headers = useHeaders();
// Read request headers
const accept = headers.request.get('Accept');
// Set response headers
headers.response.set('Cache-Control', 'public, max-age=3600');
headers.response.set('X-Custom-Header', 'value');
```
## `useLocale()`
Returns the current request locale (resolved by the i18n middleware):
```typescript
import { useLocale } from 'velox/server';
const { locale, locales, defaultLocale } = useLocale();
// locale: 'fr' (current request locale)
// locales: ['en', 'fr', 'de'] (all supported locales)
// defaultLocale: 'en'
```
## `useAuth()`
A convenience hook that reads the current user from the session. Returns `null` if not authenticated:
```typescript
import { useAuth } from '$lib/auth'; // project-defined hook
const user = await useAuth();
if (!user) {
throw redirect('/login');
}
// user: { id: string, email: string, role: string }
```
The `useAuth` hook is not provided by Velox itself — you define it in your project using `useSession` and your own user model. The [Authentication guide](guide-auth.md) shows a complete implementation.
## `useCache()`
Interact with Velox's built-in server-side cache:
```typescript
import { useCache } from 'velox/server';
const cache = useCache();
// Get from cache
const cached = await cache.get<User[]>('users:all');
if (cached) return cached;
// Set with TTL (seconds)
const users = await db.users.findMany();
await cache.set('users:all', users, { ttl: 300 });
// Invalidate
await cache.delete('users:all');
await cache.deletePattern('users:*');
```

View file

@ -0,0 +1,227 @@
---
title: Router API
sort: 100
section-id: api-reference
keywords: router, useRouter, navigate, Link, createRouter, programmatic navigation
description: Complete API reference for the Velox router — createRouter, useRouter, navigate, and Link
language: en
---
# Router API
The Velox router provides both declarative and programmatic navigation. This page documents the full public API surface of `@velox/router`.
## `useRouter()`
The `useRouter` hook returns the current router instance. It is available inside any client-side component:
```typescript
import { useRouter } from 'velox/client';
const router = useRouter();
```
### Router Instance Properties
| Property | Type | Description |
|----------|------|-------------|
| `pathname` | `string` | Current URL pathname, e.g. `/blog/my-post` |
| `search` | `string` | Current query string including `?`, e.g. `?page=2` |
| `hash` | `string` | Current hash fragment including `#` |
| `params` | `Record<string, string>` | Dynamic route parameters |
| `query` | `URLSearchParams` | Parsed query parameters |
| `state` | `unknown` | History state object (if provided to `navigate`) |
### Router Instance Methods
#### `navigate(href, options?)`
Performs client-side navigation to the given href:
```typescript
router.navigate('/dashboard');
// With options:
router.navigate('/profile/edit', {
replace: true, // replace current history entry
state: { from: '/dashboard' }, // pass arbitrary state
scroll: false, // don't scroll to top after navigation
});
```
#### `back()` / `forward()`
Navigate through the browser history stack:
```typescript
router.back();
router.forward();
```
#### `prefetch(href)`
Manually prefetch a route (downloads the JS bundle and optionally data):
```typescript
router.prefetch('/heavy-page');
```
#### `refresh()`
Re-run the current route's server block and update the page without a full navigation:
```typescript
router.refresh();
```
## `<Link>` Component
The `<Link>` component renders an accessible `<a>` element with client-side navigation and built-in prefetching:
```tsx
import { Link } from 'velox/client';
<Link href="/about">About</Link>
```
### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `href` | `string` | required | Destination URL |
| `prefetch` | `'hover' \| 'viewport' \| false` | `'hover'` | Prefetch strategy |
| `replace` | `boolean` | `false` | Replace history entry instead of pushing |
| `scroll` | `boolean` | `true` | Scroll to top after navigation |
| `state` | `unknown` | `undefined` | History state object |
| `activeClass` | `string` | `'active'` | CSS class applied when href matches current pathname |
| `exactActiveClass` | `string` | `'exact-active'` | CSS class applied only on exact pathname match |
```tsx
<Link
href="/blog"
prefetch="viewport"
activeClass="nav-link--active"
exactActiveClass="nav-link--exact"
>
Blog
</Link>
```
## `createRouter()`
For testing or server-side use, create a router instance manually:
```typescript
import { createRouter } from 'velox/router';
const router = createRouter({
base: '/app', // base path prefix
history: 'hash', // 'browser' (default) | 'hash' | 'memory'
scrollRestoration: 'auto', // 'auto' | 'manual'
});
```
## `useParams()`
Access current route parameters in any component:
```typescript
import { useParams } from 'velox/client';
const { slug, category } = useParams<{ slug: string; category: string }>();
```
## `useSearchParams()`
Read and update the URL search parameters reactively:
```typescript
import { useSearchParams } from 'velox/client';
const [searchParams, setSearchParams] = useSearchParams();
// Read
const page = searchParams.get('page') ?? '1';
// Update (triggers navigation without full page reload)
setSearchParams({ page: String(currentPage + 1) });
// Merge with existing params
setSearchParams(prev => {
prev.set('sort', 'date');
return prev;
});
```
## `usePathname()`
Reactively access the current pathname:
```typescript
import { usePathname } from 'velox/client';
const pathname = usePathname();
// pathname is a Signal<string>
```
## `useNavigate()`
A lightweight hook that returns only the `navigate` function, without the full router object:
```typescript
import { useNavigate } from 'velox/client';
const navigate = useNavigate();
navigate('/login');
```
## Navigation Events
Subscribe to router lifecycle events:
```typescript
import { useRouter } from 'velox/client';
const router = useRouter();
const unsubscribe = router.on('beforeNavigate', ({ to, from, cancel }) => {
if (hasUnsavedChanges && !confirm('Leave without saving?')) {
cancel();
}
});
// Clean up
onCleanup(unsubscribe);
```
Available events: `beforeNavigate`, `afterNavigate`, `navigationError`.
## `redirect()` (Server)
Used inside server blocks and API route handlers:
```typescript
import { redirect } from 'velox/server';
// Temporary redirect (302)
throw redirect('/login');
// Permanent redirect (301)
throw redirect('/new-url', 301);
// With custom headers
throw redirect('/dashboard', 302, {
'Set-Cookie': 'session=...; Path=/',
});
```
## `notFound()` (Server)
Trigger the nearest `_error.velox` with a 404 status:
```typescript
import { notFound } from 'velox/server';
const post = await db.posts.find(id);
if (!post) throw notFound();
```

View file

@ -0,0 +1,243 @@
---
title: Server API
sort: 120
section-id: api-reference
keywords: server API, createServer, defineHandler, Response helpers, server-side
description: Reference for Velox server-side APIs — createServer, defineHandler, and Response helpers
language: en
---
# Server API
The Velox server API provides utilities for API route handlers, middleware, and server-side logic. All exports documented here come from `velox/server`.
## `defineHandler(fn)`
Wraps an API route handler with type inference and error handling:
```typescript
import { defineHandler } from 'velox/server';
export const GET = defineHandler(async (request) => {
return Response.json({ status: 'ok' });
});
```
The handler receives a `VeloxRequest` object (an extension of the standard `Request`) and must return a `Response` or `Promise<Response>`.
### `VeloxRequest`
The enhanced request object passed to handlers:
| Property | Type | Description |
|----------|------|-------------|
| `url` | `string` | Full request URL |
| `method` | `string` | HTTP method (uppercase) |
| `headers` | `Headers` | Request headers |
| `params` | `Record<string, string>` | URL route parameters |
| `query` | `URLSearchParams` | Parsed query string |
| `cookies` | `RequestCookies` | Parsed cookies |
| `context` | `Map<string, unknown>` | Values set by middleware |
| `json()` | `Promise<unknown>` | Parse body as JSON |
| `text()` | `Promise<string>` | Parse body as text |
| `formData()` | `Promise<FormData>` | Parse body as form data |
| `arrayBuffer()` | `Promise<ArrayBuffer>` | Parse body as binary |
## Response Helpers
Velox exports a set of `Response` factory helpers for common responses:
### `json(data, init?)`
Return a JSON response with appropriate `Content-Type` header:
```typescript
import { json } from 'velox/server';
return json({ users }, { status: 200 });
return json({ error: 'Not found' }, { status: 404 });
```
### `text(body, init?)`
Return a plain text response:
```typescript
import { text } from 'velox/server';
return text('Hello, World!');
return text('Not Found', { status: 404 });
```
### `html(body, init?)`
Return an HTML response:
```typescript
import { html } from 'velox/server';
return html('<h1>Hello</h1>');
```
### `redirect(url, status?, headers?)`
Return a redirect response:
```typescript
import { redirect } from 'velox/server';
return redirect('/login', 302);
return redirect('/new-location', 301);
```
### `notFound(message?)`
Return a 404 response:
```typescript
import { notFound } from 'velox/server';
return notFound('User not found');
// Returns: Response with status 404 and JSON body
```
### `unauthorized(message?)`
Return a 401 response:
```typescript
import { unauthorized } from 'velox/server';
return unauthorized('Please log in');
```
### `forbidden(message?)`
Return a 403 response:
```typescript
import { forbidden } from 'velox/server';
return forbidden('You do not have access to this resource');
```
### `badRequest(message?, details?)`
Return a 400 response with optional validation details:
```typescript
import { badRequest } from 'velox/server';
return badRequest('Validation failed', { field: 'email', message: 'Invalid format' });
```
### `stream(generator, init?)`
Stream a response body using an async generator:
```typescript
import { stream } from 'velox/server';
export const GET = defineHandler(async () => {
return stream(async function* () {
for await (const chunk of getLargeDataset()) {
yield JSON.stringify(chunk) + '\n';
}
});
});
```
## `createServer(options)`
Bootstrap a standalone Velox server programmatically (useful for testing):
```typescript
import { createServer } from 'velox/server';
const server = await createServer({
root: '/path/to/project',
port: 3700,
mode: 'development',
});
await server.start();
console.log(`Server running on port ${server.port}`);
// Later:
await server.stop();
```
### `VeloxServer` Methods
| Method | Description |
|--------|-------------|
| `start()` | Start the HTTP server |
| `stop()` | Gracefully shut down the server |
| `getUrl()` | Returns the server base URL as a string |
| `inject(request)` | Inject a synthetic request for testing without a network |
## Cookies
### Reading Cookies
```typescript
const session = request.cookies.get('session');
const userId = request.cookies.get('userId')?.value;
```
### Setting Cookies
Use the `ResponseCookies` API on the response object, or the `setCookie` helper:
```typescript
import { setCookie, deleteCookie } from 'velox/server';
const response = json({ ok: true });
setCookie(response, 'session', token, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: 60 * 60 * 24 * 7, // 7 days
});
return response;
```
### Deleting Cookies
```typescript
const response = redirect('/login');
deleteCookie(response, 'session');
return response;
```
## `useEnv(key, defaultValue?)`
Type-safe environment variable access with optional validation:
```typescript
import { useEnv } from 'velox/server';
const dbUrl = useEnv('DATABASE_URL'); // throws if missing
const port = useEnv('PORT', '3700'); // returns defaultValue if missing
const apiKey = useEnv.required('SECRET_KEY'); // alias for useEnv(key)
```
## Error Handling
Velox catches thrown errors in route handlers and middleware. Define a custom error handler in `velox.config.ts`:
```typescript
import { defineConfig } from 'velox';
export default defineConfig({
errorHandler: async (error, request) => {
if (error.status === 404) {
return json({ error: 'Not Found' }, { status: 404 });
}
console.error(error);
return json({ error: 'Internal Server Error' }, { status: 500 });
},
});
```

View file

@ -0,0 +1,253 @@
---
title: Components
sort: 110
section-id: core-concepts
keywords: components, props, slots, lifecycle, tsx, velox components, islands
description: The Velox component model, including props, slots, lifecycle hooks, and the islands architecture
language: en
---
# Components
Velox components are TypeScript/TSX files that describe a piece of UI. They are the fundamental building blocks of a Velox application — reusable, composable, and by default rendered entirely on the server.
## Defining a Component
A basic Velox component is a TypeScript function that returns JSX:
```tsx
// components/Greeting.tsx
interface GreetingProps {
name: string;
formal?: boolean;
}
export default function Greeting({ name, formal = false }: GreetingProps) {
const salutation = formal ? 'Good day' : 'Hello';
return <p>{salutation}, {name}!</p>;
}
```
Use it in a route or another component:
```tsx
---
import Greeting from '../components/Greeting.tsx';
---
<main>
<Greeting name="Alice" />
<Greeting name="Bob" formal />
</main>
```
## Props
Props are typed using TypeScript interfaces or types. All props are validated at compile time — no runtime prop checking overhead.
```tsx
interface CardProps {
title: string;
description: string;
href?: string;
variant?: 'default' | 'featured' | 'compact';
children?: VeloxNode;
}
export default function Card({
title,
description,
href,
variant = 'default',
children,
}: CardProps) {
return (
<div class={`card card--${variant}`}>
<h3>{href ? <a href={href}>{title}</a> : title}</h3>
<p>{description}</p>
{children && <div class="card__body">{children}</div>}
</div>
);
}
```
### Default Props
Set default values directly in the destructuring parameter, as shown above. Velox does not use a separate `defaultProps` mechanism.
### Required vs Optional Props
By TypeScript convention, optional props are marked with `?`. Omitting a required prop is a compile-time error.
## Slots
The `children` prop is the default slot — content placed between the opening and closing tags of a component:
```tsx
<Card title="Welcome" description="Get started">
<p>This is the card body.</p>
</Card>
```
### Named Slots
For more complex component APIs, use named slot props:
```tsx
interface ModalProps {
title: VeloxNode;
footer?: VeloxNode;
children: VeloxNode;
}
export default function Modal({ title, footer, children }: ModalProps) {
return (
<div class="modal">
<div class="modal__header">{title}</div>
<div class="modal__body">{children}</div>
{footer && <div class="modal__footer">{footer}</div>}
</div>
);
}
```
Usage:
```tsx
<Modal
title={<h2>Confirm Delete</h2>}
footer={
<>
<button onClick={cancel}>Cancel</button>
<button onClick={confirm}>Delete</button>
</>
}
>
<p>Are you sure you want to delete this item?</p>
</Modal>
```
## Server vs Client Components
By default, all Velox components run on the server. They have no JavaScript bundle size on the client and cannot use browser APIs or client-side reactivity.
To make a component interactive on the client, use the `client:*` hydration directive when you include it in a route:
```tsx
---
import Counter from '../components/Counter.tsx';
import LazyChart from '../components/LazyChart.tsx';
---
<!-- Hydrate immediately when page loads -->
<Counter client:load />
<!-- Hydrate when browser is idle -->
<Counter client:idle />
<!-- Hydrate when the component enters the viewport -->
<LazyChart client:visible />
<!-- Hydrate only when a media query matches -->
<Sidebar client:media="(max-width: 768px)" />
```
### Writing Client Components
Client components can use signals, effects, and browser APIs:
```tsx
import { signal, effect, onMount, onCleanup } from 'velox/client';
export default function LiveClock() {
const time = signal(new Date().toLocaleTimeString());
onMount(() => {
const interval = setInterval(() => {
time.value = new Date().toLocaleTimeString();
}, 1000);
onCleanup(() => clearInterval(interval));
});
return <p>Current time: {time}</p>;
}
```
## Lifecycle Hooks
Client-side components can use the following lifecycle hooks:
| Hook | When it runs |
|------|-------------|
| `onMount(fn)` | After the component is first rendered and inserted into the DOM |
| `onUpdate(fn)` | After every re-render (reactive signal change) |
| `onCleanup(fn)` | Before the component unmounts or before the next `onUpdate` call |
| `onDestroy(fn)` | When the component is permanently unmounted |
```tsx
import { signal, onMount, onUpdate, onCleanup } from 'velox/client';
export default function DataComponent() {
const data = signal<any[]>([]);
const loading = signal(true);
onMount(async () => {
const response = await fetch('/api/data');
data.value = await response.json();
loading.value = false;
});
onCleanup(() => {
// abort pending requests, clear timers, etc.
});
return (
<div>
{loading.value ? <p>Loading...</p> : <ul>{data.value.map(d => <li>{d.name}</li>)}</ul>}
</div>
);
}
```
## Component Composition Patterns
### Higher-Order Components
```tsx
function withAuth<T extends {}>(Component: VeloxComponent<T>) {
return function AuthGated(props: T) {
const user = useUser();
if (!user) return <Redirect to="/login" />;
return <Component {...props} />;
};
}
```
### Render Props / Function-as-Children
```tsx
interface FetcherProps<T> {
url: string;
render: (data: T) => VeloxNode;
}
export function Fetcher<T>({ url, render }: FetcherProps<T>) {
// ... fetch logic
return render(data);
}
```
## Component Library
Velox ships an optional official component library `@velox/ui` with accessible, unstyled base components. Install it separately:
```bash
npm install @velox/ui
```
```tsx
import { Button, Input, Dialog } from '@velox/ui';
```
See the [Component API](api-components.md) reference for the full `defineComponent` API and advanced component patterns.

View file

@ -0,0 +1,249 @@
---
title: Configuration
sort: 140
section-id: getting-started
keywords: configuration, velox.config.ts, settings, options, defineConfig
description: Complete reference for velox.config.ts and all available configuration options
language: en
---
# Configuration
Velox is configured through a single `velox.config.ts` file at the root of your project. This file is evaluated at build time (and at dev-server startup) to determine how Velox should compile and serve your application.
## Creating the Config File
If you used `create-velox` to scaffold your project, a `velox.config.ts` is generated for you. To create one manually:
```typescript
import { defineConfig } from 'velox';
export default defineConfig({
// options go here
});
```
`defineConfig` is a helper that provides full TypeScript type inference over the configuration object — use it rather than exporting a plain object.
## Top-Level Options
### `app`
General application metadata.
```typescript
app: {
name: string; // used in HTML <title> and meta tags
baseUrl: string; // canonical base URL (e.g. https://example.com)
defaultLocale?: string; // default locale for i18n (default: 'en')
trailingSlash?: boolean; // append trailing slash to all routes (default: false)
}
```
Example:
```typescript
app: {
name: 'My Velox App',
baseUrl: process.env.PUBLIC_BASE_URL ?? 'http://localhost:3700',
defaultLocale: 'en',
trailingSlash: false,
},
```
### `server`
Development and production server settings.
```typescript
server: {
port?: number; // dev server port (default: 3700)
host?: string; // bind address (default: 'localhost')
https?: { // enable HTTPS for the dev server
cert: string; // path to certificate file
key: string; // path to key file
};
cors?: {
origin: string | string[] | '*';
credentials?: boolean;
methods?: string[];
};
}
```
### `build`
Build system options.
```typescript
build: {
target: 'node' | 'edge' | 'static'; // deployment target
outDir?: string; // output directory (default: '.velox/output')
sourcemap?: boolean | 'external'; // generate source maps
minify?: boolean; // minify output (default: true in production)
splitting?: boolean; // enable code splitting (default: true)
analyze?: boolean; // emit a bundle analysis report
prerender?: string[]; // explicit list of routes to pre-render
}
```
### `routes`
Fine-grained routing options.
```typescript
routes: {
dir?: string; // routes directory (default: 'routes')
extensions?: string[]; // file extensions treated as routes
// default: ['.velox', '.tsx', '.ts']
exclude?: string[]; // glob patterns to exclude
}
```
### `assets`
Asset handling and optimisation.
```typescript
assets: {
publicDir?: string; // static assets directory (default: 'public')
imageOptimisation?: {
enabled: boolean;
formats?: ('webp' | 'avif')[];
quality?: number; // 1100, default 80
};
fonts?: {
preload?: boolean;
subsets?: string[];
};
}
```
### `css`
Stylesheet handling.
```typescript
css: {
modules?: {
localsConvention?: 'camelCase' | 'camelCaseOnly' | 'dashes';
generateScopedName?: string;
};
preprocessors?: {
sass?: boolean; // enable Sass (requires @velox/sass)
less?: boolean;
stylus?: boolean;
};
postcss?: {
plugins?: any[];
};
}
```
### `plugins`
The plugin array lets you extend Velox with first-party and community plugins.
```typescript
import { defineConfig } from 'velox';
import { veloxMDX } from '@velox/mdx';
import { veloxSass } from '@velox/sass';
import { veloxPWA } from '@velox/pwa';
export default defineConfig({
plugins: [
veloxMDX(),
veloxSass(),
veloxPWA({
name: 'My App',
themeColor: '#3a7bd5',
}),
],
});
```
### `experimental`
Opt-in to experimental features that are not yet stable.
```typescript
experimental: {
viewTransitions?: boolean; // CSS View Transitions API support
partialHydration?: boolean; // fine-grained component hydration
reactCompat?: boolean; // React component compatibility shim
}
```
## Environment-Specific Configuration
You can provide environment-specific overrides:
```typescript
import { defineConfig } from 'velox';
export default defineConfig({
app: {
name: 'My App',
baseUrl: process.env.PUBLIC_BASE_URL!,
},
server: {
port: parseInt(process.env.PORT ?? '3700'),
},
build: {
target: process.env.VELOX_TARGET as 'node' | 'edge' ?? 'node',
sourcemap: process.env.NODE_ENV !== 'production',
},
});
```
## Per-Route Rendering Configuration
You can override the rendering mode for individual routes from within the route file:
```typescript
// routes/dashboard.velox server block
export const config = {
render: 'ssr', // 'ssr' | 'ssg' | 'isr' | 'csr'
revalidate: 60, // ISR: revalidate every 60 seconds
edge: true, // run on edge runtime
};
```
## Full Example
A production-ready `velox.config.ts` for a large application:
```typescript
import { defineConfig } from 'velox';
import { veloxMDX } from '@velox/mdx';
export default defineConfig({
app: {
name: 'Acme Corp',
baseUrl: process.env.PUBLIC_BASE_URL!,
defaultLocale: 'en',
},
server: {
port: 3700,
cors: {
origin: process.env.ALLOWED_ORIGINS?.split(',') ?? '*',
credentials: true,
},
},
build: {
target: 'edge',
sourcemap: 'external',
analyze: process.env.ANALYZE === '1',
},
assets: {
imageOptimisation: {
enabled: true,
formats: ['webp', 'avif'],
quality: 85,
},
},
plugins: [veloxMDX()],
});
```
For the complete type definitions, see the [Config API](api-config.md) reference.

View file

@ -0,0 +1,267 @@
---
title: Data Fetching
sort: 130
section-id: core-concepts
keywords: data fetching, SSR, SSG, ISR, server-side rendering, static generation, fetch, async
description: How to fetch data in Velox using SSR, SSG, ISR, and client-side fetching patterns
language: en
---
# Data Fetching
Velox provides multiple data-fetching patterns depending on when and how often your data changes. You can mix strategies freely across routes in the same project.
## Server-Side Rendering (SSR)
In SSR mode, data is fetched fresh on every request. Use `await` directly in the server block of a `.velox` route:
```tsx
---
// routes/blog/[slug].velox
import { db } from '$lib/db';
import { NotFound } from 'velox/server';
const { slug } = params;
const post = await db.posts.findBySlug(slug);
if (!post) throw new NotFound();
export const meta = {
title: post.title,
description: post.excerpt,
};
export const config = { render: 'ssr' };
---
<article>
<h1>{post.title}</h1>
<p class="byline">By {post.author} on {post.publishedAt}</p>
<div innerHTML={post.htmlContent} />
</article>
```
The `render: 'ssr'` export is optional — SSR is the default for routes that contain `await` expressions.
### Request Context in SSR
In SSR mode, the `request` object is available in the server block:
```typescript
const authHeader = request.headers.get('Authorization');
const userAgent = request.headers.get('User-Agent');
const cookieHeader = request.headers.get('Cookie');
```
## Static Site Generation (SSG)
SSG routes are rendered once at build time. They are ideal for content that rarely changes.
```tsx
---
export const config = { render: 'ssg' };
const features = await fetch('https://api.example.com/features').then(r => r.json());
---
<section>
<h1>Features</h1>
<ul>
{features.map(f => <li>{f.name}: {f.description}</li>)}
</ul>
</section>
```
### Dynamic SSG Routes
For SSG routes with dynamic segments, export a `paths` function to tell Velox which parameter values to pre-render:
```tsx
---
import { db } from '$lib/db';
export const config = { render: 'ssg' };
// Called at build time to enumerate all slugs
export async function paths() {
const slugs = await db.posts.findManySlugs();
return slugs.map(slug => ({ slug }));
}
const { slug } = params;
const post = await db.posts.findBySlug(slug);
export const meta = { title: post.title };
---
<article>
<h1>{post.title}</h1>
<div innerHTML={post.htmlContent} />
</article>
```
## Incremental Static Regeneration (ISR)
ISR generates pages statically but revalidates them in the background after a configurable interval:
```tsx
---
export const config = {
render: 'isr',
revalidate: 3600, // regenerate at most once per hour
};
const products = await db.products.findMany({ orderBy: 'created_at' });
---
<section>
{products.map(p => <ProductCard product={p} />)}
</section>
```
On a cache miss (first request, or after `revalidate` seconds), Velox serves the stale page immediately while regenerating the fresh version in the background. The next request gets the fresh version.
### Manual Revalidation
Trigger revalidation programmatically (e.g., from a webhook):
```typescript
// routes/api/revalidate+server.ts
import { revalidatePath, revalidateTag } from 'velox/server';
export const POST = defineHandler(async (req) => {
const { path, secret } = await req.json();
if (secret !== process.env.REVALIDATE_SECRET) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
await revalidatePath(path);
return Response.json({ revalidated: true });
});
```
Tag-based revalidation:
```typescript
// When fetching, attach cache tags:
const data = await fetch('/api/products', {
next: { tags: ['products'] }
});
// Later, invalidate all pages that used the 'products' tag:
await revalidateTag('products');
```
## Client-Side Fetching
For data that must be fresh on the client (user-specific dashboards, real-time data), use client-side fetching in an interactive component:
```tsx
import { signal, onMount } from 'velox/client';
export default function UserDashboard() {
const stats = signal<DashboardStats | null>(null);
const error = signal<string | null>(null);
const loading = signal(true);
onMount(async () => {
try {
const res = await fetch('/api/dashboard/stats', {
credentials: 'include',
});
if (!res.ok) throw new Error('Failed to fetch stats');
stats.value = await res.json();
} catch (e: any) {
error.value = e.message;
} finally {
loading.value = false;
}
});
return (
<div>
{loading.value && <Spinner />}
{error.value && <ErrorMessage message={error.value} />}
{stats.value && <StatsGrid stats={stats.value} />}
</div>
);
}
```
### Using `@velox/query`
For production client-side data fetching, `@velox/query` handles caching, deduplication, background refetch, and stale-while-revalidate:
```typescript
import { useQuery } from '@velox/query';
export default function ProductList() {
const { data, status, error, refetch } = useQuery({
key: ['products'],
fetcher: () => fetch('/api/products').then(r => r.json()),
staleTime: 30_000,
refetchOnWindowFocus: true,
});
if (status === 'loading') return <Spinner />;
if (status === 'error') return <p>Error: {error.message}</p>;
return (
<ul>
{data.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
);
}
```
## Parallel Data Fetching
Fetch multiple data sources in parallel using `Promise.all`:
```tsx
---
const [user, posts, categories] = await Promise.all([
db.users.findById(userId),
db.posts.findByUser(userId),
db.categories.findAll(),
]);
---
```
## Streaming
For pages with slow data, use streaming to send the page shell immediately and stream in the slow parts:
```tsx
---
import { Suspense } from 'velox';
const fastData = await db.pageConfig.find();
// Slow query wrapped in Suspense — the page shell renders immediately
const slowPostsPromise = db.posts.findMany({ include: { comments: true } });
---
<main>
<h1>{fastData.title}</h1>
<Suspense fallback={<Spinner />}>
<PostList postsPromise={slowPostsPromise} />
</Suspense>
</main>
```
The `<Suspense>` boundary renders its fallback immediately while the async data resolves, then streams the final HTML to the client.
## Fetch Utilities
Velox wraps the global `fetch` with several quality-of-life additions in server contexts:
```typescript
import { fetch } from 'velox/server';
// Automatic base URL resolution for relative paths
const data = await fetch('/api/internal-endpoint');
// Request deduplication — concurrent identical fetches share one in-flight request
const [a, b] = await Promise.all([fetch('/api/same'), fetch('/api/same')]);
// ^ only one HTTP request is made
```

View file

@ -0,0 +1,213 @@
---
title: Deploy to Cloudflare
sort: 110
section-id: deployment
keywords: Cloudflare, Pages, Workers, deployment, edge, CDN, Wrangler
description: Deploying a Velox application to Cloudflare Pages and Workers
language: en
---
# Deploy to Cloudflare
Velox runs natively on Cloudflare Workers. By combining Cloudflare Pages for static assets and Cloudflare Workers for server-side rendering, you get a globally distributed application with sub-millisecond cold starts.
## Architecture
Velox on Cloudflare uses:
- **Cloudflare Pages** — serves static assets from the CDN edge
- **Cloudflare Workers** — handles SSR requests at the edge
- **KV** — used for ISR page caching
- **D1** — SQLite-at-the-edge database (optional)
- **R2** — object storage for user uploads (optional)
## Setup
Install the Cloudflare adapter:
```bash
npm install @velox/cloudflare
npm install -D wrangler
```
Configure the adapter:
```typescript
// velox.config.ts
import { defineConfig } from 'velox';
import { cloudflare } from '@velox/cloudflare';
export default defineConfig({
adapter: cloudflare({
kvNamespace: 'VELOX_CACHE', // KV namespace for ISR cache
routes: {
exclude: ['/admin/*'], // serve these from Pages CDN only
},
}),
build: {
target: 'edge',
},
});
```
## Wrangler Configuration
Create `wrangler.toml`:
```toml
name = "my-velox-app"
compatibility_date = "2026-01-01"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = ".velox/output"
[[kv_namespaces]]
binding = "VELOX_CACHE"
id = "your-kv-namespace-id"
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "your-database-id"
[vars]
NODE_ENV = "production"
```
## Deployment Steps
### Option 1: Cloudflare Pages Dashboard
1. Go to [dash.cloudflare.com](https://dash.cloudflare.com) → **Pages** → **Create a project**
2. Connect your Git provider and select your repository
3. Set build settings:
- **Framework preset:** Velox
- **Build command:** `npm run build`
- **Build output directory:** `.velox/output`
4. Add environment variables
5. Click **Save and Deploy**
### Option 2: Wrangler CLI
```bash
# Log in
npx wrangler login
# Build
npm run build
# Deploy
npx wrangler pages deploy .velox/output --project-name my-velox-app
# Or deploy as a Worker
npx wrangler deploy
```
## Using Cloudflare D1 (SQLite at the Edge)
Cloudflare D1 is a serverless SQLite database that runs at the edge alongside your Workers.
### Create a Database
```bash
npx wrangler d1 create my-database
npx wrangler d1 execute my-database --file=./migrations/001_init.sql
```
### Query D1 in Velox
```typescript
// routes/api/posts+server.ts
import { defineHandler } from 'velox/server';
export const GET = defineHandler(async (req) => {
// env.DB is automatically injected by the Cloudflare adapter
const { DB } = process.env as any;
const { results } = await DB.prepare('SELECT * FROM posts WHERE published = 1').all();
return Response.json(results);
});
```
Or with Drizzle's D1 driver:
```typescript
import { drizzle } from 'drizzle-orm/d1';
import * as schema from '$lib/schema';
export function getDB(env: Env) {
return drizzle(env.DB, { schema });
}
```
## Using Cloudflare KV
For key-value storage (feature flags, cached responses):
```typescript
const value = await env.MY_KV.get('my-key');
await env.MY_KV.put('my-key', JSON.stringify(data), { expirationTtl: 3600 });
await env.MY_KV.delete('my-key');
```
## Using Cloudflare R2
For file uploads and object storage:
```typescript
// routes/api/upload+server.ts
import { defineHandler } from 'velox/server';
export const POST = defineHandler(async (req) => {
const formData = await req.formData();
const file = formData.get('file') as File;
const key = `uploads/${Date.now()}-${file.name}`;
await env.R2_BUCKET.put(key, file.stream(), {
httpMetadata: { contentType: file.type },
});
return Response.json({ url: `https://assets.example.com/${key}` });
});
```
## Custom Domains
1. Add your domain in Cloudflare's DNS settings (it should already be proxied through Cloudflare)
2. Go to Pages → Your project → **Custom domains**
3. Click **Set up a custom domain** and enter your domain
4. Cloudflare provisions SSL automatically
For Workers, add a route in `wrangler.toml`:
```toml
[[routes]]
pattern = "example.com/*"
zone_name = "example.com"
```
## Environment Variables
Set secrets securely:
```bash
npx wrangler secret put DATABASE_URL
npx wrangler secret put SESSION_SECRET
npx wrangler secret put JWT_SECRET
```
Non-secret variables go in `wrangler.toml` under `[vars]` or in the Pages dashboard.
## Performance Tips
- Use **Cache Rules** in the Cloudflare dashboard to cache SSR pages at the CDN layer
- Enable **Argo Smart Routing** for improved global routing
- Use **Tiered Cache** to reduce origin requests
- Set `Cache-Control: public, max-age=0, s-maxage=3600` on ISR pages to let Cloudflare cache them
## Limits
| Feature | Limit |
|---------|-------|
| Worker request timeout | 30s (CPU time) |
| Worker memory | 128 MB |
| KV value size | 25 MB |
| D1 database size | 2 GB |
| R2 object size | 5 GB |

View file

@ -0,0 +1,301 @@
---
title: Docker
sort: 120
section-id: deployment
keywords: Docker, Dockerfile, docker-compose, container, containerisation, deployment
description: Containerising a Velox application with Docker and docker-compose
language: en
---
# Docker
Containerising your Velox application with Docker makes it portable across any infrastructure — from a simple VPS to a Kubernetes cluster. This guide covers building optimised Docker images and running multi-service stacks with docker-compose.
## Dockerfile
A production-grade multi-stage Dockerfile:
```dockerfile
# Stage 1: Install dependencies
FROM node:22-alpine AS deps
WORKDIR /app
# Copy package manifests only — enables Docker layer caching
COPY package.json package-lock.json ./
RUN npm ci --frozen-lockfile
# Stage 2: Build the application
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build args — pass at build time
ARG PUBLIC_BASE_URL
ARG NODE_ENV=production
ENV PUBLIC_BASE_URL=$PUBLIC_BASE_URL
ENV NODE_ENV=$NODE_ENV
RUN npm run build
# Stage 3: Production runtime
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3700
# Create non-root user for security
RUN addgroup --system --gid 1001 veloxgroup && \
adduser --system --uid 1001 veloxuser
# Copy only production artefacts
COPY --from=builder --chown=veloxuser:veloxgroup /app/.velox/output ./
COPY --from=builder --chown=veloxuser:veloxgroup /app/package.json ./
# Install production dependencies only
RUN npm ci --omit=dev --frozen-lockfile
USER veloxuser
EXPOSE 3700
CMD ["node", "server.js"]
```
## Building and Running
```bash
# Build the image
docker build \
--build-arg PUBLIC_BASE_URL=https://example.com \
-t my-velox-app:latest .
# Run the container
docker run \
--env-file .env.production \
-p 3700:3700 \
--name velox-app \
my-velox-app:latest
# Run in background
docker run -d \
--env-file .env.production \
-p 3700:3700 \
--restart unless-stopped \
--name velox-app \
my-velox-app:latest
```
## `.dockerignore`
Exclude files from the build context to speed up builds:
```
node_modules
.velox
.git
.gitignore
*.md
.env
.env.*
!.env.example
tests
*.test.*
*.spec.*
coverage
```
## docker-compose
A complete stack with the app, PostgreSQL, and Redis:
```yaml
# docker-compose.yml
version: '3.9'
services:
app:
build:
context: .
args:
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-http://localhost:3700}
image: my-velox-app:latest
ports:
- "3700:3700"
environment:
NODE_ENV: production
DATABASE_URL: postgresql://velox:${DB_PASSWORD}@db:5432/velox
REDIS_URL: redis://redis:6379
SESSION_SECRET: ${SESSION_SECRET}
JWT_SECRET: ${JWT_SECRET}
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3700/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: velox
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: velox
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U velox"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "--no-auth-warning", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- nginx_cache:/var/cache/nginx
depends_on:
- app
restart: unless-stopped
volumes:
postgres_data:
redis_data:
nginx_cache:
```
## Nginx Reverse Proxy
```nginx
# nginx/nginx.conf
events { worker_connections 1024; }
http {
upstream velox_app {
server app:3700;
keepalive 64;
}
# Redirect HTTP → HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Static assets with long cache
location /assets/ {
proxy_pass http://velox_app;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Application
location / {
proxy_pass http://velox_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
}
```
## Health Check Endpoint
Add a health check to your application for Docker and load balancers:
```typescript
// routes/api/health+server.ts
import { defineHandler, json } from 'velox/server';
import { db } from '$lib/db';
export const GET = defineHandler(async () => {
try {
await db.$queryRaw`SELECT 1`;
return json({ status: 'ok', timestamp: new Date().toISOString() });
} catch (error) {
return json({ status: 'error', error: String(error) }, { status: 503 });
}
});
```
## Running Database Migrations
Run migrations as a separate one-shot container before starting the app:
```yaml
# docker-compose.yml — add this service
migrate:
image: my-velox-app:latest
command: npx prisma migrate deploy
environment:
DATABASE_URL: postgresql://velox:${DB_PASSWORD}@db:5432/velox
depends_on:
db:
condition: service_healthy
restart: "no"
```
## Container Best Practices
| Practice | Why |
|----------|-----|
| Multi-stage builds | Reduces final image size by 6080% |
| Non-root user | Limits damage if the container is compromised |
| Read-only filesystem | Mount only what needs to be writable |
| `--restart unless-stopped` | Survives host reboots |
| Resource limits | Prevents a runaway container from affecting neighbours |
| Health checks | Enables zero-downtime rolling updates |
Set resource limits:
```yaml
app:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
```

View file

@ -0,0 +1,275 @@
---
title: Self-Hosted
sort: 130
section-id: deployment
keywords: self-hosted, VPS, nginx, PM2, systemd, Linux, server deployment
description: Running Velox on a VPS with nginx as a reverse proxy, managed by PM2 or systemd
language: en
---
# Self-Hosted
Deploying Velox to your own server (a VPS, dedicated server, or on-premises machine) gives you full control over your infrastructure. This guide covers setting up a Linux server with nginx as a reverse proxy, and managing the Node.js process with either PM2 or systemd.
## Server Preparation
This guide assumes Ubuntu 24.04 LTS. Adjust package manager commands for other distributions.
```bash
# Update the system
sudo apt update && sudo apt upgrade -y
# Install Node.js (LTS) via nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
source ~/.bashrc
nvm install 22
nvm use 22
nvm alias default 22
# Install nginx
sudo apt install -y nginx
# Install PostgreSQL (if needed)
sudo apt install -y postgresql postgresql-contrib
# Install Redis (if needed)
sudo apt install -y redis-server
```
## Deploying the Application
### Option A: Direct File Copy
```bash
# On your local machine — build the app
npm run build
# Copy build output to the server
rsync -avz --delete .velox/output/ user@your-server.com:/var/www/my-velox-app/
rsync -avz package.json package-lock.json user@your-server.com:/var/www/my-velox-app/
# On the server — install production dependencies
cd /var/www/my-velox-app
npm ci --omit=dev
```
### Option B: Git + CI/CD
Create a deploy script on the server:
```bash
#!/bin/bash
# /home/deploy/deploy.sh
set -e
APP_DIR=/var/www/my-velox-app
REPO=https://github.com/your-org/your-repo.git
cd $APP_DIR
# Pull latest code
git fetch --all
git reset --hard origin/main
# Install dependencies
npm ci --frozen-lockfile --omit=dev
# Build
npm run build
# Restart application
pm2 reload my-velox-app --update-env
echo "Deployment complete."
```
Call this from your GitHub Actions workflow:
```yaml
# .github/workflows/deploy.yml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: deploy
key: ${{ secrets.SERVER_SSH_KEY }}
script: /home/deploy/deploy.sh
```
## Environment Variables
Store production secrets in `/etc/environment` or a `.env.production` file:
```bash
# /etc/environment (system-wide)
NODE_ENV=production
DATABASE_URL=postgresql://velox:secretpass@localhost:5432/velox
SESSION_SECRET=your-long-random-secret-here
JWT_SECRET=another-long-random-secret
# Or in a project-specific .env.production
sudo nano /var/www/my-velox-app/.env.production
```
Load the env file in your start command (covered below).
## Process Management with PM2
PM2 is a battle-tested process manager for Node.js applications.
```bash
npm install -g pm2
```
Create a PM2 ecosystem file:
```javascript
// /var/www/my-velox-app/ecosystem.config.js
module.exports = {
apps: [{
name: 'my-velox-app',
script: 'server.js',
cwd: '/var/www/my-velox-app',
instances: 'max', // one process per CPU core
exec_mode: 'cluster', // enable cluster mode for zero-downtime restarts
env_file: '/var/www/my-velox-app/.env.production',
env: {
NODE_ENV: 'production',
PORT: 3700,
},
error_file: '/var/log/pm2/my-velox-app-error.log',
out_file: '/var/log/pm2/my-velox-app-out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
max_memory_restart: '500M', // restart if memory exceeds 500 MB
}],
};
```
Start and persist:
```bash
pm2 start ecosystem.config.js
pm2 save # persist process list across reboots
pm2 startup # install systemd startup script
# Useful commands
pm2 status
pm2 logs my-velox-app
pm2 reload my-velox-app # zero-downtime reload
pm2 restart my-velox-app # full restart
pm2 monit # real-time monitoring
```
## systemd Service (Alternative to PM2)
For simpler setups, a systemd unit file:
```ini
# /etc/systemd/system/my-velox-app.service
[Unit]
Description=My Velox Application
After=network.target postgresql.service redis.service
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/my-velox-app
EnvironmentFile=/var/www/my-velox-app/.env.production
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=my-velox-app
# Resource limits
LimitNOFILE=65536
MemoryMax=512M
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable my-velox-app
sudo systemctl start my-velox-app
sudo journalctl -u my-velox-app -f # follow logs
```
## Nginx Configuration
```nginx
# /etc/nginx/sites-available/my-velox-app
upstream velox {
server 127.0.0.1:3700;
keepalive 32;
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
gzip on;
gzip_types text/plain application/json application/javascript text/css;
# Long-lived cache for hashed static assets
location ~ ^/assets/.*\.[0-9a-f]{8}\. {
proxy_pass http://velox;
add_header Cache-Control "public, max-age=31536000, immutable";
}
location / {
proxy_pass http://velox;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}
```
```bash
sudo ln -s /etc/nginx/sites-available/my-velox-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
## SSL with Let's Encrypt
```bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
# Certbot automatically renews certificates via a cron job
```
## Firewall
```bash
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status
```

View file

@ -0,0 +1,183 @@
---
title: Deploy to Vercel
sort: 100
section-id: deployment
keywords: Vercel, deployment, serverless, edge, CI/CD, deploy
description: Step-by-step guide to deploying a Velox application to Vercel
language: en
---
# Deploy to Vercel
Vercel is the recommended hosting platform for Velox applications. The Velox Vercel adapter handles all configuration automatically — no manual `vercel.json` setup is required for most projects.
## Prerequisites
- A Vercel account ([vercel.com](https://vercel.com))
- The Vercel CLI: `npm install -g vercel`
- A Velox project committed to a Git repository (GitHub, GitLab, or Bitbucket)
## Automatic Deployment via Git Integration
### Step 1 — Push Your Project to Git
```bash
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/your-username/your-project.git
git push -u origin main
```
### Step 2 — Import the Project on Vercel
1. Go to [vercel.com/new](https://vercel.com/new)
2. Click **Add New Project** → **Import Git Repository**
3. Select your repository
4. Vercel automatically detects Velox and sets the correct build settings:
- **Framework Preset:** Velox
- **Build Command:** `velox build`
- **Output Directory:** `.velox/output`
- **Install Command:** `npm ci`
### Step 3 — Configure Environment Variables
In the Vercel project dashboard:
1. Go to **Settings** → **Environment Variables**
2. Add each variable from your `.env.production` file
Do **not** add `PUBLIC_BASE_URL` manually — Vercel sets `VERCEL_URL` automatically and the Velox Vercel adapter uses it.
### Step 4 — Deploy
Click **Deploy**. Vercel will:
1. Clone your repository
2. Install dependencies
3. Run `velox build`
4. Deploy to its global edge network
Your site is live at `https://your-project.vercel.app`.
## Vercel CLI Deployment
For manual or scripted deployments:
```bash
# Install adapter
npm install @velox/vercel
# Deploy to preview (equivalent to a branch deploy)
vercel
# Deploy to production
vercel --prod
```
## Configuring the Vercel Adapter
Install and configure `@velox/vercel`:
```bash
npm install @velox/vercel
```
```typescript
// velox.config.ts
import { defineConfig } from 'velox';
import { vercel } from '@velox/vercel';
export default defineConfig({
adapter: vercel({
// Route-level edge function configuration
edgeRoutes: ['/api/stream', '/api/realtime'],
// Override ISR revalidation for specific routes
isr: {
expiration: 60, // default revalidation (seconds)
bypassToken: process.env.VERCEL_ISR_BYPASS_TOKEN,
},
// Enable Vercel Image Optimisation (uses Vercel's CDN)
images: {
sizes: [640, 1080, 1920],
},
}),
});
```
## Edge Functions
Mark individual routes to run on Vercel Edge instead of Node.js serverless:
```typescript
// routes/api/stream+server.ts
export const config = { edge: true };
export const GET = defineHandler(async (req) => {
// Runs on Vercel Edge — uses Web APIs only
return new Response('Hello from edge!');
});
```
Edge functions are deployed globally in ~70 Vercel regions and have ~0ms cold start. Use them for latency-sensitive, stateless handlers.
## Custom Domains
1. Go to your project on Vercel → **Settings** → **Domains**
2. Click **Add Domain**
3. Enter your domain (e.g., `example.com`)
4. Follow the DNS configuration instructions:
- Add a **CNAME** record: `www``cname.vercel-dns.com`
- Add an **A** record: `@``76.76.21.21`
5. SSL certificates are provisioned automatically via Let's Encrypt
Set the canonical base URL environment variable in Vercel:
```
PUBLIC_BASE_URL = https://example.com
```
## Preview Deployments
Every pull request gets an automatic preview deployment at a unique URL (`https://your-project-git-branch-name.vercel.app`). This is configured by default and requires no additional setup.
To share preview deployments with your team, enable **Password Protection** under **Settings****Deployment Protection**.
## Build Cache
Velox's Velocitor build cache is automatically persisted across deployments by the Vercel adapter. Subsequent deployments typically build 35× faster than the first.
## `vercel.json` Reference
For advanced configuration, create a `vercel.json` at your project root:
```json
{
"regions": ["iad1", "fra1"],
"cleanUrls": true,
"trailingSlash": false,
"headers": [
{
"source": "/assets/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
}
],
"redirects": [
{ "source": "/old-path", "destination": "/new-path", "permanent": true }
]
}
```
## Monitoring and Analytics
Enable Vercel Analytics and Speed Insights in your project dashboard. The Velox adapter hooks into these automatically — no code changes needed.
For custom event tracking:
```typescript
import { track } from '@vercel/analytics';
track('button_clicked', { component: 'Hero', variant: 'primary' });
```

View file

@ -0,0 +1,288 @@
---
title: Authentication
sort: 100
section-id: guides
keywords: authentication, JWT, OAuth2, session, login, auth, security
description: Implementing authentication in Velox using JWT, OAuth2, and session-based strategies
language: en
---
# Authentication
This guide covers the three most common authentication patterns for Velox applications: session-based auth, JWT-based auth, and OAuth2 with third-party providers.
## Session-Based Authentication
Session auth stores user state server-side. The client holds only a signed session cookie. This is the simplest and most secure approach for most web applications.
### Setup
Install the session plugin:
```bash
npm install @velox/session @velox/session-redis
```
Configure in `velox.config.ts`:
```typescript
import { RedisSessionStore } from '@velox/session-redis';
export default defineConfig({
session: {
secret: process.env.SESSION_SECRET!,
cookieName: 'app.session',
maxAge: 60 * 60 * 24 * 7, // 7 days
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
store: new RedisSessionStore({ url: process.env.REDIS_URL! }),
},
});
```
### Login Handler
```typescript
// routes/api/auth/login+server.ts
import { defineHandler, json, redirect } from 'velox/server';
import { useSession } from 'velox/server';
import { db } from '$lib/db';
import { verifyPassword } from '$lib/crypto';
export const POST = defineHandler(async (req) => {
const { email, password } = await req.json();
if (!email || !password) {
return json({ error: 'Email and password are required' }, { status: 400 });
}
const user = await db.users.findByEmail(email);
if (!user || !(await verifyPassword(password, user.passwordHash))) {
return json({ error: 'Invalid credentials' }, { status: 401 });
}
const session = await useSession();
await session.regenerate(); // prevent session fixation
await session.update({ userId: user.id, role: user.role });
return json({ ok: true, redirectTo: '/dashboard' });
});
```
### Auth Middleware
```typescript
// middleware/auth.ts
import { defineMiddleware, redirect } from 'velox/server';
import { useSession } from 'velox/server';
const PUBLIC_PATHS = ['/login', '/register', '/api/auth'];
export default defineMiddleware(async ({ request, next }) => {
const pathname = new URL(request.url).pathname;
if (PUBLIC_PATHS.some(p => pathname.startsWith(p))) {
return next();
}
const session = await useSession<{ userId: string }>();
if (!session.data.userId) {
return redirect(`/login?next=${encodeURIComponent(pathname)}`);
}
request.context.set('userId', session.data.userId);
return next();
});
```
## JWT Authentication
JWT is stateless and ideal for API-first applications or mobile/SPA backends.
### Generating Tokens
```typescript
// lib/jwt.ts
import { SignJWT, jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.JWT_SECRET!);
export interface JWTPayload {
sub: string; // user ID
role: string;
iat: number;
exp: number;
}
export async function signToken(userId: string, role: string): Promise<string> {
return new SignJWT({ sub: userId, role })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('15m')
.sign(secret);
}
export async function signRefreshToken(userId: string): Promise<string> {
return new SignJWT({ sub: userId, type: 'refresh' })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('30d')
.sign(secret);
}
export async function verifyToken(token: string): Promise<JWTPayload> {
const { payload } = await jwtVerify(token, secret);
return payload as JWTPayload;
}
```
### Login + Refresh Endpoints
```typescript
// routes/api/auth/token+server.ts
import { defineHandler, json, badRequest, unauthorized } from 'velox/server';
import { signToken, signRefreshToken, verifyToken } from '$lib/jwt';
import { db } from '$lib/db';
import { verifyPassword } from '$lib/crypto';
export const POST = defineHandler(async (req) => {
const { grant_type, email, password, refresh_token } = await req.json();
if (grant_type === 'password') {
const user = await db.users.findByEmail(email);
if (!user || !(await verifyPassword(password, user.passwordHash))) {
return unauthorized('Invalid credentials');
}
return json({
access_token: await signToken(user.id, user.role),
refresh_token: await signRefreshToken(user.id),
token_type: 'Bearer',
expires_in: 900,
});
}
if (grant_type === 'refresh_token') {
const payload = await verifyToken(refresh_token).catch(() => null);
if (!payload || payload.type !== 'refresh') {
return unauthorized('Invalid refresh token');
}
const user = await db.users.findById(payload.sub);
return json({
access_token: await signToken(user.id, user.role),
token_type: 'Bearer',
expires_in: 900,
});
}
return badRequest('Unsupported grant_type');
});
```
## OAuth2 with Third-Party Providers
### Using `@velox/auth`
```bash
npm install @velox/auth
```
Configure providers:
```typescript
// lib/auth.ts
import { createAuth } from '@velox/auth';
export const auth = createAuth({
providers: [
{
id: 'github',
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
authorizationUrl: 'https://github.com/login/oauth/authorize',
tokenUrl: 'https://github.com/login/oauth/access_token',
userInfoUrl: 'https://api.github.com/user',
scopes: ['user:email'],
},
{
id: 'google',
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
// Google uses OpenID Connect — most fields are auto-discovered
},
],
callbacks: {
async onUserInfo(provider, userInfo) {
const user = await db.users.upsert({
where: { email: userInfo.email },
create: { email: userInfo.email, name: userInfo.name, provider },
update: { name: userInfo.name },
});
return { id: user.id, role: user.role };
},
},
});
```
OAuth callback route:
```typescript
// routes/auth/[provider]/callback+server.ts
import { defineHandler } from 'velox/server';
import { auth } from '$lib/auth';
export const GET = defineHandler(async (req) => {
return auth.handleCallback(req);
});
```
## Password Hashing
Always use a slow hashing algorithm. Velox recommends Argon2:
```typescript
// lib/crypto.ts
import { hash, verify } from '@node-rs/argon2';
export async function hashPassword(password: string): Promise<string> {
return hash(password, {
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1,
});
}
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
return verify(hash, password);
}
```
## Role-Based Access Control
```typescript
// middleware/rbac.ts
import { defineMiddleware, forbidden } from 'velox/server';
const ROUTE_ROLES: Record<string, string[]> = {
'/admin': ['admin'],
'/api/admin': ['admin'],
'/api/reports': ['admin', 'analyst'],
};
export default defineMiddleware(async ({ request, next }) => {
const pathname = new URL(request.url).pathname;
const requiredRoles = Object.entries(ROUTE_ROLES)
.find(([path]) => pathname.startsWith(path))?.[1];
if (!requiredRoles) return next();
const user = request.context.get('user') as { role: string } | undefined;
if (!user || !requiredRoles.includes(user.role)) {
return forbidden('Insufficient permissions');
}
return next();
});
```

View file

@ -0,0 +1,285 @@
---
title: Database Integration
sort: 110
section-id: guides
keywords: database, Prisma, DrizzleORM, SQL, ORM, PostgreSQL, database integration
description: How to integrate databases in Velox using Prisma, DrizzleORM, or raw SQL
language: en
---
# Database Integration
Velox is database-agnostic. This guide covers the three most popular approaches: Prisma (full-featured ORM), DrizzleORM (lightweight TypeScript-first ORM), and raw SQL with a typed query builder.
## Prisma
Prisma is the most popular ORM in the Node.js ecosystem. It provides a schema-first approach, auto-generated type-safe client, and powerful migrations.
### Setup
```bash
npm install prisma @prisma/client
npx prisma init
```
This creates a `prisma/schema.prisma` file. Example schema:
```prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
slug String @unique
content String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
USER
ADMIN
ANALYST
}
```
Generate and run the initial migration:
```bash
npx prisma migrate dev --name init
npx prisma generate
```
### Database Client Singleton
In a server environment, always reuse a single Prisma client instance:
```typescript
// lib/db.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const db = globalForPrisma.prisma ?? new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'warn', 'error'] : ['error'],
});
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db;
```
### Usage in Routes
```typescript
// routes/blog/[slug].velox server block
import { db } from '$lib/db';
const post = await db.post.findUnique({
where: { slug: params.slug, published: true },
include: { author: { select: { name: true } } },
});
if (!post) throw notFound();
```
### Transactions
```typescript
const result = await db.$transaction(async (tx) => {
const user = await tx.user.create({ data: { email, name } });
const profile = await tx.profile.create({ data: { userId: user.id } });
return { user, profile };
});
```
## DrizzleORM
DrizzleORM is a TypeScript-first ORM with a SQL-like query API and zero overhead.
### Setup
```bash
npm install drizzle-orm postgres
npm install -D drizzle-kit
```
Define your schema in TypeScript:
```typescript
// lib/schema.ts
import { pgTable, text, boolean, timestamp, pgEnum } from 'drizzle-orm/pg-core';
export const roleEnum = pgEnum('role', ['user', 'admin', 'analyst']);
export const users = pgTable('users', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
email: text('email').notNull().unique(),
name: text('name'),
role: roleEnum('role').default('user').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
export const posts = pgTable('posts', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
title: text('title').notNull(),
slug: text('slug').notNull().unique(),
content: text('content').notNull(),
published: boolean('published').default(false).notNull(),
authorId: text('author_id').notNull().references(() => users.id),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
```
### Database Client
```typescript
// lib/db.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client, { schema });
```
### Querying
```typescript
import { db } from '$lib/db';
import { posts, users } from '$lib/schema';
import { eq, and, desc } from 'drizzle-orm';
// Find one
const post = await db.query.posts.findFirst({
where: and(eq(posts.slug, slug), eq(posts.published, true)),
with: { author: { columns: { name: true } } },
});
// Find many with ordering
const recentPosts = await db
.select()
.from(posts)
.where(eq(posts.published, true))
.orderBy(desc(posts.createdAt))
.limit(10);
// Insert
const [newPost] = await db
.insert(posts)
.values({ title, slug, content, authorId })
.returning();
// Update
await db
.update(posts)
.set({ published: true, updatedAt: new Date() })
.where(eq(posts.id, postId));
// Delete
await db.delete(posts).where(eq(posts.id, postId));
```
### Migrations
Configure Drizzle Kit:
```typescript
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './lib/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: { url: process.env.DATABASE_URL! },
});
```
```bash
npx drizzle-kit generate
npx drizzle-kit migrate
```
## Raw SQL with `postgres.js`
For maximum control and performance with complex queries:
```typescript
// lib/db.ts
import postgres from 'postgres';
export const sql = postgres(process.env.DATABASE_URL!, {
max: 20, // connection pool size
idle_timeout: 20, // seconds before idle connection closes
connect_timeout: 10, // connection timeout
types: {
bigint: postgres.BigInt, // return BigInt instead of string
},
});
```
Usage:
```typescript
import { sql } from '$lib/db';
// Parameterised query (safe from SQL injection)
const users = await sql<User[]>`
SELECT id, email, name, role
FROM users
WHERE role = ${role}
ORDER BY created_at DESC
LIMIT ${limit}
`;
// Transaction
const result = await sql.begin(async (sql) => {
const [user] = await sql`
INSERT INTO users (email, name)
VALUES (${email}, ${name})
RETURNING *
`;
await sql`
INSERT INTO audit_log (user_id, action)
VALUES (${user.id}, 'register')
`;
return user;
});
```
## Connection Pooling in Production
For serverless or edge deployments, use a connection pooler:
- **PgBouncer** — a lightweight PostgreSQL connection pooler for VPS deployments
- **Supabase Supavisor** — serverless-aware pooler built for transactional workloads
- **Neon / PlanetScale** — managed databases with built-in HTTP-based connection pooling
```typescript
// For serverless (e.g. Vercel, Cloudflare) — use HTTP-based driver
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
const users = await sql`SELECT * FROM users LIMIT 10`;
```

View file

@ -0,0 +1,261 @@
---
title: Internationalisation
sort: 130
section-id: guides
keywords: i18n, internationalisation, localisation, translation, locale routing, multilingual
description: Setting up internationalisation in Velox — translation files, locale routing, and pluralisation
language: en
---
# Internationalisation
Velox has built-in internationalisation (i18n) support through `@velox/i18n`. It handles locale detection, URL-based locale routing, typed translation files, and pluralisation.
## Setup
```bash
npm install @velox/i18n
```
Configure in `velox.config.ts`:
```typescript
import { defineConfig } from 'velox';
export default defineConfig({
i18n: {
locales: ['en', 'fr', 'de', 'ja'],
defaultLocale: 'en',
routing: 'prefix-except-default',
// 'prefix': all locales get a prefix (/en, /fr, /de, /ja)
// 'prefix-except-default': default locale has no prefix
// 'domain': different domains per locale
messagesDir: 'messages',
},
});
```
## Translation Files
Create a `messages/` directory at your project root:
```
messages/
├── en.json
├── fr.json
├── de.json
└── ja.json
```
Translation files use a flat or nested key structure:
```json
// messages/en.json
{
"nav.home": "Home",
"nav.blog": "Blog",
"nav.about": "About",
"home.hero.title": "Build faster with Velox",
"home.hero.subtitle": "The TypeScript framework for the modern web",
"home.cta": "Get started",
"post.readMore": "Read more",
"post.publishedOn": "Published on {date}",
"post.comments": "{count, plural, =0{No comments} =1{1 comment} other{# comments}}",
"auth.loginButton": "Log in",
"auth.logoutButton": "Log out",
"errors.notFound": "Page not found",
"errors.serverError": "Something went wrong"
}
```
```json
// messages/fr.json
{
"nav.home": "Accueil",
"nav.blog": "Blog",
"nav.about": "À propos",
"home.hero.title": "Construisez plus vite avec Velox",
"home.hero.subtitle": "Le framework TypeScript pour le web moderne",
"home.cta": "Commencer",
"post.readMore": "Lire la suite",
"post.publishedOn": "Publié le {date}",
"post.comments": "{count, plural, =0{Aucun commentaire} =1{1 commentaire} other{# commentaires}}",
"auth.loginButton": "Se connecter",
"auth.logoutButton": "Se déconnecter",
"errors.notFound": "Page introuvable",
"errors.serverError": "Une erreur est survenue"
}
```
## Using Translations
### In Server Blocks
```tsx
---
import { useTranslations } from 'velox/i18n';
const t = useTranslations();
const locale = useLocale().locale;
---
<main>
<h1>{t('home.hero.title')}</h1>
<p>{t('home.hero.subtitle')}</p>
<a href="/docs">{t('home.cta')}</a>
</main>
```
### With Parameters
```tsx
---
const t = useTranslations();
const publishedDate = new Intl.DateTimeFormat(locale).format(post.createdAt);
---
<p>{t('post.publishedOn', { date: publishedDate })}</p>
```
### Pluralisation
Velox uses the ICU message format for pluralisation:
```typescript
t('post.comments', { count: 0 }); // "No comments"
t('post.comments', { count: 1 }); // "1 comment"
t('post.comments', { count: 42 }); // "42 comments"
```
### In Client Components
```typescript
import { useTranslations } from 'velox/i18n/client';
export default function LikeButton({ postId }: { postId: string }) {
const t = useTranslations();
const liked = signal(false);
return (
<button onClick={() => liked.value = !liked.value}>
{liked.value ? t('post.liked') : t('post.like')}
</button>
);
}
```
## Locale Routing
With `routing: 'prefix-except-default'` and `defaultLocale: 'en'`:
| URL | Locale |
|-----|--------|
| `/` | `en` |
| `/about` | `en` |
| `/fr` | `fr` |
| `/fr/about` | `fr` |
| `/de/blog/my-post` | `de` |
Velox automatically generates alternate hreflang links for SEO.
## Locale Switcher Component
```tsx
import { useLocale, usePathname } from 'velox/i18n/client';
export default function LocaleSwitcher() {
const { locale, locales } = useLocale();
const pathname = usePathname();
return (
<div class="locale-switcher">
{locales.map(loc => (
<a
key={loc}
href={getLocalizedPath(pathname.value, loc)}
class={loc === locale ? 'active' : ''}
hreflang={loc}
>
{getLocaleLabel(loc)}
</a>
))}
</div>
);
}
function getLocaleLabel(locale: string): string {
const labels: Record<string, string> = {
en: 'English',
fr: 'Français',
de: 'Deutsch',
ja: '日本語',
};
return labels[locale] ?? locale;
}
```
## Domain-Based Routing
For country-specific top-level domains:
```typescript
export default defineConfig({
i18n: {
locales: ['en', 'fr', 'de'],
defaultLocale: 'en',
routing: 'domain',
domains: {
en: 'example.com',
fr: 'example.fr',
de: 'example.de',
},
},
});
```
## Type-Safe Translation Keys
Generate a TypeScript type for your translation keys:
```bash
npx velox i18n:generate-types
```
This creates `.velox/types/i18n.d.ts` so that `t('invalid.key')` is a compile-time error.
## RTL Languages
For right-to-left languages (Arabic, Hebrew, etc.), add the `dir` attribute dynamically:
```tsx
// layouts/default.velox
---
const { locale } = useLocale();
const isRTL = ['ar', 'he', 'fa'].includes(locale);
---
<html lang={locale} dir={isRTL ? 'rtl' : 'ltr'}>
...
</html>
```
## Date, Number, and Currency Formatting
Use the `Intl` APIs with the current locale:
```typescript
import { useLocale } from 'velox/i18n';
const { locale } = useLocale();
// Dates
const dateFormatter = new Intl.DateTimeFormat(locale, { dateStyle: 'long' });
const formatted = dateFormatter.format(new Date(post.createdAt));
// Currency
const priceFormatter = new Intl.NumberFormat(locale, {
style: 'currency',
currency: 'USD',
});
const price = priceFormatter.format(product.price);
```

View file

@ -0,0 +1,283 @@
---
title: Performance
sort: 140
section-id: guides
keywords: performance, code splitting, lazy loading, caching, optimisation, Core Web Vitals
description: Performance optimisation strategies for Velox apps — code splitting, lazy loading, and caching
language: en
---
# Performance
![Performance Dashboard](assets/images/performance.jpg)
Velox is designed to be fast by default. The Rust-based compiler handles tree-shaking, code splitting, and asset optimisation automatically. This guide covers the additional strategies you can apply to push your application to peak performance.
## Core Web Vitals Targets
Before optimising, establish baselines. Aim for:
| Metric | Good | Needs Work |
|--------|------|-----------|
| LCP (Largest Contentful Paint) | ≤ 2.5s | > 4.0s |
| FID / INP (Interaction to Next Paint) | ≤ 200ms | > 500ms |
| CLS (Cumulative Layout Shift) | ≤ 0.1 | > 0.25 |
| TTFB (Time to First Byte) | ≤ 800ms | > 1800ms |
Use Velox's built-in analytics to monitor these in production:
```typescript
// velox.config.ts
export default defineConfig({
analytics: {
webVitals: true,
endpoint: '/api/analytics/vitals',
},
});
```
## Code Splitting
Velox automatically splits your JavaScript bundle by route. Every route gets its own chunk, and shared code is extracted into a common chunk. You do not need to configure this.
For further control, use dynamic imports:
```typescript
// Only loads when the user clicks "Open"
const HeavyEditor = lazy(() => import('./HeavyEditor'));
function PostPage() {
const isEditing = signal(false);
return (
<div>
<h1>{post.title}</h1>
{isEditing.value && (
<Suspense fallback={<Skeleton />}>
<HeavyEditor post={post} />
</Suspense>
)}
<button onClick={() => isEditing.value = true}>Edit</button>
</div>
);
}
```
## Lazy Hydration
Delay hydration of interactive components until they are needed:
```tsx
<!-- Hydrate only when the component scrolls into view -->
<CommentSection client:visible postId={post.id} />
<!-- Hydrate only when the browser is idle -->
<AnalyticsWidget client:idle />
<!-- Hydrate only when a CSS media query matches -->
<MobileNav client:media="(max-width: 768px)" />
<!-- Never hydrate — purely server-rendered, no JS -->
<StaticSidebar />
```
The less JavaScript you ship to the client, the better the INP score.
## Image Optimisation
Enable image optimisation in `velox.config.ts`:
```typescript
assets: {
imageOptimisation: {
enabled: true,
formats: ['webp', 'avif'],
quality: 85,
maxWidth: 2000,
},
},
```
Use the `<Image>` component for automatic width/height and format negotiation:
```tsx
import { Image } from 'velox';
<Image
src="/assets/images/hero.jpg"
alt="Hero image"
width={1200}
height={400}
priority // preload this image (use for above-the-fold images)
sizes="(max-width: 768px) 100vw, 1200px"
/>
```
The `priority` prop adds a `<link rel="preload">` tag to the document head and marks the image as `fetchpriority="high"`, which is the single biggest LCP improvement for image-heavy pages.
## HTTP Caching
Set appropriate `Cache-Control` headers for your routes:
```typescript
// In a route server block
response.headers.set('Cache-Control', 'public, max-age=3600, stale-while-revalidate=86400');
// Or via config for specific paths
export default defineConfig({
headers: [
{
source: '/assets/*',
headers: [
{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' },
],
},
{
source: '/',
headers: [
{ key: 'Cache-Control', value: 'public, max-age=0, s-maxage=3600, stale-while-revalidate=86400' },
],
},
],
});
```
## Server-Side Caching
Cache expensive computations and database queries:
```typescript
import { useCache } from 'velox/server';
const cache = useCache();
async function getPopularPosts() {
const cached = await cache.get<Post[]>('posts:popular');
if (cached) return cached;
const posts = await db.posts.findMany({
where: { published: true },
orderBy: { viewCount: 'desc' },
take: 10,
});
await cache.set('posts:popular', posts, { ttl: 300 }); // 5 minutes
return posts;
}
```
## Database Query Optimisation
Common patterns that prevent N+1 queries:
```typescript
// Bad: N+1 query
const posts = await db.post.findMany();
const postsWithAuthors = await Promise.all(
posts.map(p => db.user.findById(p.authorId)) // N extra queries!
);
// Good: single query with include
const postsWithAuthors = await db.post.findMany({
include: { author: { select: { id: true, name: true } } },
});
```
Add indexes for commonly filtered columns:
```sql
-- In a migration
CREATE INDEX CONCURRENTLY idx_posts_published_created
ON posts (published, created_at DESC)
WHERE published = true;
```
## Edge Deployment
Deploy to edge locations close to your users:
```typescript
// velox.config.ts
export default defineConfig({
build: {
target: 'edge',
},
});
```
Edge-compatible routes must use Web APIs only:
```typescript
// ✅ Edge-compatible
import { defineHandler } from 'velox/server';
export const GET = defineHandler(async (req) => {
const data = await fetch('https://api.example.com/data');
return Response.json(await data.json());
});
// ❌ Not edge-compatible (uses Node.js built-ins)
import fs from 'node:fs';
```
## Bundle Analysis
Generate and review a bundle analysis report:
```bash
ANALYZE=1 npm run build
# Opens .velox/output/analyze.html in your browser
```
Look for:
- Unexpectedly large dependencies (replace with smaller alternatives)
- Duplicate dependencies at different versions
- Client-side imports of server-only packages
## Prefetching
Configure link prefetching globally:
```typescript
export default defineConfig({
prefetch: {
defaultStrategy: 'hover', // 'hover' | 'viewport' | false
concurrency: 2, // max concurrent prefetch requests
ignore: ['/admin/*', '/api/*'],
},
});
```
## Font Loading
Avoid layout shift from font loading:
```css
/* styles/global.css */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-var.woff2') format('woff2');
font-weight: 100 900;
font-display: swap; /* show fallback text immediately */
}
```
```tsx
// layouts/default.velox
<Head>
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossOrigin="" />
</Head>
```
## Performance Monitoring
Integrate with monitoring services:
```typescript
// lib/monitoring.ts
export function reportWebVitals({ name, value, id }: Metric) {
fetch('/api/analytics/vitals', {
method: 'POST',
body: JSON.stringify({ name, value, id, page: window.location.pathname }),
keepalive: true,
});
}
```

View file

@ -0,0 +1,327 @@
---
title: Testing
sort: 120
section-id: guides
keywords: testing, unit tests, integration tests, E2E, Playwright, Vitest, testing strategy
description: Testing Velox applications with unit tests, integration tests, and end-to-end tests using Playwright
language: en
---
# Testing
Velox integrates with Vitest for unit and integration tests, and Playwright for end-to-end tests. The `@velox/test` package provides additional test utilities tailored for Velox's server blocks and API routes.
## Setup
```bash
npm install -D vitest @velox/test @playwright/test
npx playwright install
```
Add test scripts to `package.json`:
```json
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:e2e": "playwright test",
"test:coverage": "vitest --coverage"
}
}
```
Configure Vitest:
```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { veloxTestPlugin } from '@velox/test';
export default defineConfig({
plugins: [veloxTestPlugin()],
test: {
environment: 'node',
globals: true,
setupFiles: ['./tests/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'lcov'],
},
},
});
```
## Unit Tests
### Testing Utility Functions
```typescript
// lib/formatters.ts
export function formatCurrency(amount: number, currency = 'USD'): string {
return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount);
}
```
```typescript
// tests/unit/formatters.test.ts
import { describe, it, expect } from 'vitest';
import { formatCurrency } from '$lib/formatters';
describe('formatCurrency', () => {
it('formats USD amounts', () => {
expect(formatCurrency(1000)).toBe('$1,000.00');
expect(formatCurrency(9.99)).toBe('$9.99');
});
it('formats other currencies', () => {
expect(formatCurrency(1000, 'EUR')).toBe('€1,000.00');
});
it('handles zero', () => {
expect(formatCurrency(0)).toBe('$0.00');
});
});
```
### Testing Components
```tsx
// tests/unit/Counter.test.tsx
import { describe, it, expect } from 'vitest';
import { render, fireEvent } from '@velox/test';
import Counter from '$components/Counter';
describe('Counter', () => {
it('renders with initial value', () => {
const { getByText } = render(<Counter initialValue={5} />);
expect(getByText('5')).toBeTruthy();
});
it('increments on button click', async () => {
const { getByText, getByRole } = render(<Counter initialValue={0} />);
await fireEvent.click(getByRole('button', { name: /increment/i }));
expect(getByText('1')).toBeTruthy();
});
it('decrements below initial value', async () => {
const { getByText, getByRole } = render(<Counter initialValue={3} />);
await fireEvent.click(getByRole('button', { name: /decrement/i }));
expect(getByText('2')).toBeTruthy();
});
});
```
## Integration Tests
### Testing API Routes
The `createTestServer` utility starts a real Velox server for integration testing:
```typescript
// tests/integration/api/users.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createTestServer } from '@velox/test';
import { db } from '$lib/db';
let server: Awaited<ReturnType<typeof createTestServer>>;
beforeAll(async () => {
server = await createTestServer({ seed: true });
});
afterAll(async () => {
await server.stop();
await db.$disconnect();
});
describe('GET /api/users', () => {
it('returns all users', async () => {
const res = await server.inject({
method: 'GET',
url: '/api/users',
headers: { Authorization: `Bearer ${server.getAdminToken()}` },
});
expect(res.status).toBe(200);
const body = await res.json();
expect(Array.isArray(body)).toBe(true);
expect(body.length).toBeGreaterThan(0);
});
it('returns 401 without auth', async () => {
const res = await server.inject({ method: 'GET', url: '/api/users' });
expect(res.status).toBe(401);
});
});
describe('POST /api/users', () => {
it('creates a new user', async () => {
const res = await server.inject({
method: 'POST',
url: '/api/users',
body: { email: 'new@example.com', name: 'New User' },
headers: { Authorization: `Bearer ${server.getAdminToken()}` },
});
expect(res.status).toBe(201);
const user = await res.json();
expect(user.email).toBe('new@example.com');
expect(user.id).toBeDefined();
});
it('rejects duplicate email', async () => {
await server.inject({
method: 'POST',
url: '/api/users',
body: { email: 'dup@example.com', name: 'First' },
headers: { Authorization: `Bearer ${server.getAdminToken()}` },
});
const res = await server.inject({
method: 'POST',
url: '/api/users',
body: { email: 'dup@example.com', name: 'Second' },
headers: { Authorization: `Bearer ${server.getAdminToken()}` },
});
expect(res.status).toBe(409);
});
});
```
### Testing Database Operations
```typescript
// tests/integration/db/posts.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { db } from '$lib/db';
import { createTestUser, createTestPost } from '../factories';
beforeEach(async () => {
await db.$executeRaw`TRUNCATE posts, users RESTART IDENTITY CASCADE`;
});
describe('Post queries', () => {
it('finds published posts only', async () => {
const user = await createTestUser();
await createTestPost({ authorId: user.id, published: true });
await createTestPost({ authorId: user.id, published: false });
const posts = await db.post.findMany({ where: { published: true } });
expect(posts).toHaveLength(1);
});
});
```
## End-to-End Tests with Playwright
```typescript
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
use: {
baseURL: 'http://localhost:3700',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
webServer: {
command: 'npm run dev',
url: 'http://localhost:3700',
reuseExistingServer: !process.env.CI,
},
});
```
```typescript
// tests/e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Authentication', () => {
test('user can log in', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('correct-password');
await page.getByRole('button', { name: 'Log in' }).click();
await page.waitForURL('/dashboard');
await expect(page.getByText('Welcome back')).toBeVisible();
});
test('shows error for invalid credentials', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('wrong-password');
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page.getByRole('alert')).toContainText('Invalid credentials');
await expect(page).toHaveURL('/login');
});
});
```
## Test Factories
Use factories to generate test data consistently:
```typescript
// tests/factories.ts
import { db } from '$lib/db';
import { hashPassword } from '$lib/crypto';
export async function createTestUser(overrides = {}) {
return db.user.create({
data: {
email: `user-${Date.now()}@example.com`,
name: 'Test User',
passwordHash: await hashPassword('test-password'),
...overrides,
},
});
}
export async function createTestPost(overrides = {}) {
return db.post.create({
data: {
title: 'Test Post',
slug: `test-post-${Date.now()}`,
content: 'Test content',
published: true,
...overrides,
},
});
}
```
## CI Configuration
```yaml
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test
options: >-
--health-cmd pg_isready
--health-interval 10s
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '22' }
- run: npm ci
- run: npx prisma migrate deploy
env:
DATABASE_URL: postgresql://postgres:postgres@localhost/test
- run: npm test
- run: npx playwright install --with-deps
- run: npm run test:e2e
```

93
velox-docs/pages/index.md Normal file
View file

@ -0,0 +1,93 @@
---
title: Introduction
sort: 100
section-id: getting-started
keywords: velox, framework, typescript, javascript, full-stack, introduction
description: An introduction to Velox, the high-performance TypeScript web framework
language: en
---
# Introduction to Velox
![Velox Framework Hero](assets/images/hero.jpg)
Velox is a high-performance, full-stack TypeScript and JavaScript web framework built for developers who refuse to compromise between developer experience and production performance. Combining a file-based routing model inspired by the best parts of Next.js, an island-based rendering architecture inspired by Astro, and a Rust-powered build toolchain that makes cold starts and hot reloads feel instantaneous, Velox occupies a unique position in the modern web ecosystem.
## Why Velox?
The JavaScript ecosystem is rich, yet fragmented. Some frameworks offer outstanding developer experience but struggle with raw performance at scale. Others are extremely fast but require significant configuration overhead before you can write your first component. Velox was created to close that gap.
The core philosophy of Velox can be summarised in three words: **fast by default**. Every architectural decision — from the Rust-based bundler (Velocitor) to the zero-runtime component model — is made to ensure that your production application runs as efficiently as possible without requiring manual intervention from the developer.
## Key Features
### Rust-Powered Toolchain (Velocitor)
The Velox build system, internally called Velocitor, is written in Rust. It handles TypeScript transpilation, module bundling, tree-shaking, code splitting, and asset optimisation in a single pass. On modern hardware, a full production build of a medium-sized application typically completes in under three seconds. Incremental rebuilds during development are sub-50ms for most file changes.
### File-Based Routing
Velox uses a file-based routing system. Any `.velox` or `.tsx` file placed under the `routes/` directory automatically becomes a route. Dynamic segments use `[param]` notation, optional segments use `[[param]]`, and catch-all routes use `[...rest]`. No manual route registration is ever required.
### Islands Architecture
By default, Velox renders pages entirely on the server. Interactive components are "islands" — explicitly opt-in to client-side hydration using the `client:*` directive. This means your pages ship zero JavaScript by default; you add interactivity exactly where it is needed.
### Hybrid Rendering Modes
Velox supports four rendering strategies per route:
- **SSR (Server-Side Rendering)** — rendered fresh on every request
- **SSG (Static Site Generation)** — pre-rendered at build time
- **ISR (Incremental Static Regeneration)** — statically generated but revalidated on a schedule
- **CSR (Client-Side Rendering)** — fully client-rendered for dashboard-style pages
You can mix rendering modes across routes within a single project.
### TypeScript First
Velox is written in TypeScript and treats TypeScript as a first-class citizen. Configuration files, route handlers, middleware, and components all benefit from full type inference. There is no separate type-generation step required.
### Edge-Ready
Velox applications are deployable to Cloudflare Workers, Vercel Edge, and similar runtimes out of the box. The framework's core HTTP runtime has no Node.js-specific dependencies and operates on standard Web APIs (`Request`, `Response`, `fetch`, `crypto`), making edge deployment trivially simple.
### Built-In Middleware System
The middleware system allows you to intercept and transform requests and responses at multiple points in the pipeline. Auth, rate limiting, logging, CORS, and header manipulation are all achievable through composable middleware functions.
## How Velox Compares
| Feature | Velox | Next.js | Astro | Remix |
|---|---|---|---|---|
| Build system | Rust (Velocitor) | Webpack/Turbopack | Vite | Vite |
| Default JS shipped | Zero | Varies | Zero | Varies |
| Rendering modes | SSR/SSG/ISR/CSR | SSR/SSG/ISR | SSG/SSR | SSR |
| Full-stack API routes | Yes | Yes | Yes | Yes |
| File-based routing | Yes | Yes | Yes | No |
| TypeScript support | First-class | First-class | First-class | First-class |
| Edge runtime | Native | Adapter | Adapter | Adapter |
## Who Is Velox For?
Velox is well suited for:
- Teams building content-heavy marketing sites that need fast initial load times with selective interactivity
- Full-stack application teams who want a unified framework for frontend and backend
- Engineers at high-scale companies who need ISR and edge caching to serve global audiences
- Developers migrating from Next.js who want faster build times without giving up the Next.js mental model
Velox is perhaps not the best choice if you are building a highly stateful single-page application that is mostly client-rendered — in that case a pure SPA framework may be simpler.
## Getting Started
The quickest way to get started with Velox is to install the CLI and scaffold a new project:
```bash
npm create velox@latest my-app
cd my-app
npm run dev
```
Your new project will be running at `http://localhost:3700` in under ten seconds.
Read on to the [Installation](installation.md) guide for full setup instructions, or jump directly to the [Quick Start](quick-start.md) if you prefer to learn by doing.

View file

@ -0,0 +1,194 @@
---
title: Installation
sort: 110
section-id: getting-started
keywords: install, setup, npm, yarn, pnpm, bun, node, requirements
description: How to install Velox and set up your development environment
language: en
---
# Installation
This page covers everything you need to install Velox and get your development environment ready to build production applications.
## System Requirements
Before installing Velox, ensure your system meets the following requirements:
| Requirement | Minimum Version | Recommended |
|---|---|---|
| Node.js | 20.0.0 | 22.x LTS |
| npm | 9.0.0 | 10.x |
| Operating system | macOS 12, Ubuntu 22.04, Windows 10 | Latest stable |
| RAM | 2 GB | 8 GB+ |
| Disk space | 500 MB | 2 GB (for caches) |
Velox's Rust-based build system (Velocitor) ships as a pre-compiled binary for macOS (arm64 + x86_64), Linux (x86_64 + aarch64), and Windows (x86_64). You do **not** need a Rust toolchain installed on your machine.
## Installing the Velox CLI
The Velox CLI (`velox`) is the primary tool for scaffolding projects, running the development server, and triggering builds. Install it globally with your preferred package manager:
```bash
# npm
npm install -g velox-cli
# yarn
yarn global add velox-cli
# pnpm
pnpm add -g velox-cli
# bun
bun add -g velox-cli
```
After installation, verify the CLI is available:
```bash
velox --version
# velox-cli v1.4.0
```
## Creating a New Project
### Using `create-velox` (Recommended)
The easiest way to start a new Velox project is through the `create-velox` initialiser. It interactively walks you through project setup:
```bash
npm create velox@latest
```
You will be asked:
1. **Project name** — used as the directory name and initial package name
2. **Template** — choose from `minimal`, `blog`, `docs`, `dashboard`, or `e-commerce`
3. **TypeScript or JavaScript** — TypeScript is strongly recommended
4. **Package manager** — the scaffolder will use this for the initial install
For non-interactive use, pass flags directly:
```bash
npm create velox@latest my-app -- --template minimal --ts --pm pnpm
```
### Manual Installation
If you prefer to configure everything from scratch:
```bash
mkdir my-app && cd my-app
npm init -y
npm install velox
npm install -D velox-cli typescript @types/node
```
Create a minimal `velox.config.ts`:
```typescript
import { defineConfig } from 'velox';
export default defineConfig({
app: {
name: 'my-app',
},
});
```
## Installing Dependencies in an Existing Project
If you are adding Velox to an existing TypeScript project:
```bash
npm install velox
npm install -D velox-cli
```
Then add the following scripts to your `package.json`:
```json
{
"scripts": {
"dev": "velox dev",
"build": "velox build",
"start": "velox start",
"preview": "velox preview"
}
}
```
## Node Version Management
We recommend using a Node version manager to ensure you always have the correct version. Popular options:
```bash
# nvm (macOS/Linux)
nvm install 22
nvm use 22
# fnm (fast, cross-platform)
fnm install 22
fnm use 22
# volta (pins versions per project)
volta install node@22
```
You can also pin the Node version for your project by adding a `.nvmrc` or `.node-version` file:
```
22
```
## Environment Variables
Velox reads environment variables from `.env` files at the project root. The following files are loaded in order (later files override earlier ones):
| File | Loaded in | Committed to git? |
|---|---|---|
| `.env` | All environments | Usually yes (no secrets) |
| `.env.local` | All environments | No (gitignored) |
| `.env.development` | `velox dev` only | Usually yes |
| `.env.production` | `velox build` / `velox start` | Usually yes |
| `.env.test` | Test runs | Usually yes |
Variables prefixed with `PUBLIC_` are exposed to the browser. All other variables remain server-side only.
```bash
# .env
PUBLIC_APP_NAME=My Velox App
DATABASE_URL=postgres://localhost:5432/mydb
SECRET_API_KEY=do-not-expose-this
```
## Updating Velox
To update Velox to the latest version within your project:
```bash
npm install velox@latest
npm install -D velox-cli@latest
```
To check whether updates are available without applying them:
```bash
velox upgrade --check
```
## Troubleshooting Installation
**`velox` command not found after global install**
Ensure your package manager's global bin directory is in your `PATH`. For npm, run `npm config get prefix` and add the `bin` subdirectory to your shell profile.
**Velocitor binary fails to run on Linux**
Some minimal Linux environments (e.g., Alpine Linux) may be missing the `glibc` version Velocitor requires. Install `glibc` compatibility libraries or use the musl build:
```bash
npm install velox@latest --velox-binary-variant=musl
```
**Windows Defender flags the Velocitor binary**
This is a false positive. Add an exclusion for your project's `node_modules/.velox-bin/` directory.
Next, follow the [Quick Start](quick-start.md) guide to build your first Velox application.

242
velox-docs/pages/layouts.md Normal file
View file

@ -0,0 +1,242 @@
---
title: Layouts
sort: 150
section-id: core-concepts
keywords: layouts, nested layouts, shared layouts, layout groups, _layout, slots
description: How to use nested layouts, shared layouts, and layout groups in Velox
language: en
---
# Layouts
Layouts define the structural shell around your page content — navigation headers, sidebars, footers, and anything else that persists across multiple pages. Velox provides a flexible, composable layout system built directly into the file-based router.
## Default Layout
The file `layouts/default.velox` is the root layout applied to all routes unless overridden:
```tsx
// layouts/default.velox
---
import Header from '../components/Header.tsx';
import Footer from '../components/Footer.tsx';
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{meta.title} — My App</title>
<meta name="description" content={meta.description} />
<link rel="stylesheet" href="/styles/global.css" />
</head>
<body>
<Header />
<main id="main-content">
<slot />
</main>
<Footer />
</body>
</html>
```
The `<slot />` element is where the current page's content is injected. Layout files receive `meta` (the exports from the route's server block) and `params` automatically.
## Route-Level Layout Override
A route can specify a different layout using the `layout` export in its server block:
```tsx
---
export const layout = 'minimal'; // uses layouts/minimal.velox
export const meta = { title: 'Login' };
---
<div class="login-container">
<LoginForm />
</div>
```
Set `layout` to `false` to render the page with no layout at all:
```tsx
---
export const layout = false; // bare HTML response
---
<!DOCTYPE html>
<html>...</html>
```
## `_layout.velox` — Directory Scoped Layouts
Place a `_layout.velox` file inside any `routes/` subdirectory to apply a layout to all routes in that directory and its subdirectories:
```
routes/
├── _layout.velox ← root layout (all routes)
├── index.velox
├── about.velox
└── admin/
├── _layout.velox ← admin layout (only /admin/* routes)
├── index.velox
└── users/
├── _layout.velox ← users layout (only /admin/users/* routes)
└── index.velox
```
Layouts nest — the admin layout wraps its content inside the root layout's `<slot />`, and the users layout wraps inside the admin layout's `<slot />`:
```
Root Layout
└── Admin Layout
└── Users Layout
└── Page Content
```
### Admin Layout Example
```tsx
// routes/admin/_layout.velox
---
import AdminNav from '../../components/AdminNav.tsx';
const user = request.context.get('user');
export const meta = { title: 'Admin' };
---
<div class="admin-shell">
<AdminNav user={user} />
<div class="admin-content">
<slot />
</div>
</div>
```
## Layout Groups
Wrap a directory in parentheses to create a **route group** that applies a shared layout without affecting the URL:
```
routes/
├── (public)/
│ ├── _layout.velox ← marketing/public layout
│ ├── index.velox → /
│ └── pricing.velox → /pricing
└── (app)/
├── _layout.velox ← application layout (requires auth)
├── dashboard.velox → /dashboard
└── settings.velox → /settings
```
Both `/` and `/pricing` use the public layout. Both `/dashboard` and `/settings` use the app layout. The group name `(public)` and `(app)` never appear in the URL.
## Shared Layout Components
For UI elements shared between multiple layouts (a navigation bar used by both the public and admin layouts, for example), extract them as regular components:
```tsx
// components/TopNav.tsx
interface TopNavProps {
links: { label: string; href: string }[];
user?: User | null;
}
export default function TopNav({ links, user }: TopNavProps) {
return (
<nav class="top-nav">
<ul>
{links.map(link => (
<li><a href={link.href}>{link.label}</a></li>
))}
</ul>
{user ? <UserMenu user={user} /> : <a href="/login">Log in</a>}
</nav>
);
}
```
Use it in both layouts:
```tsx
// layouts/default.velox
<TopNav links={publicLinks} user={null} />
// routes/admin/_layout.velox
<TopNav links={adminLinks} user={user} />
```
## Data in Layouts
`_layout.velox` files have their own server block and can fetch data independently:
```tsx
// routes/dashboard/_layout.velox
---
import { db } from '$lib/db';
const user = request.context.get('user');
const notifications = await db.notifications.findUnread(user.id);
---
<div class="dashboard-shell">
<DashboardNav notifications={notifications} />
<slot />
</div>
```
Layout data is fetched in parallel with page data — there is no waterfall.
## Parallel Slots
For complex layouts with multiple content regions, use named parallel slots:
```tsx
// A layout that accepts both main content and a sidebar
---
export const slots = ['default', 'sidebar'];
---
<div class="two-column">
<aside class="sidebar">
<slot name="sidebar" />
</aside>
<main>
<slot />
</main>
</div>
```
Fill the named slot from the page:
```tsx
---
export const layout = 'two-column';
---
<!-- Default slot content -->
<article>Main content here</article>
<!-- Named slot -->
<velox:slot name="sidebar">
<TableOfContents />
</velox:slot>
```
## `<Head>` Management
Layouts and pages can both add content to the HTML `<head>` using the `<Head>` component:
```tsx
import { Head } from 'velox';
<Head>
<link rel="canonical" href={canonicalUrl} />
<meta property="og:title" content={meta.title} />
<meta property="og:image" content={meta.ogImage} />
<script type="application/ld+json">{JSON.stringify(structuredData)}</script>
</Head>
```
`<Head>` insertions from nested layouts and pages are all collected and deduplicated before the final HTML is emitted.

View file

@ -0,0 +1,253 @@
---
title: Middleware
sort: 140
section-id: core-concepts
keywords: middleware, request pipeline, auth middleware, rate limiting, CORS, headers
description: How to use and write Velox middleware for request/response transformation
language: en
---
# Middleware
Middleware in Velox is a function that intercepts HTTP requests before they reach a route handler and can transform both the request and the response. Middleware is composable, typed, and supports async operations.
## How Middleware Works
The Velox request pipeline processes a request in the following order:
1. **Global middleware** — applied to every request
2. **Path-scoped middleware** — applied based on route matching
3. **Route handler** — the actual page or API route
4. **Response middleware** — runs on the way out (in reverse order)
```
Request
[Global Middleware 1]
[Global Middleware 2]
[Path Middleware]
[Route Handler]
[Response (in reverse)]
```
## Writing Middleware
Create a middleware file in the `middleware/` directory. A middleware function receives the `request` and a `next` function. Call `next()` to pass control to the next middleware or the route handler:
```typescript
// middleware/logger.ts
import { defineMiddleware } from 'velox/server';
export default defineMiddleware(async ({ request, next }) => {
const start = Date.now();
const response = await next();
const duration = Date.now() - start;
console.log(`${request.method} ${request.url} — ${response.status} (${duration}ms)`);
return response;
});
```
## Configuring Middleware
Register middleware in `velox.config.ts`:
```typescript
import { defineConfig } from 'velox';
export default defineConfig({
middleware: [
{ path: '*', handler: './middleware/logger' },
{ path: '/admin/*', handler: './middleware/auth' },
{ path: '/api/*', handler: './middleware/rateLimit' },
],
});
```
Or, use a `middleware.ts` file at the project root for global middleware:
```typescript
// middleware.ts (project root — applies to all routes automatically)
import { defineMiddleware } from 'velox/server';
export default defineMiddleware(async ({ request, next }) => {
// runs on every request
return next();
});
```
## Authentication Middleware
```typescript
// middleware/auth.ts
import { defineMiddleware, redirect } from 'velox/server';
import { verifyJWT } from '$lib/auth';
export default defineMiddleware(async ({ request, next }) => {
const token = request.cookies.get('session')?.value
?? request.headers.get('Authorization')?.replace('Bearer ', '');
if (!token) {
return redirect('/login?next=' + encodeURIComponent(request.url));
}
let user;
try {
user = await verifyJWT(token);
} catch {
return redirect('/login?error=invalid_token');
}
// Attach user to request context for downstream handlers
request.context.set('user', user);
return next();
});
```
Access the context in your route:
```typescript
// routes/dashboard.velox server block
const user = request.context.get('user');
```
## Rate Limiting Middleware
```typescript
// middleware/rateLimit.ts
import { defineMiddleware } from 'velox/server';
const counters = new Map<string, { count: number; resetAt: number }>();
const WINDOW_MS = 60_000; // 1 minute
const MAX_REQUESTS = 100;
export default defineMiddleware(async ({ request, next }) => {
const ip = request.headers.get('CF-Connecting-IP')
?? request.headers.get('X-Forwarded-For')
?? 'unknown';
const now = Date.now();
const counter = counters.get(ip) ?? { count: 0, resetAt: now + WINDOW_MS };
if (now > counter.resetAt) {
counter.count = 0;
counter.resetAt = now + WINDOW_MS;
}
counter.count++;
counters.set(ip, counter);
if (counter.count > MAX_REQUESTS) {
return new Response('Too Many Requests', {
status: 429,
headers: {
'Retry-After': String(Math.ceil((counter.resetAt - now) / 1000)),
'X-RateLimit-Limit': String(MAX_REQUESTS),
'X-RateLimit-Remaining': '0',
},
});
}
const response = await next();
response.headers.set('X-RateLimit-Limit', String(MAX_REQUESTS));
response.headers.set('X-RateLimit-Remaining', String(MAX_REQUESTS - counter.count));
return response;
});
```
## CORS Middleware
```typescript
// middleware/cors.ts
import { defineMiddleware } from 'velox/server';
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS?.split(',') ?? ['*'];
export default defineMiddleware(async ({ request, next }) => {
const origin = request.headers.get('Origin') ?? '';
const isAllowed = ALLOWED_ORIGINS.includes('*') || ALLOWED_ORIGINS.includes(origin);
if (request.method === 'OPTIONS') {
// Preflight response
return new Response(null, {
status: 204,
headers: corsHeaders(origin, isAllowed),
});
}
const response = await next();
if (isAllowed) {
for (const [key, value] of Object.entries(corsHeaders(origin, true))) {
response.headers.set(key, value);
}
}
return response;
});
function corsHeaders(origin: string, allowed: boolean) {
if (!allowed) return {};
return {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Max-Age': '86400',
};
}
```
## Security Headers Middleware
```typescript
// middleware/securityHeaders.ts
import { defineMiddleware } from 'velox/server';
export default defineMiddleware(async ({ next }) => {
const response = await next();
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-XSS-Protection', '1; mode=block');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set(
'Permissions-Policy',
'camera=(), microphone=(), geolocation=()'
);
response.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'nonce-{nonce}'; style-src 'self' 'unsafe-inline'"
);
return response;
});
```
## Composing Middleware
Combine multiple middleware into a single handler:
```typescript
import { composeMiddleware } from 'velox/server';
import logger from './logger';
import auth from './auth';
import rateLimit from './rateLimit';
export default composeMiddleware(logger, rateLimit, auth);
```
## Middleware Execution Order
In `velox.config.ts`, middleware is applied in the order it is declared for the request path, and in reverse order for the response path. This mirrors the middleware stack pattern found in Express, Koa, and similar frameworks.
| Middleware | On request | On response |
|------------|-----------|------------|
| logger | 1st | 4th (last) |
| rateLimit | 2nd | 3rd |
| auth | 3rd | 2nd |
| route | 4th | 1st |

View file

@ -0,0 +1,217 @@
---
title: Project Structure
sort: 130
section-id: getting-started
keywords: project structure, directory layout, files, folders, architecture
description: A detailed explanation of every file and folder in a Velox project
language: en
---
# Project Structure
![Velox Architecture](assets/images/architecture.jpg)
Understanding the Velox project structure is key to working effectively with the framework. This page explains the purpose of every directory and file you will encounter in a standard Velox project.
## Top-Level Layout
```
my-velox-app/
├── routes/ ← all pages and API routes
├── layouts/ ← shared page layouts
├── components/ ← reusable UI components
├── lib/ ← shared server-side utilities
├── middleware/ ← request/response interceptors
├── public/ ← static assets (copied verbatim)
├── styles/ ← global CSS and design tokens
├── tests/ ← test files
├── .velox/ ← build cache and output (gitignored)
├── velox.config.ts ← framework configuration
├── tsconfig.json ← TypeScript configuration
├── package.json
└── .env ← environment variables
```
## `routes/`
This is the most important directory. Every file in `routes/` maps to a URL path unless prefixed with `_` (underscore files are ignored by the router).
### Page Routes
Files ending in `.velox` or `.tsx` become page routes:
```
routes/
├── index.velox → /
├── about.velox → /about
├── blog/
│ ├── index.velox → /blog
│ └── [slug].velox → /blog/:slug
├── docs/
│ └── [...path].velox → /docs/* (catch-all)
└── _components/ ← underscore prefix: ignored by router
└── Header.tsx
```
### API Routes
Files suffixed with `+server.ts` become API routes. They export HTTP method handlers:
```
routes/
└── api/
├── users+server.ts → /api/users
└── users/[id]+server.ts → /api/users/:id
```
### Special Files
| File | Purpose |
|---|---|
| `_error.velox` | Custom error page (404, 500) in any directory |
| `_loading.velox` | Loading state shown during route transitions |
| `_layout.velox` | Layout that wraps all sibling routes |
## `layouts/`
Layout files define the shell that wraps your page content. A layout receives a `<slot />` where the page content is injected.
```tsx
// layouts/default.velox
---
import Header from '../components/Header.tsx';
import Footer from '../components/Footer.tsx';
---
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{meta.title}</title>
</head>
<body>
<Header />
<slot />
<Footer />
</body>
</html>
```
The default layout (`layouts/default.velox`) wraps all routes unless a route specifies a different layout via the `layout` frontmatter property.
## `components/`
Reusable UI components that can be used in routes and layouts. Components do not have server blocks — they are purely presentational (though they can accept server-fetched data as props).
```
components/
├── Header.tsx
├── Footer.tsx
├── Button.tsx
├── ui/
│ ├── Card.tsx
│ └── Modal.tsx
└── forms/
├── Input.tsx
└── Select.tsx
```
## `lib/`
Server-side utility modules — database clients, external API helpers, auth utilities. Code in `lib/` is never bundled for the client unless explicitly imported from a client-side component.
```
lib/
├── db.ts ← database connection
├── auth.ts ← authentication helpers
├── email.ts ← email sending
└── cache.ts ← caching utilities
```
## `middleware/`
Middleware functions execute on every matching request before the route handler runs. Middleware files are auto-loaded based on path:
```
middleware/
├── auth.ts ← applies to all routes (no path suffix)
└── admin.ts ← must be explicitly referenced in velox.config.ts
```
## `public/`
Static files in `public/` are served at the root path without transformation. A file at `public/robots.txt` is served at `/robots.txt`. Use this for favicons, `manifest.json`, `sitemap.xml`, and static images that do not need optimisation.
## `styles/`
Global stylesheets. Velox supports CSS Modules (`.module.css`), plain CSS, and Sass (`.scss`) if the `@velox/sass` plugin is installed. The file `styles/global.css` is automatically injected into every page.
```
styles/
├── global.css ← injected automatically
├── tokens.css ← CSS custom properties / design tokens
└── reset.css ← CSS reset or normalise
```
## `.velox/`
Generated directory — **never commit this to git**. Contains:
- `.velox/cache/` — Velocitor's incremental build cache (keyed by content hash)
- `.velox/output/` — production build output
- `.velox/tmp/` — temporary files used during development
Add `.velox/` to your `.gitignore`:
```
# .gitignore
.velox/
.env.local
node_modules/
```
## `velox.config.ts`
The central configuration file for the framework. See the full [Configuration](configuration.md) reference for every available option. A minimal example:
```typescript
import { defineConfig } from 'velox';
export default defineConfig({
app: {
name: 'my-velox-app',
baseUrl: process.env.PUBLIC_BASE_URL ?? 'http://localhost:3700',
},
build: {
target: 'node',
},
});
```
## `tsconfig.json`
Velox requires certain TypeScript compiler options to function correctly. The scaffolder generates an appropriate `tsconfig.json` automatically. Key options:
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "preserve",
"jsxImportSource": "velox",
"strict": true,
"paths": {
"$lib/*": ["./lib/*"],
"$components/*": ["./components/*"]
}
}
}
```
The `$lib` and `$components` path aliases are available throughout your project without needing to calculate relative paths.
## Generated Files
Velox generates a small number of type definition files in `.velox/types/` that provide type-safe access to environment variables, route params, and other framework internals. These are referenced automatically by the `tsconfig.json` generated by the scaffolder.
Understanding this structure will help you navigate any Velox project confidently. Next, explore the [Configuration](configuration.md) reference to learn all available options.

View file

@ -0,0 +1,208 @@
---
title: Quick Start
sort: 120
section-id: getting-started
keywords: quick start, first app, hello world, dev server, file structure
description: Build your first Velox application from scratch in minutes
language: en
---
# Quick Start
This guide walks you through creating a working Velox application from zero. By the end you will have a running dev server, a couple of pages with navigation between them, and an API route returning JSON.
## Step 1 — Scaffold the Project
```bash
npm create velox@latest my-first-app -- --template minimal --ts --pm npm
cd my-first-app
```
The scaffolder creates the following structure:
```
my-first-app/
├── routes/
│ ├── index.velox ← homepage route
│ └── about.velox ← about page route
├── layouts/
│ └── default.velox ← wraps all routes by default
├── public/
│ └── favicon.svg
├── velox.config.ts
├── tsconfig.json
└── package.json
```
## Step 2 — Start the Development Server
```bash
npm run dev
```
Velox starts the development server on port 3700 by default:
```
⚡ Velox v1.4.0 — development server
┌─────────────────────────────────────┐
│ Local: http://localhost:3700 │
│ Network: http://192.168.1.5:3700 │
└─────────────────────────────────────┘
Ready in 312ms
```
Open `http://localhost:3700` in your browser. You should see the default Velox welcome page.
## Step 3 — Understand the Default Page
Open `routes/index.velox`. A `.velox` file is a superset of TSX with some Velox-specific features:
```tsx
---
// Server-side frontmatter block — runs only on the server
export const meta = {
title: 'Home',
description: 'My first Velox app',
};
---
<main>
<h1>Welcome to Velox!</h1>
<p>Edit <code>routes/index.velox</code> to get started.</p>
<a href="/about">About</a>
</main>
```
The `---` fenced block at the top is the **server block** — it runs exclusively during server-side rendering or static generation. Exports from the server block (like `meta`) are available as template variables in the HTML section below.
## Step 4 — Add a New Route
Create a new file at `routes/contact.velox`:
```tsx
---
export const meta = {
title: 'Contact',
description: 'Get in touch with us',
};
---
<main>
<h1>Contact Us</h1>
<p>Send us a message at <a href="mailto:hello@example.com">hello@example.com</a></p>
</main>
```
Save the file. Velox automatically picks up the new route — navigate to `http://localhost:3700/contact` and you will see your new page without restarting the server.
## Step 5 — Create an API Route
API routes live in the `routes/` directory alongside page routes, but are named with a `+` prefix to distinguish them.
Create `routes/api/hello+server.ts`:
```typescript
import { defineHandler } from 'velox/server';
export const GET = defineHandler(async (req) => {
const name = new URL(req.url).searchParams.get('name') ?? 'World';
return Response.json({ message: `Hello, ${name}!` });
});
```
Test it at `http://localhost:3700/api/hello?name=Velox`. You should receive:
```json
{ "message": "Hello, Velox!" }
```
## Step 6 — Fetch Data on the Server
Update `routes/index.velox` to fetch data server-side:
```tsx
---
import { fetch } from 'velox/server';
export const meta = {
title: 'Home',
};
// This runs on the server only
const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const todo = await res.json();
---
<main>
<h1>Welcome to Velox!</h1>
<p>Todo from API: {todo.title}</p>
</main>
```
## Step 7 — Add Interactivity (Islands)
By default, Velox ships zero client-side JavaScript. To add a client-side interactive component, create a component and opt into hydration:
Create `components/Counter.tsx`:
```tsx
import { signal } from 'velox/client';
export default function Counter() {
const count = signal(0);
return (
<div>
<p>Count: {count.value}</p>
<button onClick={() => count.value++}>Increment</button>
</div>
);
}
```
Use it in your page with the `client:load` directive:
```tsx
---
import Counter from '../components/Counter.tsx';
---
<main>
<h1>Home</h1>
<Counter client:load />
</main>
```
The `client:load` directive tells Velox to hydrate this component immediately when the page loads. Other directives include `client:idle` (hydrate when browser is idle) and `client:visible` (hydrate when the component enters the viewport).
## Step 8 — Build for Production
```bash
npm run build
```
Velocitor compiles a production-ready build into the `.velox/output/` directory. Review the build output:
```
⚡ Velox — production build
Compiled 4 routes in 1.8s
┌─────────────────────────────────────────┐
│ / → SSR (0 KB JS) │
│ /about → SSG (0 KB JS) │
│ /contact → SSG (0 KB JS) │
│ /api/hello → Edge handler │
└─────────────────────────────────────────┘
Total JS shipped to client: 2.1 KB (Counter island only)
```
To preview the production build locally:
```bash
npm run preview
```
## Next Steps
- Read about [Project Structure](project-structure.md) to understand every directory and file
- Explore [Routing](routing.md) for dynamic routes and catch-all patterns
- Learn about [Data Fetching](data-fetching.md) for SSR, SSG, and ISR patterns
- Check the [Configuration](configuration.md) reference for `velox.config.ts` options

237
velox-docs/pages/routing.md Normal file
View file

@ -0,0 +1,237 @@
---
title: Routing
sort: 100
section-id: core-concepts
keywords: routing, file-based routing, dynamic routes, catch-all, nested routes, navigation
description: How Velox's file-based routing works, including dynamic segments, catch-all routes, and nested layouts
language: 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:
```tsx
---
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`:
```typescript
// 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:
```tsx
// 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:
```typescript
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:
```tsx
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:
```typescript
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:
```typescript
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:
```typescript
export const meta = () => ({
title: post.title,
description: post.excerpt,
});
```

View file

@ -0,0 +1,258 @@
---
title: State Management
sort: 120
section-id: core-concepts
keywords: state, signals, store, reactive, computed, effect, state management
description: Velox's reactive state management system — signals, stores, and reactive primitives
language: en
---
# State Management
Velox uses a signals-based reactivity system for client-side state. Signals are fine-grained reactive primitives — when a signal's value changes, only the DOM nodes that actually read that signal update. There is no virtual DOM diffing, no component tree re-render, and no unnecessary reconciliation.
## Signals
A signal is a reactive value container:
```typescript
import { signal } from 'velox/client';
const count = signal(0);
// Read the current value
console.log(count.value); // 0
// Update the value
count.value = 1;
console.log(count.value); // 1
```
In JSX, signals are automatically unwrapped — you do not need `.value` in templates:
```tsx
const count = signal(0);
// Both are equivalent:
<p>{count.value}</p>
<p>{count}</p>
```
The second form (`{count}` without `.value`) subscribes the DOM node directly to the signal. This is more efficient because Velox skips the component function entirely and updates only the text node when the signal changes.
## Computed Signals
A computed signal derives its value from one or more other signals. It re-evaluates automatically when its dependencies change:
```typescript
import { signal, computed } from 'velox/client';
const firstName = signal('Jane');
const lastName = signal('Doe');
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
console.log(fullName.value); // "Jane Doe"
firstName.value = 'John';
console.log(fullName.value); // "John Doe"
```
Computed signals are lazy and memoised — the computation only runs when the value is read, and only re-runs if a dependency has changed since the last read.
## Effects
An effect runs a side effect whenever its reactive dependencies change:
```typescript
import { signal, effect } from 'velox/client';
const query = signal('');
effect(() => {
console.log('Search query changed:', query.value);
// this re-runs every time query.value changes
});
```
Effects clean themselves up automatically when the component unmounts. To clean up manually within an effect (e.g., cancel a previous request):
```typescript
effect(() => {
const controller = new AbortController();
fetch(`/api/search?q=${query.value}`, { signal: controller.signal })
.then(r => r.json())
.then(results => resultsSignal.value = results);
return () => controller.abort(); // cleanup function
});
```
## Stores
For shared state across components, define a store. A store is a module that encapsulates signals and exposes a clean interface:
```typescript
// lib/stores/cart.ts
import { signal, computed } from 'velox/client';
export interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
const items = signal<CartItem[]>([]);
export const cartStore = {
items,
total: computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
),
itemCount: computed(() =>
items.value.reduce((sum, item) => sum + item.quantity, 0)
),
addItem(item: Omit<CartItem, 'quantity'>) {
const existing = items.value.find(i => i.id === item.id);
if (existing) {
items.value = items.value.map(i =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
);
} else {
items.value = [...items.value, { ...item, quantity: 1 }];
}
},
removeItem(id: string) {
items.value = items.value.filter(i => i.id !== id);
},
clear() {
items.value = [];
},
};
```
Use the store in any component — signals are module-level singletons:
```tsx
import { cartStore } from '$lib/stores/cart';
export default function CartIcon() {
return (
<button>
Cart ({cartStore.itemCount})
</button>
);
}
```
## Batch Updates
Multiple signal updates within a `batch()` call are processed as a single reactive update, preventing intermediate renders:
```typescript
import { signal, batch } from 'velox/client';
const x = signal(0);
const y = signal(0);
const z = signal(0);
// Without batch: triggers 3 re-renders
x.value = 1;
y.value = 2;
z.value = 3;
// With batch: triggers 1 re-render
batch(() => {
x.value = 1;
y.value = 2;
z.value = 3;
});
```
## Async Signals
For async data that needs to integrate with the reactivity system, use `asyncSignal`:
```typescript
import { signal, asyncSignal } from 'velox/client';
const userId = signal<string | null>(null);
const userProfile = asyncSignal(async () => {
if (!userId.value) return null;
const res = await fetch(`/api/users/${userId.value}`);
return res.json();
});
// userProfile.value is: { status: 'idle' | 'loading' | 'success' | 'error', data, error }
```
In JSX:
```tsx
<div>
{userProfile.value.status === 'loading' && <Spinner />}
{userProfile.value.status === 'success' && (
<p>Hello, {userProfile.value.data.name}</p>
)}
{userProfile.value.status === 'error' && (
<p>Error: {userProfile.value.error.message}</p>
)}
</div>
```
## Server State with `@velox/query`
For server state (data fetched from APIs), the `@velox/query` package provides a higher-level solution with caching, revalidation, and optimistic updates:
```typescript
import { createQuery, createMutation } from '@velox/query';
const postsQuery = createQuery({
key: ['posts'],
fetcher: () => fetch('/api/posts').then(r => r.json()),
staleTime: 60_000, // data is fresh for 60 seconds
});
const createPostMutation = createMutation({
mutator: (data) => fetch('/api/posts', { method: 'POST', body: JSON.stringify(data) }),
onSuccess: () => postsQuery.invalidate(),
});
```
## Persisting State
To persist signal state to `localStorage` or `sessionStorage`:
```typescript
import { persistedSignal } from 'velox/client';
const theme = persistedSignal('theme', 'light', {
storage: 'localStorage',
});
```
The signal is initialised from storage on mount and written back whenever it changes.
## Reactive Context (Dependency Injection)
For providing state to deeply nested component trees without prop drilling, use the context API:
```typescript
import { createContext, useContext } from 'velox/client';
const ThemeContext = createContext<'light' | 'dark'>('light');
// Provider (wraps a subtree)
<ThemeContext.Provider value={currentTheme}>
<App />
</ThemeContext.Provider>
// Consumer (anywhere in the tree below)
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button class={`btn btn--${theme}`}>Click me</button>;
}
```

302
velox-docs/search.json Normal file

File diff suppressed because one or more lines are too long

66
velox-docs/theme.yml Normal file
View file

@ -0,0 +1,66 @@
# mdcms theme — default
# Edit colours, fonts, and layout here. See docs for full reference.
# ──────────────────────────────────
# Colours
# ──────────────────────────────────
light:
accent: "#2563EB"
background: "#FFFFFF"
nav-background: "#F8FAFC"
text: "#1E293B"
text-muted: "#64748B"
dark:
accent: "#60A5FA"
background: "#0F172A"
nav-background: "#1E293B"
text: "#F1F5F9"
text-muted: "#94A3B8"
# ──────────────────────────────────
# Semantic colours
# Used by callout tags (info, warning, success, error).
# Choose values that work on both light and dark backgrounds.
# ──────────────────────────────────
colours-semantic:
info: "#2563EB"
warning: "#D97706"
success: "#16A34A"
error: "#DC2626"
# ──────────────────────────────────
# Callout defaults
# ──────────────────────────────────
callouts:
info:
icon: info
primary-colour: "#2563EB"
background-colour: "#2563EB"
warning:
icon: warning
primary-colour: "#D97706"
background-colour: "#D97706"
success:
icon: success
primary-colour: "#16A34A"
background-colour: "#16A34A"
error:
icon: error
primary-colour: "#DC2626"
background-colour: "#DC2626"
# ──────────────────────────────────
# Typography
# Format: "provider:Font Name:weight" (provider: bunny | google)
# ──────────────────────────────────
font-body: "bunny:Noto Sans:400"
font-heading: "bunny:Noto Sans:700"
font-size: 1.0 # unitless multiplier (1.0 = 16px base)
line-height: 1.7 # unitless multiplier
# ──────────────────────────────────
# Layout
# ──────────────────────────────────
main-width: 80em
nav-width: 20em