Frontend Performance

React Optimization

// 1. Memoization with React.memo
const ExpensiveComponent = React.memo(({ data }) => {
  return <div>{/* Render data */}</div>;
});

// Custom comparison function
const areEqual = (prevProps, nextProps) => {
  return prevProps.id === nextProps.id;
};
const MemoizedComponent = React.memo(Component, areEqual);

// 2. useMemo for expensive calculations
function DataTable({ items, filter }) {
  const filteredItems = useMemo(() => {
    return items.filter(item => item.name.includes(filter));
  }, [items, filter]); // Only recalculate when items or filter change

  return <Table data={filteredItems} />;
}

// 3. useCallback for stable function references
function ParentComponent() {
  const [count, setCount] = useState(0);

  // Without useCallback: new function on every render
  // const handleClick = () => setCount(c => c + 1);

  // With useCallback: stable reference
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return <ChildComponent onClick={handleClick} />;
}

// 4. Lazy loading components
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

// 5. Virtualization for long lists
import { FixedSizeList } from 'react-window';

function VirtualizedList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>{items[index].name}</div>
  );

  return (
    <FixedSizeList
      height={400}
      width="100%"
      itemCount={items.length}
      itemSize={50}
    >
      {Row}
    </FixedSizeList>
  );
}

Avoiding Re-renders

// Common causes of unnecessary re-renders:

// 1. Inline objects/arrays (new reference every render)
// Bad
<Child style={{ color: 'red' }} />
<Child items={[1, 2, 3]} />

// Good - define outside or use useMemo
const style = { color: 'red' };
const items = [1, 2, 3];
<Child style={style} />
<Child items={items} />

// 2. Inline functions
// Bad
<Button onClick={() => handleClick(id)} />

// Good
const handleButtonClick = useCallback(() => handleClick(id), [id]);
<Button onClick={handleButtonClick} />

// 3. State updates that don't change value
// Bad - creates new object every time
setUser({ ...user }); // Even if nothing changed

// Good - only update when needed
if (newValue !== user.name) {
  setUser({ ...user, name: newValue });
}

// 4. Context value changes
// Bad - new object every render
<Context.Provider value={{ user, setUser }}>

// Good - memoize the value
const contextValue = useMemo(() => ({ user, setUser }), [user]);
<Context.Provider value={contextValue}>

Code Splitting

// Route-based splitting
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

// Component-based splitting
const HeavyChart = lazy(() => import('./components/HeavyChart'));

function Analytics() {
  const [showChart, setShowChart] = useState(false);

  return (
    <div>
      <button onClick={() => setShowChart(true)}>Show Chart</button>
      {showChart && (
        <Suspense fallback={<ChartSkeleton />}>
          <HeavyChart />
        </Suspense>
      )}
    </div>
  );
}

// Named exports with lazy
const { Modal } = lazy(() =>
  import('./components/Modal').then(module => ({
    default: module.Modal
  }))
);

// Preloading on hover/focus
const loadDashboard = () => import('./pages/Dashboard');

function NavLink() {
  return (
    <Link
      to="/dashboard"
      onMouseEnter={loadDashboard}
      onFocus={loadDashboard}
    >
      Dashboard
    </Link>
  );
}

Image Optimization

// 1. Lazy loading images
<img src="image.jpg" loading="lazy" alt="Description" />

// 2. Responsive images
<img
  src="image-800.jpg"
  srcSet="
    image-400.jpg 400w,
    image-800.jpg 800w,
    image-1200.jpg 1200w
  "
  sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
  alt="Description"
/>

// 3. Modern formats with fallback
<picture>
  <source srcSet="image.avif" type="image/avif" />
  <source srcSet="image.webp" type="image/webp" />
  <img src="image.jpg" alt="Description" />
</picture>

// 4. React image component with loading state
function OptimizedImage({ src, alt, placeholder }) {
  const [loaded, setLoaded] = useState(false);
  const [error, setError] = useState(false);

  return (
    <div className="image-container">
      {!loaded && <div className="placeholder">{placeholder}</div>}
      {error && <div className="error">Failed to load</div>}
      <img
        src={src}
        alt={alt}
        loading="lazy"
        onLoad={() => setLoaded(true)}
        onError={() => setError(true)}
        style={{ opacity: loaded ? 1 : 0 }}
      />
    </div>
  );
}

// 5. Next.js Image component (automatic optimization)
import Image from 'next/image';

<Image
  src="/hero.jpg"
  width={1200}
  height={600}
  alt="Hero"
  priority  // Preload above-the-fold images
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,..."
/>

Backend Performance

Database Optimization

// 1. Indexing
// Create indexes for frequently queried fields
db.users.createIndex({ email: 1 });
db.orders.createIndex({ customerId: 1, createdAt: -1 });
db.products.createIndex({ category: 1, price: 1 });

