--- 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' }; ---

{post.title}

By {post.author} on {post.publishedAt}

``` 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()); ---

Features

``` ### 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 }; ---

{post.title}

``` ## 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' }); ---
{products.map(p => )}
``` 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(null); const error = signal(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 (
{loading.value && } {error.value && } {stats.value && }
); } ``` ### 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 ; if (status === 'error') return

Error: {error.message}

; return (
    {data.map(p =>
  • {p.name}
  • )}
); } ``` ## 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 } }); ---

{fastData.title}

}>
``` The `` 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 ```