6.6 KiB
| title | sort | section-id | keywords | description | language |
|---|---|---|---|---|---|
| Performance | 140 | guides | performance, code splitting, lazy loading, caching, optimisation, Core Web Vitals | Performance optimisation strategies for Velox apps — code splitting, lazy loading, and caching | en |
Performance
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:
// 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:
// 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:
<!-- 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:
assets: {
imageOptimisation: {
enabled: true,
formats: ['webp', 'avif'],
quality: 85,
maxWidth: 2000,
},
},
Use the <Image> component for automatic width/height and format negotiation:
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:
// 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:
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:
// 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:
-- 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:
// velox.config.ts
export default defineConfig({
build: {
target: 'edge',
},
});
Edge-compatible routes must use Web APIs only:
// ✅ 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:
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:
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:
/* 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 */
}
// 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:
// 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,
});
}
