Сайт использует сookies для хранения данных. Продолжая использовать сайт, вы даёте согласие на работу с этими файлами.

ОК
💻
Технологии
Опубликовано:
14.09.2026
Обновлено:
14.09.2026

Как исправить ошибку «'Promise<Element>' is not a valid JSX element» в React и Next.js

Тимофей Ищенко

Коротко: Разбираем причины ошибки Promise<Element> is not a valid JSX element в React и Next.js. Пошаговые способы исправления типов и настройки TypeScript.

При переходе на React Server Components (RSC) или разработке страниц в Next.js App Router разработчики регулярно сталкиваются с ошибкой тайпчекера: 'Promise<Element>' is not a valid JSX element (или ее расширенным вариантом: 'Component' cannot be used as a JSX component. Its return type 'Promise<Element>' is not a valid JSX element).

// Пример компонента, вызывающего ошибку компиляции
async function UserProfile({ userId }: { userId: string }) {
  const user = await fetchUserData(userId);
  return <div>{user.name}</div>;
}

// Ошибка при вызове в JSX:
// 'UserProfile' cannot be used as a JSX component.
// Its return type 'Promise<JSX.Element>' is not a valid JSX element.
export default function Page() {
  return <UserProfile userId="123" />;
}

Эта проблема связана не с логикой рендеринга самого React, а с тем, как TypeScript проверяет возвращаемые типы JSX-элементов. Ниже разобран механизм возникновения ошибки и рабочие способы ее устранения — от обновления зависимостей до архитектурного разделения компонентов.


В чем суть проблемы и почему возникает ошибка

Конфликт синхронной модели JSX и промисов (async/await)

Исторически любой React-компонент представлял собой синхронную функцию. Тайпчекер TypeScript ожидал, что функциональный компонент вернет значение типа JSX.Element либо ReactNode (примитивы, массивы или null).

Когда функция объявляется с ключевым словом async, JavaScript автоматически оборачивает возвращаемое значение в объект Promise. Сигнатура функции меняется с () => JSX.Element на () => Promise<JSX.Element>. Ранние версии TypeScript и определений @types/react не допускали промисы в качестве валидного узла JSX-дерева, поэтому статический анализ завершался ошибкой.

Роль TypeScript 5.1 и механизма JSX.ElementType

До релиза TypeScript 5.1 компилятор жестко требовал, чтобы тип возвращаемого значения функционального компонента строго наследовался от JSX.Element. Даже когда серверный рантайм React уже поддерживал асинхронные компоненты, TypeScript запрещал их использование в разметке.

Начиная с TypeScript 5.1 был внедрен механизм JSX.ElementType. Он позволил библиотекам гибко задавать типы, допустимые в роли JSX-тегов. В сочетании с обновленными пакетами @types/react компилятор научился распознавать асинхронные функции, возвращающие Promise<ReactNode>, как валидные серверные компоненты.


Главные причины появления ошибки на практике

1. Устаревшие версии TypeScript или пакетов @types/react

Наиболее частая причина: проект использует актуальный фреймворк (Next.js 13/14/15 или React 18/19), но в файле package.json зафиксированы старые версии typescript (ниже 5.1) или @types/react (ниже 18.2.43). В этом случае среда выполнения готова к асинхронным компонентам, а компилятор блокирует сборку.

2. Попытка сделать асинхронным клиентский компонент ('use client')

Асинхронными могут быть только React Server Components (RSC). Если файл помечен директивой 'use client', React ожидает синхронного рендеринга на стороне браузера. Объявление клиентского компонента как async function приводит к ошибкам типов и сбоям в рантайме.

'use client';

// ОШИБКА: Клиентский компонент не может быть асинхронной функцией
export default async function ClientWidget() {
  const data = await fetch('/api/data');
  return <div>{data.title}</div>;
}

3. Дублирование типов в node_modules и монорепозиториях

В монорепозиториях (Turborepo, Nx, pnpm workspaces) или при наличии вложенных транзитивных зависимостей часто возникает конфликт версий @types/react. Редактор кода или сборщик может подхватить устаревший файл деклараций из соседнего пакета, игнорируя глобально установленный пакет.


Пошаговые способы решения

Решение 1. Обновление TypeScript и типов React (рекомендуемый путь)

Для штатной поддержки асинхронных компонентов обновите компилятор и сопутствующие типы:

  1. Убедитесь, что версия typescript в проекте — не ниже 5.1.3, а пакеты @types/react и @types/react-dom — не ниже 18.2.43 (или соответствуют React 19).
  2. Запустите команду обновления пакетов:
# Для npm
npm install -D typescript@latest @types/react@latest @types/react-dom@latest

# Для pnpm
pnpm add -D typescript@latest @types/react@latest @types/react-dom@latest

# Для yarn
yarn add -D typescript@latest @types/react@latest @types/react-dom@latest
  1. Перезапустите TypeScript Server в редакторе (VS Code):
    • Нажмите комбинацию клавиш Ctrl + Shift + P (или Cmd + Shift + P на macOS).
    • Выполните команду TypeScript: Restart TS Server.
    • Убедитесь, что IDE использует версию из проекта: вызовите TypeScript: Select TypeScript Version и переключитесь на Use Workspace Version.

