perf hacks

Dynamic Imports

Load code only when needed. Modals, charts, and heavy components don't belong in your initial bundle.

Core Principle

Code that isn't needed immediately shouldn't be in your initial bundle. Dynamic imports load code only when it's actually needed.

The Basic Pattern

import dynamic from 'next/dynamic';

// Instead of: import HeavyChart from '@/components/HeavyChart';
const HeavyChart = dynamic(() => import('@/components/HeavyChart'));

Now HeavyChart loads in a separate chunk, downloaded only when rendered.

With Loading State

const HeavyChart = dynamic(
  () => import('@/components/HeavyChart'),
  {
    loading: () => <div className="h-64 animate-pulse bg-gray-200" />,
  }
);

Client-Only Components

Some components use window or document and can't render on the server:

const MapComponent = dynamic(
  () => import('@/components/Map'),
  { ssr: false }
);

ssr: false means no SEO for that content. Fine for maps and widgets. Bad for main content.

When to Use Dynamic Imports

Component TypeDynamic?Why
Modals/dialogsYesNot visible initially
Charts/graphsYesHeavy, usually below fold
Rich text editorsYesVery heavy, few users need it
NavigationNoNeeded immediately
Hero sectionNoAbove fold, needs SEO
Auth formsMaybeIf behind a click

Pattern: Lazy Modal

'use client';

import { useState } from 'react';
import dynamic from 'next/dynamic';

const SettingsModal = dynamic(() => import('./SettingsModal'));

export function SettingsButton() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>Settings</button>
      {open && <SettingsModal onClose={() => setOpen(false)} />}
    </>
  );
}

Modal code only downloads when button is clicked.

Pattern: Load on Scroll

'use client';

import { useEffect, useState, useRef } from 'react';
import dynamic from 'next/dynamic';

const HeavySection = dynamic(() => import('./HeavySection'));

export function LazySection() {
  const [visible, setVisible] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setVisible(true);
          observer.disconnect();
        }
      },
      { rootMargin: '100px' }
    );

    if (ref.current) observer.observe(ref.current);
    return () => observer.disconnect();
  }, []);

  return (
    <div ref={ref}>
      {visible ? <HeavySection /> : <Skeleton />}
    </div>
  );
}

Component loads 100px before it scrolls into view.

Pattern: Feature-Gated Loading

import dynamic from 'next/dynamic';

const AdminPanel = dynamic(() => import('./AdminPanel'));

export default function Dashboard({ user }) {
  return (
    <div>
      <MainContent />
      {user.role === 'admin' && <AdminPanel />}
    </div>
  );
}

Regular users never download admin code.

Pattern: Conditional Heavy Library

'use client';

import { useState } from 'react';

export function ExportButton({ data }) {
  const [exporting, setExporting] = useState(false);

  const handleExport = async () => {
    setExporting(true);

    // Import xlsx only when needed (500KB+ library)
    const XLSX = await import('xlsx');
    const worksheet = XLSX.utils.json_to_sheet(data);
    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, worksheet, 'Data');
    XLSX.writeFile(workbook, 'export.xlsx');

    setExporting(false);
  };

  return (
    <button onClick={handleExport} disabled={exporting}>
      {exporting ? 'Exporting...' : 'Export to Excel'}
    </button>
  );
}

The xlsx library only loads when someone actually exports.

Quick Checklist

Modals use dynamic imports Modal code shouldn't load until user clicks open.

Charts/graphs are lazy loaded Heavy visualization libraries load on demand.

Heavy libraries imported on-demand xlsx, pdf-lib, etc. load only when needed.

Below-fold sections load on scroll IntersectionObserver triggers loading before visible.

Admin/premium features gated Don't ship code most users will never use.


Next: SSR Patterns - understand server vs client components.

On this page