From 25884d2a090d47f15d92819713ea432a9ec313de Mon Sep 17 00:00:00 2001 From: kbenestad Date: Wed, 20 May 2026 18:18:05 +0700 Subject: [PATCH] Add velox-docs: config, nav, theme, search.json, and pages --- velox-docs/config.yml | 8 + velox-docs/nav.yml | 227 +++++++++++++++++ velox-docs/pages/api-components.md | 270 ++++++++++++++++++++ velox-docs/pages/api-config.md | 276 +++++++++++++++++++++ velox-docs/pages/api-hooks.md | 222 +++++++++++++++++ velox-docs/pages/api-router.md | 227 +++++++++++++++++ velox-docs/pages/api-server.md | 243 ++++++++++++++++++ velox-docs/pages/components.md | 253 +++++++++++++++++++ velox-docs/pages/configuration.md | 249 +++++++++++++++++++ velox-docs/pages/data-fetching.md | 267 ++++++++++++++++++++ velox-docs/pages/deploy-cloudflare.md | 213 ++++++++++++++++ velox-docs/pages/deploy-docker.md | 301 +++++++++++++++++++++++ velox-docs/pages/deploy-self-hosted.md | 275 +++++++++++++++++++++ velox-docs/pages/deploy-vercel.md | 183 ++++++++++++++ velox-docs/pages/guide-auth.md | 288 ++++++++++++++++++++++ velox-docs/pages/guide-database.md | 285 +++++++++++++++++++++ velox-docs/pages/guide-i18n.md | 261 ++++++++++++++++++++ velox-docs/pages/guide-performance.md | 283 +++++++++++++++++++++ velox-docs/pages/guide-testing.md | 327 +++++++++++++++++++++++++ velox-docs/pages/index.md | 93 +++++++ velox-docs/pages/installation.md | 194 +++++++++++++++ velox-docs/pages/layouts.md | 242 ++++++++++++++++++ velox-docs/pages/middleware.md | 253 +++++++++++++++++++ velox-docs/pages/project-structure.md | 217 ++++++++++++++++ velox-docs/pages/quick-start.md | 208 ++++++++++++++++ velox-docs/pages/routing.md | 237 ++++++++++++++++++ velox-docs/pages/state-management.md | 258 +++++++++++++++++++ velox-docs/search.json | 302 +++++++++++++++++++++++ velox-docs/theme.yml | 66 +++++ 29 files changed, 6728 insertions(+) create mode 100644 velox-docs/config.yml create mode 100644 velox-docs/nav.yml create mode 100644 velox-docs/pages/api-components.md create mode 100644 velox-docs/pages/api-config.md create mode 100644 velox-docs/pages/api-hooks.md create mode 100644 velox-docs/pages/api-router.md create mode 100644 velox-docs/pages/api-server.md create mode 100644 velox-docs/pages/components.md create mode 100644 velox-docs/pages/configuration.md create mode 100644 velox-docs/pages/data-fetching.md create mode 100644 velox-docs/pages/deploy-cloudflare.md create mode 100644 velox-docs/pages/deploy-docker.md create mode 100644 velox-docs/pages/deploy-self-hosted.md create mode 100644 velox-docs/pages/deploy-vercel.md create mode 100644 velox-docs/pages/guide-auth.md create mode 100644 velox-docs/pages/guide-database.md create mode 100644 velox-docs/pages/guide-i18n.md create mode 100644 velox-docs/pages/guide-performance.md create mode 100644 velox-docs/pages/guide-testing.md create mode 100644 velox-docs/pages/index.md create mode 100644 velox-docs/pages/installation.md create mode 100644 velox-docs/pages/layouts.md create mode 100644 velox-docs/pages/middleware.md create mode 100644 velox-docs/pages/project-structure.md create mode 100644 velox-docs/pages/quick-start.md create mode 100644 velox-docs/pages/routing.md create mode 100644 velox-docs/pages/state-management.md create mode 100644 velox-docs/search.json create mode 100644 velox-docs/theme.yml diff --git a/velox-docs/config.yml b/velox-docs/config.yml new file mode 100644 index 0000000..48df0f9 --- /dev/null +++ b/velox-docs/config.yml @@ -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 diff --git a/velox-docs/nav.yml b/velox-docs/nav.yml new file mode 100644 index 0000000..5bb125c --- /dev/null +++ b/velox-docs/nav.yml @@ -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 diff --git a/velox-docs/pages/api-components.md b/velox-docs/pages/api-components.md new file mode 100644 index 0000000..d58659b --- /dev/null +++ b/velox-docs/pages/api-components.md @@ -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(initialValue: T): Signal; + +interface Signal { + 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 + + + + +// Consume in any descendant +function Button() { + const theme = useContext(ThemeContext); + return ; +} +``` + +## `ref()` + +Create a DOM element reference: + +```typescript +import { ref, onMount } from 'velox/client'; + +export default function TextInput() { + const inputRef = ref(); + + onMount(() => { + inputRef.current?.focus(); + }); + + return ; +} +``` + +## `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 ( +
+ + {count} + +
+ ); + }, +}); + +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 + + +// With a Suspense boundary for a loading state +import { Suspense } from 'velox'; + +}> + + +``` + +## `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
    {items.map(i =>
  • {i}
  • )}
