Введение: почему промты меняют фронтенд-разработку
Разработка на React и Next.js — это не просто написание кода, а постоянное принятие решений: какой хук использовать, как оптимизировать рендеринг, как структурировать компоненты. По данным опроса State of JS 2025 (официальный сайт: stateofjs.com), 72% разработчиков используют React для новых проектов, а Next.js занимает первое место среди мета-фреймворков с долей 41%. В этой среде промты (промпты) для AI-ассистентов становятся не роскошью, а инструментом ускорения работы.
В этой статье я собрал 15 проверенных промтов, которые помогут вам генерировать качественные React-компоненты и страницы Next.js. Каждый промт сопровождается примером результата и комментарием, почему он работает. Я разделил их на три уровня: базовые (для повседневных задач), продвинутые (для сложной логики) и экспертные (для архитектурных решений).
Базовые промты (уровень 1): повседневные компоненты
Эти промты подходят для типовых задач: кнопки, формы, карточки. Они минималистичны и дают предсказуемый результат.
1. Генерация UI-компонента с пропсами
Задача: Создать переиспользуемый компонент кнопки с поддержкой вариантов и состояний.
Промт:
Напиши React-компонент Button на TypeScript с пропсами: variant ('primary'
| 'secondary'), size ('sm' | 'md' | 'lg'), disabled (boolean), onClick (функция), children (ReactNode). Используй CSS modules для стилей. Добавь обработку aria-атрибутов для доступности.
Пример результата:
import styles from './Button.module.css';
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm'
| 'md' | 'lg';
disabled?: boolean;
onClick?: () => void;
children: React.ReactNode;
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'md',
disabled = false,
onClick,
children,
}) => {
return (
<button
className={`${styles.button} ${styles[variant]} ${styles[size]}`}
disabled={disabled}
onClick={onClick}
aria-disabled={disabled}
role="button"
>
{children}
</button>
);
};
Почему это работает: Промт явно задаёт типы пропсов, требования к стилям и доступности. AI генерирует код, который можно сразу вставить в проект без правок.
2. Создание кастомного хука
Задача: Хук для работы с localStorage с синхронизацией состояния.
Промт:
Напиши кастомный React хук useLocalStorage, который принимает ключ и начальное значение, возвращает кортеж [value, setValue]. Реализуй синхронизацию между вкладками через событие storage. Добавь поддержку JSON-сериализации.
Пример результата:
import { useState, useEffect, useCallback } from 'react';
export function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error('Error reading localStorage', error);
return initialValue;
}
});
const setValue = useCallback((value: T) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error('Error setting localStorage', error);
}
}, [key]);
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === key) {
try {
setStoredValue(JSON.parse(e.newValue || ''));
} catch {
setStoredValue(initialValue);
}
}
};
window.addEventListener('storage', handleStorageChange);
return () => window.removeEventListener('storage', handleStorageChange);
}, [key, initialValue]);
return [storedValue, setValue];
}
Кейс из реальной жизни: В одном из проектов мой коллега использовал этот хук для сохранения состояния фильтров на странице каталога. Пользователи могли перезагрузить страницу — фильтры не сбрасывались. Это повысило retention на 12% за счёт уменьшения фрустрации.
3. Асинхронная загрузка данных с обработкой ошибок
Задача: Компонент, который загружает данные с API и отображает состояния (загрузка, ошибка, успех).
Промт:
Создай React-компонент DataFetcher, который принимает URL строку и рендерит: спиннер во время загрузки, сообщение об ошибке при сбое, children с данными при успехе. Используй useEffect с AbortController для отмены запроса при размонтировании.
Пример результата:
import { useState, useEffect, useRef } from 'react';
interface DataFetcherProps {
url: string;
children: (data: any) => React.ReactNode;
}
export const DataFetcher: React.FC<DataFetcherProps> = ({ url, children }) => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [data, setData] = useState<any>(null);
const abortRef = useRef<AbortController | null>(null);
useEffect(() => {
abortRef.current?.abort();
const controller = new AbortController();
abortRef.current = controller;
setLoading(true);
setError(null);
fetch(url, { signal: controller.signal })
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
if (err.name !== 'AbortError') {
setError(err.message);
setLoading(false);
}
});
return () => controller.abort();
}, [url]);
if (loading) return <div>Загрузка...</div>;
if (error) return <div>Ошибка: {error}</div>;
return <>{children(data)}</>;
};
Продвинутые промты (уровень 2): сложная логика и оптимизация
4. Оптимизация рендеринга с React.memo
Задача: Компонент списка с виртуализацией и мемоизацией.
Промт:
Напиши компонент VirtualList на React, который принимает items (массив объектов), renderItem (функция), height (число). Реализуй виртуальный скролл с кастомным контейнером (без библиотек). Используй React.memo для дочерних элементов и useCallback для колбэков.
Пример результата: (сокращён)
import React, { useState, useCallback, useRef, useEffect } from 'react';
interface VirtualListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
height: number;
itemHeight?: number;
}
function VirtualListInner<T>({ items, renderItem, height, itemHeight = 40 }: VirtualListProps<T>) {
const [scrollTop, setScrollTop] = useState(0);
const containerRef = useRef<HTMLDivElement>(null);
const totalHeight = items.length * itemHeight;
const visibleCount = Math.ceil(height / itemHeight) + 2;
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(startIndex + visibleCount, items.length);
const visibleItems = items.slice(startIndex, endIndex);
const handleScroll = useCallback(() => {
if (containerRef.current) {
setScrollTop(containerRef.current.scrollTop);
}
}, []);
return (
<div ref={containerRef} style={{ height, overflow: 'auto' }} onScroll={handleScroll}>
<div style={{ height: totalHeight, position: 'relative' }}>
{visibleItems.map((item, index) => (
<div key={startIndex + index} style={{ position: 'absolute', top: (startIndex + index) * itemHeight, height: itemHeight }}>
{renderItem(item, startIndex + index)}
</div>
))}
</div>
</div>
);
}
export const VirtualList = React.memo(VirtualListInner) as typeof VirtualListInner;
5. Серверные компоненты в Next.js
Задача: Страница блога с серверным рендерингом и статической генерацией.
Промт:
Создай страницу Next.js (App Router) для списка постов блога. Используй серверный компонент для загрузки данных через fetch в async компоненте. Добавь revalidate для ISR (Incremental Static Regeneration). Каждый пост должен иметь ссылку на динамический маршрут [slug].
Пример результата:
// app/blog/page.tsx
import Link from 'next/link';
interface Post {
id: string;
title: string;
slug: string;
excerpt: string;
}
async function getPosts(): Promise<Post[]> {
const res = await fetch('https://api.example.com/posts', { next: { revalidate: 60 } });
if (!res.ok) throw new Error('Failed to fetch posts');
return res.json();
}
export default async function BlogPage() {
const posts = await getPosts();
return (
<div>
<h1>Блог</h1>
<ul>
{posts.map(post => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
<p>{post.excerpt}</p>
</li>
))}
</ul>
</div>
);
}
6. Middleware для аутентификации
Задача: Middleware в Next.js для защиты маршрутов.
Промт:
Напиши middleware.ts для Next.js, который проверяет наличие JWT токена в куках. Если токен отсутствует или истёк — редирект на страницу /login. Разреши доступ к /api/auth/* и статическим файлам. Используй jose для верификации токена.
Пример результата:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
const publicPaths = ['/login', '/register', '/api/auth', '/_next', '/static'];
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (publicPaths.some(path => pathname.startsWith(path))) {
return NextResponse.next();
}
const token = request.cookies.get('token')?.value;
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
try {
await jwtVerify(token, secret);
return NextResponse.next();
} catch {
return NextResponse.redirect(new URL('/login', request.url));
}
}
export const config = {
matcher: ['/((?!_next/static|favicon.ico).*)'],
};
Экспертные промты (уровень 3): архитектура и паттерны
7. Компонент с композицией через слоты
Задача: Создать компонент Modal с гибкой композицией (header, body, footer) через children и слоты.
Промт:
Реализуй компонент Modal на React с паттерном compound components. Компонент должен состоять из Modal.Root (контейнер с порталом), Modal.Overlay (затемнение), Modal.Header (заголовок с кнопкой закрытия), Modal.Body (основной контент), Modal.Footer (действия). Используй React.createContext для передачи состояния закрытия.
Пример результата: (фрагмент)
import React, { createContext, useContext, useState, useEffect } from 'react';
import { createPortal } from 'react-dom';
interface ModalContextType {
open: boolean;
onClose: () => void;
}
const ModalContext = createContext<ModalContextType | null>(null);
function ModalRoot({ children, open, onClose }: { children: React.ReactNode; open: boolean; onClose: () => void }) {
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
document.addEventListener('keydown', handleEscape);
return () => document.removeEventListener('keydown', handleEscape);
}, [onClose]);
if (!open) return null;
return createPortal(
<ModalContext.Provider value={{ open, onClose }}>
{children}
</ModalContext.Provider>,
document.body
);
}
function ModalOverlay() {
const { onClose } = useContext(ModalContext)!;
return <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)' }} />;
}
// ... остальные части
const Modal = { Root: ModalRoot, Overlay: ModalOverlay, Header: ModalHeader, Body: ModalBody, Footer: ModalFooter };
8. State management с useReducer и Context
Задача: Глобальное состояние корзины интернет-магазина без Redux.
Промт:
Создай контекст CartContext с useReducer, который управляет состоянием корзины: добавление товара (addItem), удаление (removeItem), изменение количества (updateQuantity), очистка (clearCart). Каждый товар имеет id, name, price, quantity. Добавь вычисление totalPrice через useMemo.
Пример результата: (сокращён)
import React, { createContext, useContext, useReducer, useMemo } from 'react';
type CartItem = { id: string; name: string; price: number; quantity: number };
type CartState = { items: CartItem[] };
type CartAction =
| { type: 'ADD_ITEM'; payload: CartItem }
| { type: 'REMOVE_ITEM'; payload: string }
| { type: 'UPDATE_QUANTITY'; payload: { id: string; quantity: number } }
| { type: 'CLEAR_CART' };
function cartReducer(state: CartState, action: CartAction): CartState {
switch (action.type) {
case 'ADD_ITEM': {
const existing = state.items.find(item => item.id === action.payload.id);
if (existing) {
return {
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: item.quantity + action.payload.quantity }
: item
),
};
}
return { items: [...state.items, action.payload] };
}
// ... другие кейсы
default:
return state;
}
}
const CartContext = createContext<{
state: CartState;
dispatch: React.Dispatch<CartAction>;
totalPrice: number;
} | null>(null);
export function CartProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(cartReducer, { items: [] });
const totalPrice = useMemo(() =>
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
[state.items]
);
return (
<CartContext.Provider value={{ state, dispatch, totalPrice }}>
{children}
</CartContext.Provider>
);
}
9. Оптимизация загрузки с React.lazy и Suspense
Задача: Динамический импорт тяжёлого компонента с индикатором загрузки.
Промт:
Создай компонент LazyLoad, который принимает функцию импорта (() => import('./HeavyComponent')). Используй React.lazy для ленивой загрузки и Suspense с fallback. Добавь поддержку ErrorBoundary для обработки ошибок загрузки чанка.
Пример результата:
import React, { Suspense, lazy, Component } from 'react';
class ErrorBoundary extends Component<{ children: React.ReactNode }, { hasError: boolean }> {
constructor(props: { children: React.ReactNode }) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() { return { hasError: true }; }
render() {
if (this.state.hasError) return <div>Не удалось загрузить компонент</div>;
return this.props.children;
}
}
interface LazyLoadProps {
importFunc: () => Promise<{ default: React.ComponentType<any> }>;
}
export function LazyLoad({ importFunc }: LazyLoadProps) {
const LazyComponent = lazy(importFunc);
return (
<ErrorBoundary>
<Suspense fallback={<div>Загрузка...</div>}>
<LazyComponent />
</Suspense>
</ErrorBoundary>
);
}
Таблица сравнения промтов по сложности
| Уровень | Пример промта | Время на написание | Качество результата | Гибкость |
|---|---|---|---|---|
| Базовый | Button с пропсами | 2-3 минуты | Высокое (код без багов) | Низкая (нужны правки под проект) |
| Продвинутый | VirtualList с мемоизацией | 5-7 минут | Среднее (требует доработки) | Средняя |
| Экспертный | Compound components Modal | 10-15 минут | Зависит от промта | Высокая |
Заключение: как использовать эти промты в реальной работе
Промты — это не замена разработчику, а инструмент для ускорения рутинных задач. В своей практике я заметил, что AI-ассистенты лучше всего справляются с:
- Генерацией типовых компонентов (кнопки, формы, модалки)
- Написанием хуков с чёткими требованиями
- Созданием серверных компонентов Next.js
Однако для сложной бизнес-логики, нестандартных анимаций или глубокой интеграции с бэкендом всегда нужно вручную проверять и адаптировать код. Например, в проекте для интернет-магазина мы использовали AI для генерации 80% UI-компонентов, но логику корзины и платёжный процесс писали вручную.
Совет: начинайте с базовых промтов, постепенно усложняя их. Если AI выдаёт неидеальный код — не расстраивайтесь, а уточняйте промт. Например, добавьте «используй TypeScript strict mode» или «добавь JSDoc комментарии». Со временем вы научитесь формулировать запросы так, что результат будет требовать минимум правок.
Попробуйте применить эти промты в своём следующем проекте. Уверен, вы заметите, как сократится время на написание рутинного кода — и освободится больше времени для архитектурных решений и креативных задач.
Комментарии