10 промтов для создания React/Next.js приложений: от компонентов до авторизации
React и Next.js остаются одними из самых популярных инструментов для фронтенд-разработки в 2026 году. По данным опроса State of JS 2025, React используют 87% респондентов, а Next.js — 63% среди фреймворков. Однако рутинные задачи — от написания типовых компонентов до настройки маршрутизации — отнимают время, которое можно потратить на архитектуру и бизнес-логику. Я собрал 10 проверенных промтов, которые использую ежедневно в коммерческих проектах. Каждый промт сопровождается примером использования и кодом.
1. Генерация функционального компонента с TypeScript и Storybook
Когда использовать: Создание нового UI-компонента для библиотеки или страницы.
Промт: «Сгенерируй React компонент на TypeScript с именем Button. Компонент должен принимать пропсы: variant (primary
| secondary), size (sm | md | lg), children (ReactNode), onClick (функция). Используй CSS modules для стилей. Создай файл .stories.tsx для Storybook с примерами всех вариантов. Экспортируй тип пропсов.»
Пример результата (Button.tsx):
import React from 'react';
import styles from './Button.module.css';
type ButtonVariant = 'primary' | 'secondary';
type ButtonSize = 'sm'
| 'md' | 'lg';
type ButtonProps = {
variant?: ButtonVariant;
size?: ButtonSize;
children: React.ReactNode;
onClick?: () => void;
};
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'md',
children,
onClick,
}) => {
const className = `${styles.button} ${styles[variant]} ${styles[size]}`;
return (
<button className={className} onClick={onClick}>
{children}
</button>
);
};
Button.stories.tsx:
import { Button } from './Button';
export default { component: Button };
export const Primary = { args: { variant: 'primary', children: 'Click me' } };
export const Secondary = { args: { variant: 'secondary', children: 'Cancel' } };
Совет: Уточните в промте, нужно ли использовать forwardRef для доступа к DOM-элементу — это часто требуется для форм.
2. Создание страницы Next.js с серверным рендерингом и SEO
Когда использовать: Добавление новой публичной страницы с динамическими данными.
Промт: «Создай страницу Next.js (App Router) для блога. Используй серверный компонент. Получи список постов из API https://jsonplaceholder.typicode.com/posts. Отобрази заголовки постов в виде ссылок на страницу /posts/[id]. Добавь метаданные (title, description) через generateMetadata. Используй CSS modules для стилей. Название файла: app/blog/page.tsx.»
Пример кода (app/blog/page.tsx):
import Link from 'next/link';
import styles from './page.module.css';
interface Post {
id: number;
title: string;
}
async function getPosts(): Promise<Post[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!res.ok) throw new Error('Failed to fetch posts');
return res.json();
}
export async function generateMetadata() {
return { title: 'Блог', description: 'Список последних статей' };
}
export default async function BlogPage() {
const posts = await getPosts();
return (
<div className={styles.container}>
<h1>Блог</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/posts/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
</div>
);
}
Важно: Для кеширования данных используйте параметр next: { revalidate: 60 } в fetch, чтобы страница обновлялась каждые 60 секунд.
3. Форма с валидацией через React Hook Form и Zod
Когда использовать: Регистрация, логин, любые формы с проверкой.
Промт: «Создай React компонент формы регистрации с полями: email, password (с подтверждением). Используй React Hook Form для управления состоянием и Zod для схемы валидации. Валидация: email — корректный email, password — минимум 8 символов, одна заглавная буква. Выводи ошибки под каждым полем. Стили — inline. Код на TypeScript.»
Пример кода:
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
const schema = z
.object({
email: z.string().email('Неверный email'),
password: z
.string()
.min(8, 'Минимум 8 символов')
.regex(/[A-Z]/, 'Нужна заглавная буква'),
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: 'Пароли не совпадают',
path: ['confirmPassword'],
});
type FormData = z.infer<typeof schema>;
export const RegisterForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({ resolver: zodResolver(schema) });
const onSubmit = (data: FormData) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>Email</label>
<input {...register('email')} />
{errors.email && <p style={{ color: 'red' }}>{errors.email.message}</p>}
</div>
<div>
<label>Пароль</label>
<input type="password" {...register('password')} />
{errors.password && <p style={{ color: 'red' }}>{errors.password.message}</p>}
</div>
<div>
<label>Подтвердите пароль</label>
<input type="password" {...register('confirmPassword')} />
{errors.confirmPassword && (
<p style={{ color: 'red' }}>{errors.confirmPassword.message}</p>
)}
</div>
<button type="submit">Зарегистрироваться</button>
</form>
);
};
Совет: Для больших форм добавьте в промт «используй useFieldArray для динамических полей».
4. Кастомный хук для работы с localStorage
Когда использовать: Сохранение состояния между сессиями без сервера.
Промт: «Напиши кастомный React хук useLocalStorage, который принимает ключ (string) и начальное значение. Возвращает кортеж [value, setValue]. Хук должен синхронизироваться с localStorage, обрабатывать ошибки (например, если localStorage недоступен) и обновлять состояние при изменении ключа. Используй TypeScript с дженериками.»
Пример кода:
import { useState, useEffect, useCallback } from 'react';
export function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((prev: T) => T)) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? (JSON.parse(item) as T) : initialValue;
} catch (error) {
console.error('Error reading localStorage key “' + key + '”:', error);
return initialValue;
}
});
const setValue = useCallback(
(value: T | ((prev: T) => T)) => {
try {
const valueToStore =
value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error('Error setting localStorage key “' + key + '”:', error);
}
},
[key, storedValue]
);
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === key && e.newValue) {
setStoredValue(JSON.parse(e.newValue));
}
};
window.addEventListener('storage', handleStorageChange);
return () => window.removeEventListener('storage', handleStorageChange);
}, [key]);
return [storedValue, setValue];
}
Применение: const [theme, setTheme] = useLocalStorage('theme', 'light');
5. Middleware для авторизации в Next.js
Когда использовать: Защита маршрутов от неавторизованных пользователей.
Промт: «Создай middleware для Next.js (файл middleware.ts в корне проекта). Проверяй наличие cookie с именем 'auth-token'. Если токена нет и путь начинается с /dashboard, /profile или /admin — перенаправляй на /login. Если токен есть и пользователь пытается зайти на /login — перенаправляй на /dashboard. Используй NextResponse. Оберни логику в try-catch.»
Пример кода (middleware.ts):
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const protectedPaths = ['/dashboard', '/profile', '/admin'];
const authPaths = ['/login'];
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value;
const path = request.nextUrl.pathname;
try {
if (protectedPaths.some((p) => path.startsWith(p)) && !token) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('redirect', path);
return NextResponse.redirect(loginUrl);
}
if (authPaths.includes(path) && token) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
return NextResponse.next();
} catch (error) {
console.error('Middleware error:', error);
return NextResponse.redirect(new URL('/error', request.url));
}
}
export const config = {
matcher: ['/dashboard/:path*', '/profile/:path*', '/admin/:path*', '/login'],
};
Важно: Для продакшена используйте JWT-токены и проверку подписи на сервере; middleware — это клиентский уровень.
6. Анимация появления элементов при скролле
Когда использовать: Улучшение UX на лендингах.
Промт: «Создай React компонент FadeInOnScroll, который анимирует появление дочерних элементов при скролле. Используй Intersection Observer API. Компонент должен принимать пропс threshold (число от 0 до 1, по умолчанию 0.1) и duration (строку, по умолчанию '0.5s'). При появлении в зоне видимости добавляй класс 'visible' с opacity: 1 и transform: translateY(0). Начальное состояние: opacity: 0, translateY(20px). Код на TypeScript.»
Пример кода (FadeInOnScroll.tsx):
import React, { useRef, useEffect, useState } from 'react';
interface FadeInOnScrollProps {
children: React.ReactNode;
threshold?: number;
duration?: string;
}
export const FadeInOnScroll: React.FC<FadeInOnScrollProps> = ({
children,
threshold = 0.1,
duration = '0.5s',
}) => {
const ref = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.unobserve(entry.target);
}
},
{ threshold }
);
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, [threshold]);
return (
<div
ref={ref}
style={{
opacity: isVisible ? 1 : 0,
transform: isVisible ? 'translateY(0)' : 'translateY(20px)',
transition: `opacity ${duration}, transform ${duration}`,
}}
>
{children}
</div>
);
};
Использование: <FadeInOnScroll><h1>Привет, мир!</h1></FadeInOnScroll>
7. Таблица с сортировкой и пагинацией
Когда использовать: Отображение больших списков данных.
Промт: «Создай React компонент DataTable на TypeScript. Принимает пропсы: columns (массив объектов с key, label), data (массив объектов). Реализуй сортировку по клику на заголовок столбца (переключение asc/desc) и пагинацию с выбором количества строк на странице (10, 20, 50). Используйте useState для управления состоянием. Стили — базовые inline.»
Пример кода (сокращённо):
import React, { useState, useMemo } from 'react';
interface Column {
key: string;
label: string;
}
interface DataTableProps {
columns: Column[];
data: Record<string, any>[];
}
export const DataTable: React.FC<DataTableProps> = ({ columns, data }) => {
const [sortKey, setSortKey] = useState<string | null>(null);
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
const [page, setPage] = useState(0);
const [pageSize, setPageSize] = useState(10);
const sortedData = useMemo(() => {
if (!sortKey) return data;
return [...data].sort((a, b) => {
if (a[sortKey] < b[sortKey]) return sortDir === 'asc' ? -1 : 1;
if (a[sortKey] > b[sortKey]) return sortDir === 'asc' ? 1 : -1;
return 0;
});
}, [data, sortKey, sortDir]);
const paginatedData = sortedData.slice(page * pageSize, (page + 1) * pageSize);
const totalPages = Math.ceil(sortedData.length / pageSize);
const handleSort = (key: string) => {
if (key === sortKey) {
setSortDir((prev) => (prev === 'asc' ? 'desc' : 'asc'));
} else {
setSortKey(key);
setSortDir('asc');
}
};
return (
<div>
<table>
<thead>
<tr>
{columns.map((col) => (
<th key={col.key} onClick={() => handleSort(col.key)}>
{col.label} {sortKey === col.key ? (sortDir === 'asc' ? '▲' : '▼') : ''}
</th>
))}
</tr>
</thead>
<tbody>
{paginatedData.map((row, i) => (
<tr key={i}>
{columns.map((col) => (
<td key={col.key}>{row[col.key]}</td>
))}
</tr>
))}
</tbody>
</table>
<div>
<button disabled={page === 0} onClick={() => setPage((p) => p - 1)}>Назад</button>
<span>Страница {page + 1} из {totalPages}</span>
<button disabled={page >= totalPages - 1} onClick={() => setPage((p) => p + 1)}>Вперёд</button>
<select value={pageSize} onChange={(e) => { setPageSize(Number(e.target.value)); setPage(0); }}>
<option value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
</select>
</div>
</div>
);
};
Совет: Для больших данных (более 10 000 строк) используйте виртуализацию — добавьте в промт «используй react-window».
8. Подключение и настройка React Query (TanStack Query)
Когда использовать: Управление серверным состоянием и кеширование.
Промт: «Создай кастомный хук usePosts с использованием @tanstack/react-query. Хук должен получать список постов из API https://jsonplaceholder.typicode.com/posts. Настрой кеширование на 5 минут (staleTime: 300000). Возвращай объект { data, isLoading, isError, error }. Также создай компонент PostsList, который использует этот хук и отображает список с индикатором загрузки и обработкой ошибок. Код на TypeScript.»
Пример кода:
import { useQuery } from '@tanstack/react-query';
interface Post {
id: number;
title: string;
}
const fetchPosts = async (): Promise<Post[]> => {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!res.ok) throw new Error('Network error');
return res.json();
};
export const usePosts = () => {
return useQuery<Post[], Error>({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 300000, // 5 minutes
});
};
// PostsList.tsx
import { usePosts } from './usePosts';
export const PostsList = () => {
const { data, isLoading, isError, error } = usePosts();
if (isLoading) return <p>Загрузка...</p>;
if (isError) return <p>Ошибка: {error.message}</p>;
return (
<ul>
{data?.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
};
Важно: Не забудьте обернуть приложение в QueryClientProvider.
9. Динамическая загрузка компонентов (Lazy Loading)
Когда использовать: Оптимизация размера бандла.
Промт: «Напиши пример динамического импорта компонента в Next.js с помощью next/dynamic. Создай страницу, которая загружает тяжёлый компонент Chart только при нажатии кнопки. Пока компонент загружается, покажи спиннер. Используй TypeScript. Компонент Chart должен быть в отдельном файле и экспортироваться по умолчанию.»
Пример кода:
// app/dashboard/page.tsx
import dynamic from 'next/dynamic';
import { useState } from 'react';
const DynamicChart = dynamic(() => import('./Chart'), {
loading: () => <p>Загрузка графика...</p>,
ssr: false, // отключаем SSR для клиентского компонента
});
export default function DashboardPage() {
const [showChart, setShowChart] = useState(false);
return (
<div>
<h1>Дашборд</h1>
<button onClick={() => setShowChart(true)}>Показать график</button>
{showChart && <DynamicChart />}
</div>
);
}
// app/dashboard/Chart.tsx
const Chart = () => {
// Тяжёлая логика
return <div>График загружен</div>;
};
export default Chart;
Совет: Для компонентов, которые не нужны на всех страницах, всегда используйте lazy loading — это уменьшает начальный бандл.
10. Генерация тестов для компонента
Когда использовать: Обеспечение качества кода.
Промт: «Напиши тесты на Jest и React Testing Library для компонента Button (из первого промта). Проверь: рендеринг с разными вариантами (primary, secondary), вызов onClick при клике, наличие класса для размера lg. Используй describe и it. Имитируй события через fireEvent.»
Пример кода (Button.test.tsx):
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('renders primary variant with correct text', () => {
render(<Button variant="primary">Click me</Button>);
const button = screen.getByText('Click me');
expect(button).toBeInTheDocument();
expect(button).toHaveClass('primary');
});
it('calls onClick when clicked', () => {
const onClick = jest.fn();
render(<Button onClick={onClick}>Click</Button>);
fireEvent.click(screen.getByText('Click'));
expect(onClick).toHaveBeenCalledTimes(1);
});
it('applies large size class', () => {
render(<Button size="lg">Large</Button>);
expect(screen.getByText('Large')).toHaveClass('lg');
});
});
Заключение
Эти 10 промтов покрывают 80% задач, с которыми я сталкиваюсь в коммерческой разработке на React и Next.js. Адаптируйте их под свои проекты: добавляйте специфичные пропсы, меняйте API, комбинируйте несколько промтов в один. Главное — всегда уточняйте контекст: фреймворк, библиотеки, стили. В 2026 году AI-инструменты стали незаменимыми помощниками, но качество результата напрямую зависит от того, как вы формулируете запрос. Экспериментируйте, и ваша продуктивность вырастет в разы.
Комментарии