// Compound index for common query patterns
db.orders.createIndex({ status: 1, createdAt: -1 });

// 2. Query optimization
// Bad - fetches all fields
const users = await User.find({ status: 'active' });

// Good - select only needed fields
const users = await User.find({ status: 'active' })
  .select('name email')
  .lean(); // Returns plain objects (faster)

// 3. Pagination
// Bad - skip is slow for large offsets
const users = await User.find().skip(10000).limit(20);

// Good - cursor-based pagination
const users = await User.find({ _id: { $gt: lastId } })
  .sort({ _id: 1 })
  .limit(20);

// 4. Avoid N+1 queries
// Bad - N+1 problem
const orders = await Order.find();
for (const order of orders) {
  order.user = await User.findById(order.userId);
}

// Good - use populate or aggregation
const orders = await Order.find().populate('userId', 'name email');

// Or with $lookup
const orders = await Order.aggregate([
  { $lookup: {
    from: 'users',
    localField: 'userId',
    foreignField: '_id',
    as: 'user'
  }},
  { $unwind: '$user' }
]);

// 5. Batch operations
// Bad - individual inserts
for (const item of items) {
  await Item.create(item);
}

// Good - bulk insert
await Item.insertMany(items);

// Bulk updates
await Item.bulkWrite([
  { updateOne: { filter: { _id: id1 }, update: { $set: { status: 'active' } } } },
  { updateOne: { filter: { _id: id2 }, update: { $set: { status: 'inactive' } } } },
]);

Caching Strategies

// Redis caching
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);

// Cache-aside pattern
async function getUser(userId) {
  const cacheKey = `user:${userId}`;

  // Check cache first
  const cached = await redis.get(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }

  // Fetch from database
  const user = await User.findById(userId).lean();

  // Store in cache
  await redis.setex(cacheKey, 3600, JSON.stringify(user)); // 1 hour TTL

  return user;
}

// Invalidate cache on update
async function updateUser(userId, data) {
  await User.findByIdAndUpdate(userId, data);
  await redis.del(`user:${userId}`);
}

// Cache wrapper utility
function withCache(keyFn, ttl = 3600) {
  return function(target, propertyKey, descriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = async function(...args) {
      const key = keyFn(...args);
      const cached = await redis.get(key);

      if (cached) {
        return JSON.parse(cached);
      }

      const result = await originalMethod.apply(this, args);
      await redis.setex(key, ttl, JSON.stringify(result));

      return result;
    };

    return descriptor;
  };
}

// HTTP caching headers
app.get('/api/products', async (req, res) => {
  const products = await Product.find();

  res.set({
    'Cache-Control': 'public, max-age=300', // 5 minutes
    'ETag': generateETag(products),
  });

  res.json(products);
});

// Conditional request handling
app.get('/api/products/:id', async (req, res) => {
  const product = await Product.findById(req.params.id);
  const etag = `"${product.updatedAt.getTime()}"`;

  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end(); // Not Modified
  }

  res.set('ETag', etag);
  res.json(product);
});

API Response Optimization

// 1. Compression
const compression = require('compression');
app.use(compression());

// 2. Response streaming
app.get('/api/export', async (req, res) => {
  res.setHeader('Content-Type', 'application/json');

  const cursor = Order.find().cursor();

  res.write('[');
  let first = true;

  for await (const doc of cursor) {
    if (!first) res.write(',');
    res.write(JSON.stringify(doc));
    first = false;
  }

  res.write(']');
  res.end();
});

// 3. Field filtering
app.get('/api/users', async (req, res) => {
  const fields = req.query.fields?.split(',') || ['name', 'email'];
  const projection = fields.reduce((acc, f) => ({ ...acc, [f]: 1 }), {});

  const users = await User.find({}, projection).lean();
  res.json(users);
});

// 4. Sparse fieldsets (GraphQL-like)
// ?fields[user]=name,email&fields[orders]=id,total
function parseFields(query) {
  const fields = {};
  for (const [key, value] of Object.entries(query)) {
    if (key.startsWith('fields[')) {
      const entity = key.match(/fields\[(\w+)\]/)[1];
      fields[entity] = value.split(',');
    }
  }
  return fields;
}

Network Optimization

HTTP/2 and Preloading

// Preload critical resources
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/api/user" as="fetch" crossorigin>
<link rel="preload" href="/hero.webp" as="image">

// Prefetch for next navigation
<link rel="prefetch" href="/dashboard">
<link rel="prefetch" href="/api/dashboard-data">

// DNS prefetch for external domains
<link rel="dns-prefetch" href="https://api.example.com">
<link rel="preconnect" href="https://api.example.com">

// React Helmet for dynamic preloading
import { Helmet } from 'react-helmet';

function ProductPage({ productId }) {
  return (
    <>
      <Helmet>
        <link rel="preload" href={`/api/products/${productId}`} as="fetch" />
      </Helmet>
      {/* Component content */}
    </>
  );
}

