React Server Components: будущее React

RSC, Server Actions и новая архитектура React приложений

React Server Components переосмысливают границу между сервером и клиентом. Zero bundle size для серверных компонентов, прямой доступ к базе данных, автоматическая сериализация. Будущее React уже здесь. Что такое React Server Components Традиционный React (CSR): Весь код отправляется клиенту Рендеринг на клиенте API запросы для данных Большой bundle size Server Components (RSC): Рендеринг на сервере Прямой доступ к базе данных Zero bundle size Автоматическая сериализация Streaming Server vs Client Components Server Component (по умолчанию): // app/page.js import { db } from '@/lib/db'; export default async function Page() { // Прямой доступ к БД на сервере! const users = await db.user.findMany(); return ( Users {users.map(user => ( {user.name} ))} ); } Client Component (с интерактивностью): 'use client'; // Директива для клиентского компонента import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); return ( setCount(count + 1)}> Count: {count} ); } Композиция Server + Client: // Server Component import ClientButton from './ClientButton'; export default async function Page() { const data = await fetchData(); return ( Server Component Data: {data} {/* Client Component внутри Server Component */} ); } Data Fetching в RSC Параллельные запросы: async function getUser(id) { const res = await fetch(`https://api.example.com/users/${id}`); return res.json(); } async function getPosts(userId) { const res = await fetch(`https://api.example.com/posts?userId=${userId}`); return res.json(); } export default async function Profile({ params }) { // Параллельное выполнение const [user, posts] = await Promise.all([ getUser(params.id), getPosts(params.id) ]); return ( {user.name} ); } Кэширование: // Автоматическое кэширование fetch const res = await fetch('https://api.example.com/data', { cache: 'force-cache' // по умолчанию }); // Revalidate каждые 60 секунд const res = await fetch('https://api.example.com/data', { next: { revalidate: 60 } }); // No cache const res = await fetch('https://api.example.com/data', { cache: 'no-store' }); Streaming и Suspense Streaming SSR: import { Suspense } from 'react'; async function SlowComponent() { await new Promise(resolve => setTimeout(resolve, 3000)); return Loaded! ; } export default function Page() { return ( Instant Content Loading slow component... }> More instant content ); } Nested Suspense: export default function Dashboard() { return ( }> }> }> ); } Server Actions Что такое Server Actions: Асинхронные функции выполняющиеся на сервере, вызываемые из клиента. Простой пример: // app/actions.js 'use server'; import { db } from '@/lib/db'; import { revalidatePath } from 'next/cache'; export async function createPost(formData) { const title = formData.get('title'); const content = formData.get('content'); await db.post.create({ data: { title, content } }); revalidatePath('/posts'); } // app/new-post/page.js import { createPost } from '../actions'; export default function NewPost() { return ( Create Post ); } С обработкой ошибок: 'use server'; export async function updateUser(formData) { const userId = formData.get('userId'); const name = formData.get('name'); try { await db.user.update({ where: { id: userId }, data: { name } }); return { success: true }; } catch (error) { return { success: false, error: error.message }; } } // Client component 'use client'; import { useFormState } from 'react-dom'; import { updateUser } from './actions'; export default function UpdateForm() { const [state, formAction] = useFormState(updateUser, null); return ( Update {state?.error && Error: {state.error} } {state?.success && Updated successfully! } ); } Programmatic вызов: 'use client'; import { deletePost } from './actions'; export default function PostCard({ post }) { const handleDelete = async () => { if (confirm('Delete post?')) { await deletePost(post.id); } }; return ( {post.title} Delete ); } Loading и Error States loading.js: // app/dashboard/loading.js export default function Loading() { return ( ); } error.js: // app/dashboard/error.js 'use client'; export default function Error({ error, reset }) { return ( Something went wrong! {error.message} Try again ); } Layout и Template layout.js (сохраняет state): // app/dashboard/layout.js export default function DashboardLayout({ children }) { return ( {children} ); } template.js (пересоздается): // app/dashboard/template.js export default function DashboardTemplate({ children }) { // Пересоздается при каждой навигации return {children} ; } Metadata API Static metadata: // app/about/page.js export const metadata = { title: 'About Us', description: 'Learn more about our company', openGraph: { title: 'About Us', description: 'Learn more about our company', images: ['/og-image.png'] } }; Dynamic metadata: // app/posts/[id]/page.js export async function generateMetadata({ params }) { const post = await getPost(params.id); return { title: post.title, description: post.excerpt, openGraph: { images: [post.coverImage] } }; } Route Handlers (API Routes) // app/api/users/route.js import { NextResponse } from 'next/server'; export async function GET(request) { const users = await db.user.findMany(); return NextResponse.json(users); } export async function POST(request) { const body = await request.json(); const user = await db.user.create({ data: body }); return NextResponse.json(user, { status: 201 }); } // app/api/users/[id]/route.js export async function GET(request, { params }) { const user = await db.user.findUnique({ where: { id: params.id } }); if (!user) { return NextResponse.json( { error: 'User not found' }, { status: 404 } ); } return NextResponse.json(user); } Middleware // middleware.js import { NextResponse } from 'next/server'; export function middleware(request) { // Authentication const token = request.cookies.get('token'); if (!token && request.nextUrl.pathname.startsWith('/dashboard')) { return NextResponse.redirect(new URL('/login', request.url)); } // Rate limiting const ip = request.ip || 'unknown'; if (isRateLimited(ip)) { return NextResponse.json( { error: 'Too many requests' }, { status: 429 } ); } return NextResponse.next(); } export const config = { matcher: ['/dashboard/:path*', '/api/:path*'] }; Parallel Routes // app/dashboard/layout.js export default function Layout({ children, analytics, team }) { return ( {children} {analytics} {team} ); } // Структура: // app/dashboard/ // layout.js // page.js // @analytics/ // page.js // @team/ // page.js Intercepting Routes // Modal при клике, full page при прямой навигации // app/photos/ // page.js // [id]/ // page.js // (.)[id]/ // Перехватывает /photos/[id] // page.js // Показывает как modal // app/photos/(.)[id]/page.js export default function PhotoModal({ params }) { return ( ); } Partial Prerendering (Experimental) // next.config.js module.exports = { experimental: { ppr: true } } // Статический shell + динамические части export default function Page() { return ( <> {/* Static */} }> {/* Dynamic */} {/* Static */} ); } Migration от Pages Router Pages Router: // pages/posts/[id].js export async function getServerSideProps({ params }) { const post = await getPost(params.id); return { props: { post } }; } export default function Post({ post }) { return {post.title} ; } App Router: // app/posts/[id]/page.js export default async function Post({ params }) { const post = await getPost(params.id); return {post.title} ; } Best Practices Server Components по умолчанию — используйте 'use client' только когда нужно Fetch на верхнем уровне — React автоматически dedupe Suspense boundaries — для лучшего UX Server Actions — для mutations Кэширование — правильная стратегия revalidation Performance Benefits Zero bundle size для Server Components Faster Time to Interactive — меньше JS Streaming — progressive rendering Automatic code splitting — per route Заключение: React Server Components — фундаментальная смена архитектуры. Начните с Next.js App Router. Используйте Server Components по умолчанию. Применяйте Server Actions для mutations. Streaming + Suspense для лучшего UX. RSC — будущее React, адаптируйтесь сейчас.