Dev Highlights

Доки по разработке

This project is maintained by teniryte

5. Ленивая загрузка и оптимизация фронтенда

Модуль 18-lazy-loading демонстрирует несколько важных техник:

5.1. Базовый паттерн next/dynamic

Функция dynamic из next/dynamic позволяет:

Простой пример:

import dynamic from 'next/dynamic';

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

export default function Page() {
  return (
    <div>
      <h1>Lazy Loading</h1>
      <HeavyComponent />
    </div>
  );
}

Принципы:

5.2. Отключение SSR для клиентских‑только компонентов

В примере из вашего проекта:

Паттерн:

import dynamic from 'next/dynamic';

const HeavyBrowserOnlyComponent = dynamic(
  () => import('./HeavyBrowserOnlyComponent'),
  {
    loading: () => <p>Загрузка...</p>,
    ssr: false, // отключаем SSR
  },
);

Когда так делать:

5.3. Lazy Loading для клиентских компонентов

Файл 01-client-components.tsx показывает:

Паттерн:

// 'use client' обязательно, т.к. компонент использует состояние
'use client';

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

const ComponentA = dynamic(() => import('./components/A')); // отдельный бандл
const ComponentB = dynamic(() => import('./components/B')); // подгружается по условию
const ComponentC = dynamic(() => import('./components/C'), { ssr: false });

export default function ClientComponentExample() {
  const [showMore, setShowMore] = useState(false);

  return (
    <div>
      {/* Загружается сразу, но в отдельном клиентском бандле */}
      <ComponentA />

      {/* Загружается только когда showMore === true */}
      {showMore && <ComponentB />}
      <button onClick={() => setShowMore((prev) => !prev)}>Toggle</button>

      {/* Рендерится только на клиенте, без SSR */}
      <ComponentC />
    </div>
  );
}

Принципы:

5.4. Ленивая загрузка сторонних библиотек (пример с fuse.js)

В 02-external-libraries.tsx ваш код делает следующее:

Паттерн:

// 'use client'
import { useState } from 'react';

const names = ['Tim', 'Joe', 'Bel', 'Lee'];

export default function SearchWithFuse() {
  const [results, setResults] = useState<any[]>([]);

  const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const { value } = e.currentTarget;

    // Динамический импорт только при необходимости
    const Fuse = (await import('fuse.js')).default;

    const fuse = new Fuse(names);
    setResults(fuse.search(value));
  };

  return (
    <div>
      <input type="text" placeholder="Search" onChange={handleChange} />
      <pre>Results: {JSON.stringify(results, null, 2)}</pre>
    </div>
  );
}

Принципы:

import Fuse from 'fuse.js'; // грузится сразу

делайте:

const Fuse = (await import('fuse.js')).default; // грузится только по событию

5.5. Кастомный компонент загрузки для dynamic

Файл 03-custom-loading.tsx демонстрирует:

Паттерн:

// 'use client'
import dynamic from 'next/dynamic';

const WithCustomLoading = dynamic(
  () => import('./components/WithCustomLoading'),
  {
    loading: () => <p>Loading...</p>, // показывается, пока идёт загрузка
  },
);

export default function Page() {
  return (
    <div>
      <WithCustomLoading />
    </div>
  );
}

Принципы:

5.6. Динамический импорт именованных экспортов

Файл 04-named-exports.tsx показывает пример:

Паттерн:

import dynamic from 'next/dynamic';

// Предположим, что файл ../components/hello.ts экспортирует именованный компонент Hello
const ClientComponent = dynamic(() =>
  import('../components/hello').then((mod) => mod.Hello),
);

Принципы:

5.7. Оптимизация изображений: next/image

В компоненте HeavyComponent вы показываете:

Пример:

import Image from 'next/image';
import Link from 'next/link';

export default function HeavyComponent() {
  return (
    <div>
      <Image
        src="/big-photo.jpg"
        alt="Example"
        width={600}
        height={400}
        loading="lazy" // по умолчанию в Next.js и так lazy
      />

      <Image
        src="/hero.jpg"
        alt="Main banner"
        width={1200}
        height={600}
        priority // отключает lazy, грузит сразу
      />

      <Link href="/about" prefetch={false}>
        О нас
      </Link>
    </div>
  );
}

Принципы:

5.8. Стратегия оптимизации фронтенда

Исходя из ваших примеров, можно сформулировать стратегию:

Модуль 18-lazy-loading в вашем проекте — готовый практический справочник по этим приёмам. Этого раздела должно хватить, чтобы вспомнить и переиспользовать все основные паттерны ленивой загрузки и оптимизации фронтенда в Next.js.