Совместимость с Next.js

React Aria предоставляет набор хуков и утилит для создания доступных компонентов в React. При работе с Next.js важно учитывать особенности серверного рендеринга (SSR), динамической загрузки компонентов и управления состоянием, чтобы элементы оставались доступными и корректно рендерились на сервере.


Особенности SSR и React Aria

Next.js выполняет рендеринг на сервере, что накладывает ограничения на использование некоторых браузерных API, которые React Aria может применять внутри своих хуков. Например:

  • window, document и navigator недоступны на сервере.
  • API управления фокусом и измерения элементов (useOverlayPosition, useFocusRing) требуют DOM.

Для корректной работы React Aria следует:

  1. Использовать условный рендеринг компонентов, зависящих от DOM:
import { useEffect, useState } from 'react';
import { useButton } from '@react-aria/button';

export default function SSRSafeButton(props) {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  if (!isClient) return <button {...props}>Загрузка...</button>;

  let { buttonProps } = useButton(props);
  return <button {...buttonProps}>{props.children}</button>;
}
  1. Использовать динамический импорт с отключением SSR:
import dynamic from 'next/dynamic';

const ClientOnlyComponent = dynamic(
  () => import('../components/ClientOnlyComponent'),
  { ssr: false }
);

Это позволяет загружать компоненты, использующие React Aria, исключительно на клиенте, избегая ошибок при серверном рендеринге.


Управление фокусом и клавиатурная навигация

React Aria активно использует хуки для управления фокусом (useFocusRing, useFocus) и клавиатурной навигацией (useKeyboard, useListBox). При интеграции с Next.js важно учитывать:

  • Все состояния фокуса и клавиатурные события должны инициироваться на клиенте.
  • Для серверного рендеринга следует предоставлять базовую разметку с атрибутами доступности (aria-*), чтобы начальная страница была семантически корректной.

Пример использования useListBox в Next.js:

import { useListBox, useListBoxSection } from '@react-aria/listbox';
import { useListState } from '@react-stately/list';
import { useEffect, useState } from 'react';

export default function ListBox({ items }) {
  const [isClient, setIsClient] = useState(false);
  useEffect(() => setIsClient(true), []);

  const state = useListState({ items });
  let ref = React.useRef();
  let { listBoxProps } = useListBox({ 'aria-label': 'Выбор элементов' }, state, ref);

  if (!isClient) return <ul>{items.map(item => <li key={item.key}>{item.name}</li>)}</ul>;

  return (
    <ul {...listBoxProps} ref={ref}>
      {state.collection.map(item => (
        <li key={item.key}>{item.rendered}</li>
      ))}
    </ul>
  );
}

Работа с Overlay и Popover

Компоненты типа Dialog, Tooltip или Menu используют React Aria Overlay, который рассчитывает позиции относительно DOM. При серверном рендеринге позиционирование невозможно, поэтому:

  • Использовать динамический импорт или условный рендеринг для клиентской стороны.
  • Обеспечивать базовую разметку на сервере для SEO и доступности.
  • Проверять наличие document перед вызовом хуков useOverlayPosition.
import { useOverlayPosition, useOverlayTrigger } from '@react-aria/overlays';

function PopoverTrigger() {
  const [isOpen, setIsOpen] = useState(false);
  const triggerRef = useRef();
  const popoverRef = useRef();

  const { triggerProps, overlayProps } = useOverlayTrigger(
    { type: 'dialog', isOpen, onOpenChange: setIsOpen },
    triggerRef
  );

  const { overlayProps: positionProps } = useOverlayPosition({
    targetRef: triggerRef,
    overlayRef: popoverRef
  });

  return (
    <>
      <button {...triggerProps} ref={triggerRef}>Открыть поповер</button>
      {isOpen && (
        <div {...overlayProps} {...positionProps} ref={popoverRef}>
          Содержимое Popover
        </div>
      )}
    </>
  );
}

Доступность и атрибуты aria-*

Next.js позволяет формировать серверный HTML, поэтому важно, чтобы компоненты уже имели базовые aria-* атрибуты. React Aria автоматически добавляет их при использовании своих хуков, что улучшает совместимость:

  • aria-expanded, aria-haspopup для кнопок-триггеров.
  • aria-selected, aria-disabled для списков и элементов управления.
  • role и aria-label для интерактивных областей.

Оптимизация и производительность

При интеграции React Aria с Next.js следует учитывать производительность:

  • Использовать dynamic с { ssr: false } только для действительно требующих DOM компонентов.
  • Состояния и контексты React Aria можно оборачивать в отдельные клиентские провайдеры.
  • Минимизировать вызовы хуков на сервере, отдавая базовую разметку и атрибуты для SEO и доступности.

Совместимость с TypeScript

React Aria полностью поддерживает TypeScript, и при работе с Next.js рекомендуется:

  • Типизировать состояние компонентов с помощью useListState и useSelectState.
  • Явно указывать типы ref для Overlay и других DOM-зависимых компонентов.
  • Использовать React.RefObject и проверять наличие DOM перед вызовом хуков, чтобы избежать ошибок при SSR.
const triggerRef = useRef<HTMLButtonElement>(null);
const overlayRef = useRef<HTMLDivElement>(null);

Это обеспечивает строгую типизацию и предотвращает runtime ошибки на сервере.


Итоговые рекомендации

  1. Везде, где React Aria использует DOM, применять условный рендеринг или динамический импорт.
  2. На сервере отдавать базовую разметку с aria-* для доступности и SEO.
  3. Управление фокусом, Overlay и позиции рассчитывать только на клиенте.
  4. Типизировать компоненты и состояния для безопасной работы с TypeScript.
  5. Минимизировать использование SSR для компонентов, зависящих от браузера, сохраняя при этом семантическую корректность HTML.