; +}); +``` + +Accepts a custom equality function as the second argument: + +```typescript +const MyComponent = memo(Component, (prev, next) => prev.id === next.id); +``` diff --git a/velox-docs/pages/api-config.md b/velox-docs/pages/api-config.md new file mode 100644 index 0000000..718cfa8 --- /dev/null +++ b/velox-docs/pages/api-config.md @@ -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; +} +``` + +## `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 1–100. Default: 80 */ + quality?: number; + /** Maximum width in pixels before downscaling */ + maxWidth?: number; + }; + + fonts?: { + /** Emit 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; + + /** 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; + 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) }; + }, + }; +} +``` diff --git a/velox-docs/pages/api-hooks.md b/velox-docs/pages/api-hooks.md new file mode 100644 index 0000000..79ce840 --- /dev/null +++ b/velox-docs/pages/api-hooks.md @@ -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; // route dynamic params + query: URLSearchParams; // parsed query string + cookies: RequestCookies; // parsed cookies + context: Map; // 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('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:*'); +``` diff --git a/velox-docs/pages/api-router.md b/velox-docs/pages/api-router.md new file mode 100644 index 0000000..8911d75 --- /dev/null +++ b/velox-docs/pages/api-router.md @@ -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` | 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(); +``` + +## `` Component + +The `` component renders an accessible `` element with client-side navigation and built-in prefetching: + +```tsx +import { Link } from 'velox/client'; + +About +``` + +### 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 + + Blog + +``` + +## `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 +``` + +## `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(); +``` diff --git a/velox-docs/pages/api-server.md b/velox-docs/pages/api-server.md new file mode 100644 index 0000000..3f46293 --- /dev/null +++ b/velox-docs/pages/api-server.md @@ -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`. + +### `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` | URL route parameters | +| `query` | `URLSearchParams` | Parsed query string | +| `cookies` | `RequestCookies` | Parsed cookies | +| `context` | `Map` | Values set by middleware | +| `json()` | `Promise` | Parse body as JSON | +| `text()` | `Promise` | Parse body as text | +| `formData()` | `Promise` | Parse body as form data | +| `arrayBuffer()` | `Promise` | 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('

Hello

'); +``` + +### `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 }); + }, +}); +``` diff --git a/velox-docs/pages/components.md b/velox-docs/pages/components.md new file mode 100644 index 0000000..b30a8ac --- /dev/null +++ b/velox-docs/pages/components.md @@ -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

{salutation}, {name}!

; +} +``` + +Use it in a route or another component: + +```tsx +--- +import Greeting from '../components/Greeting.tsx'; +--- + +
+ + +
+``` + +## 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 ( +
+

{href ? {title} : title}

+

{description}

+ {children &&
{children}
} +
+ ); +} +``` + +### 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 + +

This is the card body.

+
+``` + +### 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 ( + + ); +} +``` + +Usage: + +```tsx +Confirm Delete} + footer={ + <> + + + + } +> +

Are you sure you want to delete this item?

+
+``` + +## 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'; +--- + + + + + + + + + + + + +``` + +### 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

