Performance Optimization
On this page 23
stx is built with performance in mind, leveraging Bun's native speed and optimized template processing. This guide covers performance characteristics, benchmark results, and optimization strategies.
Performance Benchmarks
stx has been extensively benchmarked against popular competitors. See our comprehensive benchmark results for detailed comparisons.
Key Performance Highlights
Template Engine Performance:
- Optimized for Bun runtime with native performance
- Laravel Blade syntax with comprehensive directive system
- Template caching enabled by default in production
- Streaming SSR for large pages
Framework Performance:
- 44.1% faster than VanillaJS in js-framework-benchmark
- 0.57ms geometric mean (VanillaJS: 1.02ms)
- Faster than VanillaJS in 8 of 9 operations
- Industry-leading optimizations
Markdown Parsing:
- 2.89x faster than markdown-it on small documents
- 1.96x faster on medium documents
- 1.45x faster on large documents
- Position-based parsing with flat token stream
HTML Sanitization:
- 77.93x faster than DOMPurify
- 1.70-1.99x faster than other competitors
- Fastest in all benchmark categories
YAML Parsing:
- 1.5-2.7x faster than js-yaml
- Native Bun YAML implementation
Build Optimization
Tree Shaking
stx automatically performs tree shaking to eliminate unused code:
// config/build.ts
import { defineConfig } from '@stacksjs/stx'
export default defineConfig({
build: {
// Enable aggressive tree shaking
treeshake: {
moduleSideEffects: false,
propertyReadSideEffects: false
},
// Minify output
minify: true,
// Generate sourcemaps
sourcemap: false,
// Optimize dependencies
optimizeDeps: {
include: [
// List dependencies to pre-bundle
]
}
}
})
Code Splitting
Automatically split your code into chunks:
// router.ts
export const routes = [
{
path: '/',
component: () => import('./pages/Home.stx')
},
{
path: '/dashboard',
component: () => import('./pages/Dashboard.stx'),
children: [
{
path: 'analytics',
component: () => import('./pages/dashboard/Analytics.stx')
}
]
}
]
Asset Optimization
Optimize assets during build:
// config/build.ts
export default defineConfig({
build: {
// Optimize assets
assetsInlineLimit: 4096, // Inline small files
cssCodeSplit: true, // Split CSS
rollupOptions: {
output: {
manualChunks: {
// Custom chunk splitting
vendor: ['lodash', 'axios'],
ui: ['./components/ui/']
}
}
}
}
})
Runtime Performance
Component Optimization
Use memo for expensive computations:
@component('')
interface Props {
data: any[]
sortBy: string
filterBy: string
}
// Memoize expensive computation
const sortedAndFilteredData = memo(
() => data
.filter(item => item[filterBy])
.sort((a, b) => a[sortBy].localeCompare(b[sortBy])),
[data, sortBy, filterBy]
)
<table>
<tbody>
@foreach(sortedAndFilteredDataasitem)
<tr>
<!-- Row content -->
</tr>
</tbody>
</table>
Lazy Loading
Lazy load components and routes:
@component('')
// Lazy load heavy components
const HeavyChart = lazy(() => import('./components/HeavyChart.stx'))
const DataGrid = lazy(() => import('./components/DataGrid.stx'))
let showChart = false
<divclass="">
<button@click="showChart=!showChart">
Toggle Chart
</button>
@if(showChart)
<Suspense>
<loading-spinner />
<HeavyChart />
<error-message />
<Suspense>
<DataGrid:data="gridData" />
</Suspense>
</div>
Virtual Scrolling
Handle large lists efficiently:
@component('')
interface Props {
items: any[]
itemHeight: number
visibleItems: number
}
const containerRef = ref<HTMLElement>()
let startIndex = 0
let scrollTop = 0
// Calculate visible items
const visibleData = computed(() => {
const start = Math.floor(scrollTop / itemHeight)
return items.slice(start, start + visibleItems)
})
function handleScroll(e: Event) {
scrollTop = (e.target as HTMLElement).scrollTop
}
<div
class="virtual-list"
ref="containerRef"
="handleScroll"
x-style="{
height: `${visibleItems * itemHeight}px`,
overflow: 'auto'
}"
>
<divx-style="
height: `${items.length * itemHeight}px`,
position: 'relative'
}">
@foreach(visibleDataasitemindex)
<divx-style="
position: 'absolute',
top: `${(startIndex + index) * itemHeight}px`,
height: `${itemHeight}px`
}">
<slot:item="item" />
</div>
</div>
</div>
<!-- Usage -->
<VirtualList
:items="largeDataset"
:itemHeight="50"
:visibleItems="10"
>
<template>
<divclass="">
{{ item.name }}
</div>
</template>
</VirtualList>
State Management
Optimize state updates:
@component('')
interface Todo {
id: number
text: string
completed: boolean
}
// Use shallowRef for large objects
const todos = shallowRef<Todo[]>([])
// Batch updates
function updateMultipleTodos(updates: Partial<Todo>[]) {
batch(() => {
updates.forEach(update => {
const todo = todos.value.find(t => t.id === update.id)
if (todo) {
Object.assign(todo, update)
}
})
})
}
<divclass="">
@foreach(todosastodo)
<todo-item
:key="todo.id"
:todo="todo"
/>
</div>
Caching Strategies
Component Caching
Cache expensive component renders:
@component('')
interface Props {
id: string
data: any
}
// Cache key generator
const cacheKey = computed(() =>
`${id}-${JSON.stringify(data)}`
)
(cacheKey)
<divclass="">
<!-- Expensive rendering -->
</div>
API Response Caching
Cache API responses:
// utils/cache.ts
interface CacheOptions {
ttl: number
maxSize: number
}
class APICache {
private cache = new Map()
private options: CacheOptions
constructor(options: CacheOptions) {
this.options = options
}
async get(key: string, fetcher: () => Promise<any>) {
const cached = this.cache.get(key)
if (cached && Date.now() - cached.timestamp < this.options.ttl) {
return cached.data
}
const data = await fetcher()
this.set(key, data)
return data
}
private set(key: string, data: any) {
if (this.cache.size >= this.options.maxSize) {
const oldestKey = this.cache.keys().next().value
this.cache.delete(oldestKey)
}
this.cache.set(key, {
data,
timestamp: Date.now()
})
}
}
// Usage
const apiCache = new APICache({
ttl: 5 * 60 * 1000, // 5 minutes
maxSize: 100
})
async function fetchData(id: string) {
return apiCache.get(
`data-${id}`,
() => api.fetch(id)
)
}
Monitoring and Profiling
Performance Monitoring
Add performance monitoring:
// utils/performance.ts
interface PerformanceMetric {
component: string
event: string
duration: number
timestamp: number
}
class PerformanceMonitor {
private metrics: PerformanceMetric[] = []
measure(component: string, event: string, callback: () => void) {
const start = performance.now()
callback()
const duration = performance.now() - start
this.metrics.push({
component,
event,
duration,
timestamp: Date.now()
})
// Log if duration exceeds threshold
if (duration > 16) { // 60fps threshold
console.warn(
`Performance warning: ${component} ${event} took ${duration}ms`
)
}
}
getMetrics() {
return this.metrics
}
}
// Usage in component
@component('MonitoredComponent')
@ts
const monitor = new PerformanceMonitor()
function expensiveOperation() {
monitor.measure(
'MonitoredComponent',
'expensiveOperation',
() => {
// Expensive work
}
)
}
@endts
@endcomponent
Template Performance Optimization
Enable Production Caching
stx caches compiled templates by default in production:
// stx.config.ts
export default {
cache: true, // Enabled by default
cachePath: '.stx/cache',
cacheVersion: '1.0.0'
}
Use Streaming for Large Pages
Enable streaming SSR for better perceived performance:
// stx.config.ts
export default {
streaming: {
enabled: true,
bufferSize: 16384,
strategy: 'auto', // 'auto', 'eager', or 'lazy'
timeout: 30000
}
}
Selective Directive Processing
Disable unused directives to reduce processing overhead:
// stx.config.ts
export default {
animation: { enabled: false }, // If not using animations
a11y: { enabled: false }, // If not using a11y features
seo: { enabled: false } // If SEO is handled elsewhere
}
Best Practices
-
Template Optimization
- Enable caching in production
- Use streaming for large pages
- Disable unused directive processors
- Minimize dynamic includes
-
Build Optimization
- Enable tree shaking
- Use code splitting
- Optimize assets
- Configure proper chunks
-
Component Design
- Use lazy loading
- Implement virtual scrolling
- Cache expensive computations
- Optimize re-renders
-
State Management
- Use shallow reactivity
- Batch updates
- Implement proper caching
- Monitor performance
-
Asset Loading
- Optimize images
- Use proper formats
- Implement lazy loading
- Configure caching
Performance Trade-offs
stx prioritizes developer experience and features over raw speed:
Advantages:
- Laravel Blade familiarity
- Comprehensive directive system
- Runtime flexibility
- Excellent Bun integration
Considerations:
- Slower than pre-compiled engines (Pug, Handlebars) for simple templates
- Rich directive processing adds overhead
- Still excellent for real-world applications (microseconds, not milliseconds)
See Benchmark Results for detailed comparisons.
Next Steps
- Review Benchmark Results
- Learn about Testing
- Explore Deployment
- Understand Security
- Check out Monitoring