Решение 2. Корректное разделение Server и Client Components

Если компонент использует состояние, эффекты или браузерные события (useState, useEffect, onClick), он должен оставаться клиентским и синхронным. Асинхронную загрузку данных в таком случае переносят на уровень серверного родителя.

Архитектурный паттерн (Server Container -> Client Presentation):

  1. Серверный компонент выполняет асинхронный запрос:
// ServerPage.tsx (Server Component по умолчанию)
import ClientForm from './ClientForm';

export default async function ServerPage() {
  const initialData = await fetchFormData();
  return <ClientForm initialData={initialData} />;
}
  1. Клиентский компонент принимает готовые данные через props:
// ClientForm.tsx
'use client';

import { useState } from 'react';

interface ClientFormProps {
  initialData: { title: string };
}

export default function ClientForm({ initialData }: ClientFormProps) {
  const [data, setData] = useState(initialData);

  return (
    <form>
      <input 
        value={data.title} 
        onChange={(e) => setData({ title: e.target.value })} 
      />
    </form>
  );
}

Если данные требуется загружать напрямую в клиентском компоненте, используйте хук use() в связке с Suspense или библиотеки управления серверным состоянием (TanStack Query, SWR):

'use client';

import { use } from 'react';

export default function UserInfo({ userPromise }: { userPromise: Promise<{ name: string }> }) {
  // Хук use() синхронно разворачивает переданный промис внутри Suspense
  const user = use(userPromise);
  return <div>{user.name}</div>;
}

Решение 3. Временное подавление через @ts-expect-error

Если проект заблокирован внешними зависимостями и моментальный апгрейд TypeScript невозможен, используйте точечное подавление директивой @ts-expect-error:

// AsyncWidget.tsx
async function AsyncWidget() {
  const data = await getData();
  return <div>{data.text}</div>;
}

// Page.tsx
export default function Page() {
  return (
    <main>
      {/* @ts-expect-error Server Component Async Return Type */}
      <AsyncWidget />
    </main>
  );
}

Обратите внимание: данный метод допустим только как временная мера перед плановым рефакторингом зависимостей.


Особенности работы в Next.js App Router

В App Router (app/) все компоненты по умолчанию исполняются на сервере. Асинхронные страницы (page.tsx) и лэйауты (layout.tsx) являются стандартным шаблоном.

Рекомендации по конфигурации проекта:

  1. Конфигурация tsconfig.json: убедитесь, что параметр jsx установлен в "preserve", а также включен флаг пропуска проверки внешних библиотек "skipLibCheck": true.
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ]
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}
  1. Совместимость с UI-библиотеками: если устаревший UI-провайдер принимает в качестве children только строгий JSX.Element, оберните асинхронный вызов в компонент-обертку или используйте Suspense.

Чек-лист для устранения ошибки в кодовой базе

  • [ ] В package.json установлена версия typescript >= 5.1.3.
  • [ ] Пакеты @types/react и @types/react-dom обновлены до актуальных версий.
  • [ ] В настройках IDE (VS Code) выбрана версия TypeScript из рабочей области (Workspace Version).
  • [ ] Компоненты, содержащие директиву 'use client', не имеют модификатора async.
  • [ ] В tsconfig.json активирован флаг "skipLibCheck": true и задан современный "moduleResolution" (bundler или node16).
  • [ ] При наличии монорепозитория выполнена дедупликация пакетов (pnpm dedupe / npm dedupe).

Часто задаваемые вопросы (FAQ)

Можно ли сделать асинхронным клиентский компонент?

Нет. Клиентские компоненты должны возвращать разметку синхронно. Для асинхронных операций в них применяются хуки (useEffect, use), TanStack Query или получение данных через серверные компоненты-контейнеры.

Помогает ли приведение типов вида as unknown as JSX.Element?

Кастинг типов устраняет сообщение об ошибке, но снижает безопасность кода и маскирует реальное состояние типов. Основным решением остается синхронизация версий typescript и @types/react.

Что делать, если зависимости обновлены, но редактор продолжает подсвечивать ошибку?

Это указывает на кэш TS Server в редакторе. Нажмите Ctrl + Shift + P, выберите TypeScript: Restart TS Server и перепроверьте, что в статусной строке IDE используется версия из node_modules проекта, а не глобальная версия редактора.

Почему в Pages Router асинхронные компоненты вызывают ошибку?

В каталоге pages/ (Pages Router) действует классическая модель рендеринга React, не поддерживающая React Server Components на уровне страниц. Для загрузки данных там предусмотрены функции getServerSideProps и getStaticProps.


Заключение

Ошибка 'Promise<Element>' is not a valid JSX element отражает эволюцию экосистемы React и ее переход к серверным компонентам. Проблема решается обновлением связки typescript (5.1+) и @types/react, настройкой правильной версии компилятора в IDE и разделением серверной логики получения данных и клиентского интерактивного UI.

Источники

Это авторская статья, основанная на личном опыте и субъективном взгляде автора. Заметили ошибку или битую ссылку? Сообщите нам: info@codesrc.ru - мы оперативно исправим. Спасибо, что помогаете делать блог лучше.
Следите за нами в соцсетях:

Читайте также