Skip to content

Performance Guide

Optimize and monitor Constellation's performance.

Core Web Vitals

Monitor these three key metrics:

1. Largest Contentful Paint (LCP)

  • Measures: Page load speed (when main content is visible)
  • Target: < 2.5 seconds
  • Tips:
  • Optimize images with Next.js Image component
  • Minimize JavaScript
  • Use Server Components for initial load
  • Implement code splitting

2. Interaction to Next Paint (INP)

  • Measures: Responsiveness to user interactions
  • Target: < 200 milliseconds
  • Tips:
  • Minimize JavaScript execution time
  • Defer non-critical JavaScript
  • Optimize event handlers
  • Use React.memo for expensive components

3. Cumulative Layout Shift (CLS)

  • Measures: Visual stability (unexpected layout changes)
  • Target: < 0.1
  • Tips:
  • Set fixed sizes for images and videos
  • Avoid inserting content above existing content
  • Use transforms instead of changing dimensions

Measurement Tools

Chrome DevTools

# Open DevTools (F12)
# Go to Performance tab
# Record page load
# Analyze results

Lighthouse

# Built into Chrome DevTools
# Generates performance report
# Provides specific recommendations
# Target: 90+ score

Web Vitals

// src/lib/web-vitals.ts
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

export function reportWebVitals(metric: any) {
  console.log(metric);
  // Send to analytics service
}

getCLS(reportWebVitals);
getFID(reportWebVitals);
getFCP(reportWebVitals);
getLCP(reportWebVitals);
getTTFB(reportWebVitals);

Optimization Strategies

Code Splitting

// โœ… GOOD - Automatic route-based splitting
// Each route gets its own bundle

// โœ… GOOD - Manual component splitting
import dynamic from 'next/dynamic';

const HeavyEditor = dynamic(() => import('./Editor'), {
  loading: () => <div>Loading editor...</div>,
});

export function Dashboard() {
  return (
    <>
      <Sidebar />
      <HeavyEditor />
    </>
  );
}

Image Optimization

import Image from 'next/image';

// โœ… GOOD - Optimized with Next.js Image
<Image
  src="/image.jpg"
  alt="Description"
  width={800}
  height={600}
  priority={false}
  loading="lazy"
  placeholder="blur"
  blurDataURL="data:image/jpeg..."
/>

// โŒ BAD - Regular img tag
<img src="/image.jpg" />

Component Memoization

import { memo, useMemo, useCallback } from 'react';

// โœ… GOOD - Memoized component
export const ListItem = memo(function ListItem({ item, onSelect }) {
  return (
    <div onClick={() => onSelect(item)}>
      {item.name}
    </div>
  );
});

// โœ… GOOD - Memoized callback
const handleSelect = useCallback((item) => {
  setSelected(item);
}, []);

// โœ… GOOD - Memoized expensive computation
const total = useMemo(() => {
  return items.reduce((sum, item) => sum + item.price, 0);
}, [items]);

Bundle Analysis

# Analyze bundle size
npm run build:analyze

# Look for:
# - Large third-party libraries
# - Duplicate dependencies
# - Unused code

Server Components

// โœ… GOOD - Server Component (no JavaScript sent)
export default async function Dashboard() {
  const data = await fetchData();
  return <div>{data}</div>;
}

// โŒ BAD - Client Component (sends all JS to browser)
'use client';
export default function Dashboard() {
  const [data, setData] = useState();
  useEffect(() => {
    fetchData().then(setData);
  }, []);
  return <div>{data}</div>;
}

Caching Strategies

Browser Caching

