Performance
Performance
Section titled “Performance”ArcAide is designed with performance as a core consideration, leveraging modern web technologies and optimization strategies to provide excellent user experience.
Frontend Performance
Section titled “Frontend Performance”Astro Optimization
Section titled “Astro Optimization”Astro provides excellent performance out of the box:
Island Architecture
Section titled “Island Architecture”// Only interactive components are hydratedexport default function CampaignPage({ campaign }) { return ( <Layout> {/* Static content - no JavaScript */} <h1>{campaign.name}</h1> <p>{campaign.description}</p>
{/* Interactive components - hydrated on demand */} <ArcEditor client:visible campaign={campaign} /> <SearchBar client:idle /> <SideBar client:load /> </Layout> )}
Selective Hydration Strategies
Section titled “Selective Hydration Strategies”// Hydration strategies for different use cases{/* Critical interactive content */}<Navigation client:load />
{/* Above-the-fold interactivity */}<ModeToggle client:idle />
{/* Scroll-triggered content */}<ArcList client:visible />
{/* Media query responsive */}<MobileSidebar client:media="(max-width: 768px)" />
{/* Only when needed */}<AdvancedEditor client:only="react" />
Bundle Optimization
Section titled “Bundle Optimization”Code Splitting
Section titled “Code Splitting”// Automatic route-based splittingconst pages = { '/dashboard': () => import('./pages/dashboard.astro'), '/campaign/[slug]': () => import('./pages/campaign/[slug].astro'), '/arc/[slug]': () => import('./pages/arc/[slug].astro'),}
// Component-level splitting for large dependenciesconst SlateEditor = lazy(() => import('./components/slate-editor').then((module) => ({ default: module.SlateEditor, })))
// Library splitting for optimal cachingconst externals = { 'react': 'React', 'react-dom': 'ReactDOM', '@tanstack/react-query': 'ReactQuery',}
Tree Shaking
Section titled “Tree Shaking”// Import only what's neededimport { useQuery, useMutation } from '@tanstack/react-query'import { create } from 'zustand'import { eq, and } from 'drizzle-orm'
// Avoid importing entire libraries// ❌ import * as lodash from 'lodash'// ✅ import { debounce } from 'lodash-es'
Asset Optimization
Section titled “Asset Optimization”Image Optimization
Section titled “Image Optimization”---// Astro's built-in image optimizationimport { Image } from 'astro:assets'import parchmentBg from '../assets/parchmentBackground.jpg'---
<!-- Optimized images with multiple formats --><Image src={parchmentBg} alt='Parchment background' width={1920} height={1080} format='webp' quality={80} loading='lazy'/>
<!-- Responsive images --><Image src={heroImage} alt='Campaign hero' widths={[240, 540, 720, 1600]} sizes='(max-width: 360px) 240px, (max-width: 720px) 540px, (max-width: 1600px) 720px, 1600px'/>
Font Loading Strategy
Section titled “Font Loading Strategy”/* Critical fonts preloaded */@font-face { font-family: 'Bookinsanity'; src: url('/fonts/Bookinsanity.otf') format('opentype'); font-display: swap; font-weight: normal; font-style: normal;}
/* Non-critical fonts loaded progressively */@font-face { font-family: 'Nodesto Caps Condensed'; src: url('/fonts/NodestoCapsCondensed.otf') format('opentype'); font-display: optional; font-weight: normal; font-style: normal;}
Runtime Performance
Section titled “Runtime Performance”React Query Optimization
Section titled “React Query Optimization”// Efficient query key strategiesconst queryKeys = { campaigns: () => ['campaigns'], campaign: (slug: string) => ['campaign', slug], arcs: (campaignSlug: string) => ['arcs', campaignSlug], arc: (campaignSlug: string, arcSlug: string) => [ 'arc', campaignSlug, arcSlug, ],}
// Optimized data fetchingexport function useArcs(campaignSlug: string) { return useQuery({ queryKey: queryKeys.arcs(campaignSlug), queryFn: () => fetchArcs(campaignSlug), staleTime: 5 * 60 * 1000, // 5 minutes cacheTime: 10 * 60 * 1000, // 10 minutes // Only refetch when data is actually stale refetchOnMount: false, refetchOnWindowFocus: false, })}
// Intelligent prefetchingexport function usePrefetchArc(campaignSlug: string, arcSlug: string) { const queryClient = useQueryClient()
return useCallback(() => { queryClient.prefetchQuery({ queryKey: queryKeys.arc(campaignSlug, arcSlug), queryFn: () => fetchArc(campaignSlug, arcSlug), staleTime: 5 * 60 * 1000, }) }, [queryClient, campaignSlug, arcSlug])}
Component Optimization
Section titled “Component Optimization”// Memoization for expensive computationsexport const ArcViewer = memo(function ArcViewer({ arc }: { arc: Arc }) { const consolidatedContent = useMemo(() => { return consolidateArcContent(arc) }, [arc.hook, arc.protagonist, arc.antagonist, arc.problem, arc.key, arc.outcome])
return <SlateViewer content={consolidatedContent} />})
// Virtualization for large listsexport function ThingsList({ campaignSlug }: { campaignSlug: string }) { const { data: things } = useThings(campaignSlug, { fetchAll: true })
const Row = useCallback(({ index, style }) => ( <div style={style}> <ThingCard thing={things[index]} /> </div> ), [things])
return ( <FixedSizeList height={600} itemCount={things?.length || 0} itemSize={120} width="100%" > {Row} </FixedSizeList> )}
Backend Performance
Section titled “Backend Performance”Database Optimization
Section titled “Database Optimization”Query Optimization
Section titled “Query Optimization”// Efficient database queries with proper indexingexport async function getArcsWithThings(campaignId: number) { // Single query with joins instead of N+1 queries return await db .select({ arc: arcs, things: sql<Thing[]>` COALESCE( json_group_array( json_object( 'id', ${things.id}, 'slug', ${things.slug}, 'name', ${things.name}, 'typeId', ${things.typeId} ) ) FILTER (WHERE ${things.id} IS NOT NULL), '[]' ) `.as('things'), }) .from(arcs) .leftJoin(arcThings, eq(arcs.id, arcThings.arcId)) .leftJoin(things, eq(arcThings.thingId, things.id)) .where(eq(arcs.campaignId, campaignId)) .groupBy(arcs.id) .orderBy(desc(arcs.updatedAt))}
// Pagination for large datasetsexport async function getThingsPaginated( campaignId: number, page: number = 1, limit: number = 20) { const offset = (page - 1) * limit
const [things, totalCount] = await Promise.all([ db .select() .from(things) .where(eq(things.campaignId, campaignId)) .orderBy(desc(things.updatedAt)) .limit(limit) .offset(offset),
db .select({ count: sql<number>`count(*)` }) .from(things) .where(eq(things.campaignId, campaignId)) .then((result) => result[0].count), ])
return { things, pagination: { page, limit, total: totalCount, totalPages: Math.ceil(totalCount / limit), }, }}
Full-Text Search Optimization
Section titled “Full-Text Search Optimization”-- Optimized FTS queries with rankingSELECT arcs.*, arcs_fts.rankFROM arcs_ftsJOIN arcs ON arcs.id = arcs_fts.rowidWHERE arcs_fts MATCH ? AND arcs.campaign_id = ?ORDER BY arcs_fts.rank, arcs.updated_at DESCLIMIT 20;
-- Combined search across multiple tablesSELECT 'arc' as type, arcs.id, arcs.slug, arcs.name, snippet(arcs_fts, -1, '<mark>', '</mark>', '...', 32) as excerpt, arcs_fts.rankFROM arcs_ftsJOIN arcs ON arcs.id = arcs_fts.rowidWHERE arcs_fts MATCH ? AND arcs.campaign_id = ?
UNION ALL
SELECT 'thing' as type, things.id, things.slug, things.name, snippet(things_fts, -1, '<mark>', '</mark>', '...', 32) as excerpt, things_fts.rankFROM things_ftsJOIN things ON things.id = things_fts.rowidWHERE things_fts MATCH ? AND things.campaign_id = ?
ORDER BY rank, type, nameLIMIT 50;
API Performance
Section titled “API Performance”Response Optimization
Section titled “Response Optimization”// Selective field loadingexport async function getArcsList(campaignId: number, lightweight = false) { const selectFields = lightweight ? { id: arcs.id, slug: arcs.slug, name: arcs.name, updatedAt: arcs.updatedAt, } : arcs // Full object
return await db .select(selectFields) .from(arcs) .where(eq(arcs.campaignId, campaignId)) .orderBy(desc(arcs.updatedAt))}
// Response compressionexport function compressResponse(data: any): Response { const json = JSON.stringify(data)
return new Response(json, { headers: { 'Content-Type': 'application/json', 'Content-Encoding': 'gzip', 'Cache-Control': 'public, max-age=300', // 5 minutes }, })}
Caching Strategy
Section titled “Caching Strategy”// Memory cache for frequently accessed dataconst cache = new Map<string, { data: any; expires: number }>()
export function getCached<T>( key: string, fetcher: () => Promise<T>, ttl: number = 5 * 60 * 1000 // 5 minutes): Promise<T> { const cached = cache.get(key)
if (cached && cached.expires > Date.now()) { return Promise.resolve(cached.data) }
return fetcher().then((data) => { cache.set(key, { data, expires: Date.now() + ttl, }) return data })}
// Usage in API routesexport async function GET({ params }) { const cacheKey = `campaign:${params.slug}`
const campaign = await getCached( cacheKey, () => fetchCampaign(params.slug), 10 * 60 * 1000 // 10 minutes for campaigns )
return compressResponse(campaign)}
Monitoring and Metrics
Section titled “Monitoring and Metrics”Performance Monitoring
Section titled “Performance Monitoring”// Core Web Vitals trackingexport function trackWebVitals() { // Largest Contentful Paint new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.entryType === 'largest-contentful-paint') { analytics.track('LCP', { value: entry.startTime }) } } }).observe({ entryTypes: ['largest-contentful-paint'] })
// First Input Delay new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.entryType === 'first-input') { analytics.track('FID', { value: entry.processingStart - entry.startTime, }) } } }).observe({ entryTypes: ['first-input'] })
// Cumulative Layout Shift let clsValue = 0 new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (!entry.hadRecentInput) { clsValue += entry.value } } analytics.track('CLS', { value: clsValue }) }).observe({ entryTypes: ['layout-shift'] })}
// Database query performanceexport async function withQueryTiming<T>( name: string, query: () => Promise<T>): Promise<T> { const start = performance.now()
try { const result = await query() const duration = performance.now() - start
// Log slow queries if (duration > 100) { console.warn(`Slow query: ${name} took ${duration}ms`) }
analytics.track('database_query', { name, duration, success: true, })
return result } catch (error) { const duration = performance.now() - start
analytics.track('database_query', { name, duration, success: false, error: error.message, })
throw error }}
Bundle Analysis
Section titled “Bundle Analysis”// Bundle size monitoringexport function analyzeBundles() { if (typeof window !== 'undefined') { // Track JavaScript bundle sizes performance.getEntriesByType('navigation').forEach((entry) => { analytics.track('bundle_size', { transferSize: entry.transferSize, encodedBodySize: entry.encodedBodySize, decodedBodySize: entry.decodedBodySize, }) })
// Track resource loading times performance.getEntriesByType('resource').forEach((entry) => { if (entry.name.includes('.js') || entry.name.includes('.css')) { analytics.track('resource_timing', { name: entry.name, duration: entry.duration, size: entry.transferSize, }) } }) }}
Performance Best Practices
Section titled “Performance Best Practices”Development Guidelines
Section titled “Development Guidelines”- Measure First - Always measure before optimizing
- Bundle Analysis - Regular bundle size monitoring
- Database Indexing - Proper indexes for query patterns
- Image Optimization - WebP format, responsive sizes
- Code Splitting - Route and component-level splitting
- Caching Strategy - Appropriate cache headers and strategies
Production Optimization Checklist
Section titled “Production Optimization Checklist”- Astro build optimization enabled
- Image optimization configured
- Font loading optimized
- Database indexes in place
- Query performance monitored
- Bundle sizes within targets
- Core Web Vitals tracking
- CDN configured for static assets
- Compression enabled
- Cache headers configured
Performance Targets
Section titled “Performance Targets”- First Contentful Paint: < 1.5s
- Largest Contentful Paint: < 2.5s
- First Input Delay: < 100ms
- Cumulative Layout Shift: < 0.1
- Database Queries: < 100ms average
- Bundle Size: < 250KB compressed
- Time to Interactive: < 3.5s
This comprehensive performance strategy ensures ArcAide delivers excellent user experience while maintaining scalability and efficiency.