Bundle Optimization

// webpack.config.js optimizations
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
        common: {
          minChunks: 2,
          priority: -10,
          reuseExistingChunk: true,
        },
      },
    },
    runtimeChunk: 'single',
    moduleIds: 'deterministic',
  },
};

// Analyze bundle size
// npm install -D webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
    }),
  ],
};

// Tree shaking - use ES modules
// Bad - imports entire library
import _ from 'lodash';
_.map(items, fn);

// Good - import only what you need
import map from 'lodash/map';
map(items, fn);

// Or use lodash-es
import { map } from 'lodash-es';

Measuring Performance

Core Web Vitals

// Measure Core Web Vitals
import { getCLS, getFID, getLCP, getFCP, getTTFB } from 'web-vitals';

function sendToAnalytics({ name, value, id }) {
  // Send to your analytics service
  fetch('/api/analytics', {
    method: 'POST',
    body: JSON.stringify({ name, value, id }),
  });
}

// Largest Contentful Paint (LCP) - < 2.5s
getLCP(sendToAnalytics);

// First Input Delay (FID) - < 100ms
getFID(sendToAnalytics);

// Cumulative Layout Shift (CLS) - < 0.1
getCLS(sendToAnalytics);

// First Contentful Paint (FCP)
getFCP(sendToAnalytics);

// Time to First Byte (TTFB)
getTTFB(sendToAnalytics);

// Custom performance marks
performance.mark('app-start');
// ... app initialization
performance.mark('app-ready');
performance.measure('app-initialization', 'app-start', 'app-ready');

const measure = performance.getEntriesByName('app-initialization')[0];
console.log(`App initialization: ${measure.duration}ms`);

React DevTools Profiler

// Enable Profiler in production build
// In your build config:
// REACT_APP_PROFILER=true

// Programmatic profiling
import { Profiler } from 'react';

function onRenderCallback(
  id, // Component ID
  phase, // "mount" or "update"
  actualDuration, // Time spent rendering
  baseDuration, // Estimated time without memoization
  startTime, // When React started rendering
  commitTime, // When React committed this update
) {
  console.log(`${id} ${phase}: ${actualDuration.toFixed(2)}ms`);

  // Send to monitoring service
  if (actualDuration > 16) { // More than one frame
    reportSlowRender({ id, phase, actualDuration });
  }
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MainContent />
    </Profiler>
  );
}

Server Performance Monitoring

// Response time middleware
app.use((req, res, next) => {
  const start = process.hrtime();

  res.on('finish', () => {
    const [seconds, nanoseconds] = process.hrtime(start);
    const duration = seconds * 1000 + nanoseconds / 1000000;

    console.log({
      method: req.method,
      path: req.path,
      status: res.statusCode,
      duration: `${duration.toFixed(2)}ms`,
    });

    // Alert on slow responses
    if (duration > 1000) {
      alertSlowEndpoint(req.path, duration);
    }
  });

  next();
});

// Memory monitoring
setInterval(() => {
  const usage = process.memoryUsage();
  console.log({
    heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)}MB`,
    external: `${Math.round(usage.external / 1024 / 1024)}MB`,
  });

  // Alert on high memory usage
  if (usage.heapUsed > 500 * 1024 * 1024) { // 500MB
    alertHighMemory(usage);
  }
}, 60000);

// Database query logging
mongoose.set('debug', (collectionName, method, query, doc) => {
  console.log(`${collectionName}.${method}`, JSON.stringify(query));
});

Performance Checklist

// Frontend Checklist
□ Code splitting and lazy loading
□ Image optimization (format, size, lazy loading)
□ Memoization (React.memo, useMemo, useCallback)
□ Virtualization for long lists
□ Bundle size optimization
□ Critical CSS inlined
□ Fonts optimized (preload, display: swap)
□ Service worker for caching

// Backend Checklist
□ Database indexes on queried fields
□ Query optimization (select, lean, pagination)
□ Response caching (Redis, HTTP headers)
□ Compression enabled
□ Connection pooling
□ Async/non-blocking operations
□ Rate limiting

// Network Checklist
□ HTTP/2 enabled
□ Gzip/Brotli compression
□ CDN for static assets
□ Preload critical resources
□ Minimize third-party scripts
□ Efficient API design (batch, pagination)

Key Takeaways

  • Use React.memo, useMemo, and useCallback to prevent unnecessary re-renders
  • Implement code splitting and lazy loading for faster initial load
  • Optimize images with modern formats, responsive sizes, and lazy loading
  • Add database indexes and use projection to optimize queries
  • Implement caching at multiple levels (Redis, HTTP, CDN)
  • Monitor Core Web Vitals (LCP, FID, CLS)
  • Use virtualization for rendering large lists
  • Profile and measure before optimizing