// next.config.js
module.exports = {
  headers: async () => {
    return [
      {
        source: '/images/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
    ];
  },
};

Data Caching

// Use React cache for per-request deduplication
import { cache } from 'react';

export const getUser = cache(async (id: string) => {
  return apiClient.get(`/users/${id}`);
});

// Multiple calls in same request only hit API once
const user1 = await getUser('123');
const user2 = await getUser('123'); // Cached result

Query String Caching

// Avoid query strings for static assets
// โŒ BAD
<img src="/image.jpg?v=1" />

// โœ… GOOD
<Image src="/image-v1.jpg" />

Monitoring

Monitoring Setup

// src/lib/monitoring.ts
export function trackPerformance() {
  // Report to analytics service
  const metrics = {
    FCP: 0,
    LCP: 0,
    INP: 0,
    CLS: 0,
  };

  fetch('/api/metrics', {
    method: 'POST',
    body: JSON.stringify(metrics),
  });
}

// Call on page load
useEffect(() => {
  trackPerformance();
}, []);

Analytics Dashboard

Track: - Page load times - Core Web Vitals - Error rates - User interactions - API response times


Database Query Optimization

N+1 Query Prevention

// โŒ BAD - N+1 queries
const workspaces = await db.workspace.findMany();
for (const workspace of workspaces) {
  const users = await db.user.findMany({
    where: { workspaceId: workspace.id },
  });
}

// โœ… GOOD - Single query with relation
const workspaces = await db.workspace.findMany({
  include: { users: true },
});

Query Indexing

// Add indexes to frequently queried fields
// In your database schema:
// CREATE INDEX idx_workspace_user ON workspace(user_id);
// CREATE INDEX idx_document_workspace ON document(workspace_id);

API Optimization

Response Compression

// next.config.js
module.exports = {
  compress: true, // Enable gzip compression
};

Request Batching

// Group multiple API calls
async function fetchDashboardData() {
  const [user, workspaces, notifications] = await Promise.all([
    apiClient.get('/user'),
    apiClient.get('/workspaces'),
    apiClient.get('/notifications'),
  ]);

  return { user, workspaces, notifications };
}

Pagination

// Load data in chunks, not all at once
const { data: items, total } = await apiClient.get('/items', {
  params: { page: 1, limit: 20 },
});

Lazy Loading

Route-Based

// Automatically lazy loaded by Next.js
// src/app/heavy-feature/page.tsx
// Only loads when user navigates to route

Component-Based

const Editor = dynamic(() => import('./Editor'), {
  loading: () => <Skeleton />,
});

Image Lazy Loading

<Image
  src="/image.jpg"
  loading="lazy" // Load only when visible
  alt="Description"
/>

Performance Checklist

Development

  • [ ] Use Chrome DevTools to profile code
  • [ ] Check Console for errors/warnings
  • [ ] Monitor Network tab for slow requests
  • [ ] Test on slow network (DevTools)
  • [ ] Test on slow devices (DevTools)

Build

  • [ ] bun run build succeeds
  • [ ] npm run build:analyze shows reasonable size
  • [ ] No unused dependencies
  • [ ] Code splitting working

Before Deploy

  • [ ] Lighthouse score > 90
  • [ ] Bundle size < 200KB (gzipped)
  • [ ] No performance warnings
  • [ ] Mobile performance tested
  • [ ] Real device testing done

Production

  • [ ] Monitor Core Web Vitals
  • [ ] Track error rates
  • [ ] Monitor API response times
  • [ ] Set up alerts for degradation
  • [ ] Regular performance reviews

Common Performance Issues

Slow Initial Load

Causes: - Large JavaScript bundle - Unoptimized images - Render-blocking resources - Slow API requests

Solutions: - Code split and defer non-critical JS - Optimize images - Use async/defer attributes - Implement caching

Janky Interactions

Causes: - Expensive computations in event handlers - Too many re-renders - Blocking main thread

Solutions: - Use requestAnimationFrame - Memoize components/functions - Move heavy work to Web Workers - Debounce expensive operations

Memory Leaks

Causes: - Event listeners not removed - Timers not cleared - Circular references

Solutions: - Clean up in useEffect return - Clear timers - Remove event listeners


Tools

Bundle Analyzer

npm run build:analyze

Chrome DevTools

  • Performance tab
  • Lighthouse
  • Network tab
  • Coverage tab

Web Vitals Library

npm install web-vitals


Last Updated: January 2026
Tools: web.dev/performance