
use client везде — это тихо убивает производительность Next.js
Есть одна ошибка которую я видел в Next.js проектах чаще всего - включая свои собственные. Разработчик добавляет 'use client' в компонент потому что там нужна одна интерактивная кнопка. Потом забывает. Через полгода фича переделывается, кнопка уходит - директива остаётся. И весь поддерев компонентов который никогда не нуждался в клиентском рендеринге продолжает гнать JavaScript в браузер каждому пользователю.
Бандл вырос. Никто не заметил.
Как это работает под капотом
'use client' в Next.js - это не просто пометка. Это граница. Всё что находится внутри компонента с этой директивой - и всё что он импортирует - автоматически становится клиентским кодом и едет в браузер.
Момент когда добавляешь 'use client' - этот компонент и всё что он импортирует попадает в клиентский бандл и гидратируется. Самая частая ошибка - добавить 'use client' к layout или wrapper компоненту потому что один дочерний элемент его требует.
Вот как это выглядит на практике:
// ✗ Плохо - весь поддерево едет на клиент
'use client'
import { Header } from './Header'
import { StaticContent } from './StaticContent' // не нуждается в клиенте
import { InteractiveWidget } from './InteractiveWidget' // только это требует
export function PageLayout({ children }) {
return (
<>
<Header />
<StaticContent /> {/* теперь клиентский - хотя не должен */}
<InteractiveWidget />
{children}
</>
)
}
StaticContent и Header теперь клиентские компоненты. Они включены в JS бандл и гидратируются - даже если никогда не используют state, эффекты или обработчики событий.
Золотое правило
Опускай 'use client' как можно глубже в дерево компонентов. Страница с одним интерактивным выпадающим меню не нуждается в 'use client' на уровне страницы. Только само меню нуждается в нём. Всё остальное остаётся серверным.
// ✓ Правильно - layout и статичный контент остаются серверными
import { Header } from './Header' // server component
import { StaticContent } from './StaticContent' // server component
import { InteractiveWidget } from './InteractiveWidget' // только он клиентский
export function PageLayout({ children }) {
return (
<>
<Header />
<StaticContent />
<InteractiveWidget />
{children}
</>
)
}
// InteractiveWidget.tsx - только здесь директива
'use client'
export function InteractiveWidget() {
const [open, setOpen] = useState(false)
return <button onClick={() => setOpen(!open)}>...</button>
}
Три вопроса прежде чем добавить 'use client'
Простой тест перед каждым использованием директивы:
- Нужны ли здесь обработчики событий (
onClick,onChange)? - Нужен ли здесь
useState,useEffectили другие хуки? - Нужны ли здесь браузерные API (
window,document)?
Если ответ на все три - нет, компонент должен оставаться серверным.
Особенно опасный паттерн - унаследованный 'use client'
Кто-то добавляет интерактивность в большой компонент и ставит 'use client' наверху файла. Через шесть месяцев фича рефакторится или тихо удаляется. Директива остаётся. Целое поддерево компонентов которым никогда не нужно было быть клиентскими продолжает отправлять JavaScript каждому пользователю.
Это не видно в метриках. Это не попадает в баг-трекер. Просто накапливается.
Что это даёт на практике
Server Components уменьшают клиентский JavaScript до 70%. React Server Components рендерятся полностью на сервере и стримят HTML клиенту без отправки кода компонента в браузер. Страница которая могла быть в основном статичным HTML не становится бандлом на 480KB.
На своём сайте придерживаюсь этого принципа - серверные компоненты по умолчанию, 'use client' только там где реально нужна интерактивность. Список заметок, страница серии, хлебные крошки - всё серверное. Только элементы с реальным пользовательским взаимодействием получают директиву.
'use client' - это инструмент, не дефолт. 🚀

Комментарии