Skip to content

Performance Guide

Server Components vs Client Components

Next.js uses React Server Components by default. Understanding when to use each type is the most impactful performance decision you'll make.

Server Components (Default)

Components render on the server. The browser receives HTML, not JavaScript.

tsx
// This is a Server Component by default — no directive needed
export default function ProductList() {
  return (
    <div>
      <h1>Products</h1>
      {/* This component ships zero JavaScript to the browser */}
    </div>
  );
}

Use for: Static content, data fetching, layouts, pages, anything that doesn't need interactivity.

Client Components

Components render on the client. Add 'use client' at the top of the file.

tsx
'use client';

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}

Use for: Event handlers (onClick, onChange), useState, useEffect, browser APIs.

The Rule

Keep Client Components as small and specific as possible. Push 'use client' down to the leaf components that actually need interactivity.

// ✅ Good: Only the interactive part is a Client Component
<ProductPage>              {/* Server Component */}
  <ProductDetails />       {/* Server Component */}
  <AddToCartButton />      {/* Client Component */}
</ProductPage>

// ❌ Bad: Entire page is a Client Component because of one button
'use client'
<ProductPage>              {/* Everything ships as JavaScript */}
  <ProductDetails />
  <AddToCartButton />
</ProductPage>

Image Optimization

Use Next.js <Image> component instead of the HTML <img> tag. It automatically:

  • Serves images in modern formats (WebP, AVIF)
  • Lazy loads images below the fold
  • Prevents layout shift with required width/height
  • Generates responsive sizes
tsx
import Image from 'next/image';

// Local image
import heroImage from '@/public/hero.jpg';

export default function Hero() {
  return (
    <Image
      src={heroImage}
      alt="Hero banner showing the product in action"
      priority // Add for above-the-fold images (disables lazy loading)
      placeholder="blur" // Shows blurred version while loading
    />
  );
}

// Remote image (must configure domains in next.config.ts)
<Image src="https://example.com/photo.jpg" alt="Description" width={800} height={600} />;

Configuring Remote Images

typescript
// next.config.ts
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
      },
    ],
  },
};

Font Optimization

This project uses next/font/local to self-host fonts from app/fonts/ at build time — no runtime Google Fonts requests.

  • Eliminates layout shift with font-display: swap
  • Inlines font files in the production bundle

See the current setup in app/layout.tsx:

tsx
import localFont from 'next/font/local';

const inter = localFont({
  src: [{ path: './fonts/InterVariable.woff2', weight: '100 900', style: 'normal' }],
  variable: '--font-inter',
  display: 'swap',
});

const apfelGrotezk = localFont({
  src: [
    { path: './fonts/ApfelGrotezk-Regular.otf', weight: '400', style: 'normal' },
    // … additional weights
  ],
  variable: '--font-apfel-grotezk',
  display: 'swap',
});

Body text uses the Inter CSS variable via font-sans in globals.css. Display headings use Apfel Grotezk where styled in components.

Metadata for SEO

Next.js provides a built-in Metadata API. The project already has base metadata in app/layout.tsx. Add page-specific metadata:

tsx
// app/login/page.tsx
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Log In',
  description: 'Access your VideoSphere account.',
  openGraph: {
    title: 'Log In',
    description: 'Access your VideoSphere account.',
  },
};

Dynamic Imports (Code Splitting)

Use next/dynamic to load components only when needed:

tsx
import dynamic from 'next/dynamic';

// This component won't be included in the initial JavaScript bundle
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
  loading: () => <p>Loading chart...</p>,
});

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <HeavyChart />
    </div>
  );
}

When to Use Dynamic Imports

  • Large charting/visualization libraries
  • Rich text editors
  • Components behind modals or tabs (not visible initially)
  • Components that use browser-only APIs

React Suspense & Loading UI

Next.js uses loading.tsx files to show instant loading states:

app/
├── dashboard/
│   ├── page.tsx      // The page content
│   └── loading.tsx   // Shows while page is loading
tsx
// app/dashboard/loading.tsx
export default function Loading() {
  return <div className="animate-pulse">Loading dashboard...</div>;
}

For more granular loading states within a page:

tsx
import { Suspense } from 'react';

export default function Page() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<div>Loading stats...</div>}>
        <StatsSection />
      </Suspense>
      <Suspense fallback={<div>Loading chart...</div>}>
        <ChartSection />
      </Suspense>
    </div>
  );
}

Running Lighthouse

Lighthouse is a built-in Chrome tool that audits your site for performance, accessibility, SEO, and best practices.

How to Run

  1. Open your site in Chrome
  2. Open DevTools (F12 or Cmd+Opt+I)
  3. Go to the Lighthouse tab
  4. Select categories: Performance, Accessibility, Best Practices, SEO
  5. Select Navigation mode
  6. Click Analyze page load

Key Metrics

MetricTargetWhat It Measures
First Contentful Paint (FCP)< 1.8sTime until first content appears
Largest Contentful Paint (LCP)< 2.5sTime until largest content element loads
Cumulative Layout Shift (CLS)< 0.1Visual stability (things jumping around)
Total Blocking Time (TBT)< 200msTime the main thread is blocked

Quick Wins Checklist

  • [ ] Use Server Components wherever possible (no 'use client' unless needed)
  • [ ] Use next/image for all images
  • [ ] Add priority prop to above-the-fold images
  • [ ] Use next/font for all custom fonts
  • [ ] Add meaningful metadata to all pages
  • [ ] Use next/dynamic for heavy components
  • [ ] Run Lighthouse and aim for 90+ scores

Useful Resources