Current time: {time}

; +} +``` + +## 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([]); + 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 ( +
+ {loading.value ?

Loading...

:
    {data.value.map(d =>
  • {d.name}
  • )}
} +
+ ); +} +``` + +## Component Composition Patterns + +### Higher-Order Components + +```tsx +function withAuth(Component: VeloxComponent) { + return function AuthGated(props: T) { + const user = useUser(); + if (!user) return ; + return ; + }; +} +``` + +### Render Props / Function-as-Children + +```tsx +interface FetcherProps { + url: string; + render: (data: T) => VeloxNode; +} + +export function Fetcher({ url, render }: FetcherProps) { + // ... 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. diff --git a/velox-docs/pages/configuration.md b/velox-docs/pages/configuration.md new file mode 100644 index 0000000..97919fa --- /dev/null +++ b/velox-docs/pages/configuration.md @@ -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 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; // 1–100, 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. diff --git a/velox-docs/pages/data-fetching.md b/velox-docs/pages/data-fetching.md new file mode 100644 index 0000000..52a6ad4 --- /dev/null +++ b/velox-docs/pages/data-fetching.md @@ -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 +``` diff --git a/velox-docs/pages/deploy-cloudflare.md b/velox-docs/pages/deploy-cloudflare.md new file mode 100644 index 0000000..3241035 --- /dev/null +++ b/velox-docs/pages/deploy-cloudflare.md @@ -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 | diff --git a/velox-docs/pages/deploy-docker.md b/velox-docs/pages/deploy-docker.md new file mode 100644 index 0000000..f189529 --- /dev/null +++ b/velox-docs/pages/deploy-docker.md @@ -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 60–80% | +| 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 +``` diff --git a/velox-docs/pages/deploy-self-hosted.md b/velox-docs/pages/deploy-self-hosted.md new file mode 100644 index 0000000..124a918 --- /dev/null +++ b/velox-docs/pages/deploy-self-hosted.md @@ -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 +``` diff --git a/velox-docs/pages/deploy-vercel.md b/velox-docs/pages/deploy-vercel.md new file mode 100644 index 0000000..4823677 --- /dev/null +++ b/velox-docs/pages/deploy-vercel.md @@ -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 3–5× 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' }); +``` diff --git a/velox-docs/pages/guide-auth.md b/velox-docs/pages/guide-auth.md new file mode 100644 index 0000000..fa10365 --- /dev/null +++ b/velox-docs/pages/guide-auth.md @@ -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(); +}); +``` diff --git a/velox-docs/pages/guide-database.md b/velox-docs/pages/guide-database.md new file mode 100644 index 0000000..6f7d9c7 --- /dev/null +++ b/velox-docs/pages/guide-database.md @@ -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`; +``` diff --git a/velox-docs/pages/guide-i18n.md b/velox-docs/pages/guide-i18n.md new file mode 100644 index 0000000..b9572a8 --- /dev/null +++ b/velox-docs/pages/guide-i18n.md @@ -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); +``` diff --git a/velox-docs/pages/guide-performance.md b/velox-docs/pages/guide-performance.md new file mode 100644 index 0000000..55bc9b7 --- /dev/null +++ b/velox-docs/pages/guide-performance.md @@ -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, + }); +} +``` diff --git a/velox-docs/pages/guide-testing.md b/velox-docs/pages/guide-testing.md new file mode 100644 index 0000000..2fa68a5 --- /dev/null +++ b/velox-docs/pages/guide-testing.md @@ -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 +``` diff --git a/velox-docs/pages/index.md b/velox-docs/pages/index.md new file mode 100644 index 0000000..5452ef5 --- /dev/null +++ b/velox-docs/pages/index.md @@ -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. diff --git a/velox-docs/pages/installation.md b/velox-docs/pages/installation.md new file mode 100644 index 0000000..7e79d67 --- /dev/null +++ b/velox-docs/pages/installation.md @@ -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. diff --git a/velox-docs/pages/layouts.md b/velox-docs/pages/layouts.md new file mode 100644 index 0000000..d4128c6 --- /dev/null +++ b/velox-docs/pages/layouts.md @@ -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 + + + + +
+
+ +
+