Пошук уроків, статей та іншого контенту
Налаштуєте next/dynamic для відкладеного рендерингу компонентів, fallback-станів і клієнтських залежностей.
next/dynamicnext/dynamic завантажує React-компонент динамічно, окремим JavaScript-чанком. Це дає змогу не включати код компонента в основний bundle сторінки, якщо він потрібен лише за певної умови або після взаємодії користувача.
Типовий синтаксис:
import dynamic from "next/dynamic";
const DynamicComponent = dynamic(() => import("./DynamicComponent"));Після цього DynamicComponent можна використовувати майже як звичайний компонент:
<DynamicComponent />Модуль завантажиться лише тоді, коли Next.js почне рендерити цей компонент.
Створимо компонент із додатковою логікою.
components/Statistics.jsxexport default function Statistics() {
return (
<section>
<h2>Статистика</h2>
<p>Завантажено дані про активність користувачів.</p>
</section>
);
}Підключимо його динамічно на сторінці:
app/page.jsximport dynamic from "next/dynamic";
const Statistics = dynamic(() => import("../components/Statistics"));
export default function HomePage() {
return (
<main>
<h1>Головна сторінка</h1>
<Statistics />
</main>
);
}У цьому прикладі код Statistics буде винесено в окремий chunk. Це особливо корисно для:
великих компонентів;
редакторів;
графіків;
модальних вікон;
функціональності, яка потрібна лише після дії користувача.
Динамічний імпорт не означає, що компонент обов’язково завантажиться лише після натискання кнопки. Якщо компонент одразу присутній у дереві рендерингу, Next.js завантажить його під час рендерингу сторінки.
loadingПоки динамічний компонент завантажується, можна показати fallback-компонент за допомогою властивості loading.
import dynamic from "next/dynamic";
const Statistics = dynamic(() => import("../components/Statistics"), {
loading: () => <p>Завантаження статистики...</p>,
});
export default function HomePage() {
return (
<main>
<h1>Головна сторінка</h1>
<Statistics />
</main>
);
}Функція loading має повернути React-елемент. Її результат відображається до завершення завантаження динамічного компонента.
Fallback має відповідати приблизному розміру майбутнього компонента. Це допомагає уникнути різких зміщень інтерфейсу:
const Statistics = dynamic(() => import("../components/Statistics"), {
loading: () => (
<section aria-busy="true">
<h2>Статистика</h2>
<p>Завантаження даних...</p>
</section>
),
});Компонент можна імпортувати динамічно не під час початкового рендерингу, а після натискання кнопки. Для цього import() викликають у функції-обробнику.
Такий підхід зручний для модальних вікон або рідко використовуваних панелей.
components/FeedbackForm.jsx"use client";
export default function FeedbackForm({ onClose }) {
return (
<div role="dialog" aria-modal="true">
<h2>Залиште відгук</h2>
<textarea placeholder="Ваш відгук" />
<div>
<button type="button" onClick={onClose}>
Закрити
</button>
</div>
</div>
);
}app/page.jsx"use client";
import { useState } from "react";
export default function HomePage() {
const [FeedbackForm, setFeedbackForm] = useState(null);
const [isLoading, setIsLoading] = useState(false);
async function openFeedbackForm() {
setIsLoading(true);
const module = await import("../components/FeedbackForm");
setFeedbackForm(() => module.default);
setIsLoading(false);
}
function closeFeedbackForm() {
setFeedbackForm(null);
}
return (
<main>
<h1>Профіль користувача</h1>
<button type="button" onClick={openFeedbackForm}>
Залишити відгук
</button>
{isLoading && <p>Завантаження форми...</p>}
{FeedbackForm && <FeedbackForm onClose={closeFeedbackForm} />}
</main>
);
}Тут код FeedbackForm не завантажується під час першого відкриття сторінки. Він завантажується лише після натискання кнопки.
Функція:
setFeedbackForm(() => module.default);використовується тому, що React сприймає функцію, передану в setter, як callback для обчислення нового стану. Обгортка () => module.default повідомляє React, що значенням стану має бути саме компонент.
Серверний рендеринг не має доступу до браузерних об'єктів:
window;
document;
localStorage;
sessionStorage;
navigator.
Якщо компонент звертається до таких API під час виконання, його потрібно рендерити лише на клієнті. Для цього використовується опція ssr: false.
import dynamic from "next/dynamic";
const BrowserOnlyChart = dynamic(() => import("../components/BrowserOnlyChart"), {
ssr: false,
loading: () => <p>Підготовка графіка...</p>,
});
export default function DashboardPage() {
return (
<main>
<h1>Панель керування</h1>
<BrowserOnlyChart />
</main>
);
}components/BrowserOnlyChart.jsx"use client";
import { useEffect, useState } from "react";
export default function BrowserOnlyChart() {
const [width, setWidth] = useState(0);
useEffect(() => {
function updateWidth() {
setWidth(window.innerWidth);
}
updateWidth();
window.addEventListener("resize", updateWidth);
return () => {
window.removeEventListener("resize", updateWidth);
};
}, []);
return <p>Ширина вікна браузера: {width}px</p>;
}У цьому випадку Next.js не намагатиметься відрендерити BrowserOnlyChart на сервері. Спочатку буде показано loading, а після завантаження JavaScript-компонент з'явиться в браузері.
ssr: falseне слід використовувати для кожного компонента. Воно потрібне лише тоді, коли компонент або його залежність справді не може працювати під час серверного рендерингу.
next/dynamic і директива "use client"У проєкті з App Router динамічний компонент із клієнтською логікою має бути Client Component.
Додайте "use client" у файл компонента, якщо він використовує:
useState;
useEffect;
обробники подій;
браузерні API.
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button type="button" onClick={() => setCount(count + 1)}>
Натискань: {count}
</button>
);
}Батьківський Server Component може підключити такий компонент динамічно:
import dynamic from "next/dynamic";
const Counter = dynamic(() => import("../components/Counter"));
export default function Page() {
return (
<main>
<h1>Лічильник</h1>
<Counter />
</main>
);
}Однак у App Router опцію ssr: false можна використовувати лише в Client Component. Якщо динамічний імпорт із ssr: false оголошується у файлі з "use client", це працює коректно:
"use client";
import dynamic from "next/dynamic";
const BrowserOnlyChart = dynamic(
() => import("../components/BrowserOnlyChart"),
{
ssr: false,
loading: () => <p>Завантаження графіка...</p>,
}
);
export default function DashboardPage() {
return <BrowserOnlyChart />;
}Динамічний компонент приймає props так само, як і звичайний компонент.
import dynamic from "next/dynamic";
const UserCard = dynamic(() => import("../components/UserCard"), {
loading: () => <p>Завантаження профілю...</p>,
});
export default function Page() {
return (
<main>
<UserCard userId="user-42" showEmail={false} />
</main>
);
}components/UserCard.jsxexport default function UserCard({ userId, showEmail }) {
return (
<article>
<h2>Профіль користувача</h2>
<p>ID: {userId}</p>
{showEmail && <p>Електронна пошта доступна власнику профілю.</p>}
</article>
);
}next/dynamic не змінює API компонента. Він змінює лише спосіб завантаження його коду.
dynamic очікує модуль із default export. Якщо компонент експортується іменовано, потрібно перетворити результат import():
import dynamic from "next/dynamic";
const Report = dynamic(() =>
import("../components/Reports").then((module) => module.Report)
);
export default function Page() {
return <Report />;
}Файл компонента:
export function Report() {
return <section>Звіт готовий</section>;
}Тут then повертає саме компонент Report, а не весь об'єкт модуля.
next/dynamicВикористовуйте динамічне завантаження, якщо:
компонент великий і не потрібен одразу;
компонент відкривається після натискання кнопки;
сторінка містить важкий графік або редактор;
залежність працює лише в браузері;
потрібно зменшити початковий JavaScript для сторінки.
Не варто динамічно завантажувати кожен маленький компонент. Надмірна кількість маленьких chunk-ів може ускладнити завантаження й не дати помітної користі.
window під час серверного рендерингуНеправильно:
const screenWidth = window.innerWidth;Такий код виконається і на сервері, де window не існує.
Варіанти виправлення:
перенести звернення до window у useEffect;
використати ssr: false, якщо весь компонент є браузерним.
"use client" для інтерактивного компонентаЯкщо компонент використовує useState, useEffect або події, додайте:
"use client";Директива має бути першим оператором файлу.
loading обов'язково приховає серверний HTMLloading показує стан очікування динамічного компонента. Якщо компонент може бути відрендерений на сервері, сервер або Next.js можуть повернути його HTML ще до завантаження клієнтського JavaScript.
Для компонента, який має з'явитися лише в браузері, використовуйте ssr: false.
Не створюйте dynamic() під час кожного рендерингу:
function Page() {
const Chart = dynamic(() => import("../components/Chart"));
return <Chart />;
}Оголошуйте динамічний компонент поза тілом функціонального компонента:
const Chart = dynamic(() => import("../components/Chart"));
function Page() {
return <Chart />;
}Так компонент матиме стабільне посилання між рендерами.
Шлях у import() обчислюється відносно файлу, де оголошено dynamic:
const Widget = dynamic(() => import("../components/Widget"));Перевірте розташування файлів і регістр символів у назві шляху.
next/dynamic завантажує компонент окремим chunk-ом.
Опція loading задає fallback на час завантаження.
Для завантаження лише після дії користувача можна використати звичайний import() в обробнику.
ssr: false вимикає серверний рендеринг компонента й підходить для браузерних залежностей.
Компоненти з хуками, подіями та браузерними API мають бути Client Components.
Динамічні компоненти слід оголошувати поза тілом батьківського компонента.
Динамічне завантаження корисне для великих або необов'язкових частин інтерфейсу, але не для кожного малого компонента.