React Server Components ile Modern Web Uygulamaları
React Server Components (RSC), React ekosisteminde devrim niteliğinde bir yenilik olarak karşımıza çıkıyor. Bu teknoloji, sunucu tarafı rendering ve client tarafı interaktiviteyi harmanlayarak, modern web uygulamalarının performans ve geliştirme deneyimini yeniden şekillendiriyor. Bu yazıda, RSC'nin derinlemesine incelemesini yapacak ve gerçek dünya uygulamalarında nasıl kullanılabileceğini öğreneceğiz.
React Server Components Nedir?
React Server Components, React uygulamalarının bir kısmını sunucu tarafında render etmeyi sağlayan yeni bir paradigmadır. Bu yaklaşım, geleneksel client-side rendering ile server-side rendering'in en iyi yanlarını birleştirir. RSC, React 18 ile birlikte gelen ve Next.js 13+ sürümlerinde varsayılan olarak desteklenen bir özelliktir.
Temel Kavramlar
- Zero-Bundle-Size Components: Server Components client bundle'a dahil edilmez
- Automatic Code Splitting: İstemci tarafı kod otomatik olarak bölünür
- Streaming Server Rendering: Sayfa parça parça render edilebilir
- Server-Side Data Access: Veritabanına doğrudan erişim
Temel Özellikler ve Implementasyon
// app/components/ServerComponent.tsx // Bu bir server component'tir - '.server.tsx' uzantısı ile de belirtilebilir async function ProductList() { // Sunucu tarafında direkt veritabanına erişebilirsiniz const products = await db.products.findMany({ include: { category: true, reviews: { take: 3, orderBy: { createdAt: 'desc' } } } }); return ( <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {products.map(product => ( <ProductCard key={product.id} product={product} reviews={product.reviews} category={product.category} /> ))} </div> ); } // app/components/ProductCard.tsx // Statik UI için server component function ProductCard({ product, reviews, category }) { return ( <div className="border rounded-lg p-4 shadow-sm"> <div className="aspect-w-16 aspect-h-9 mb-4"> <Image src={product.imageUrl} alt={product.name} fill className="object-cover rounded-md" /> </div> <h3 className="text-lg font-semibold">{product.name}</h3> <p className="text-sm text-gray-600">{category.name}</p> <div className="mt-2"> <ProductPrice price={product.price} discount={product.discount} /> </div> <div className="mt-4"> <ProductReviews reviews={reviews} /> </div> <AddToCartButton client productId={product.id} /> {/* Client Component */} </div> ); } // app/components/AddToCartButton.client.tsx // İnteraktif özellikler için client component 'use client'; import { useState } from 'react'; import { useCart } from '~/hooks/useCart'; interface AddToCartButtonProps { productId: string; } function AddToCartButton({ productId }: AddToCartButtonProps) { const [isLoading, setIsLoading] = useState(false); const { addToCart } = useCart(); const handleClick = async () => { try { setIsLoading(true); await addToCart(productId); toast.success('Ürün sepete eklendi'); } catch (error) { toast.error('Bir hata oluştu'); console.error('Add to cart error:', error); } finally { setIsLoading(false); } }; return ( <button onClick={handleClick} disabled={isLoading} className={` w-full mt-4 px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-medium transition duration-200 disabled:opacity-50 disabled:cursor-not-allowed `} > {isLoading ? ( <span className="flex items-center justify-center"> <Spinner className="w-4 h-4 mr-2" /> Ekleniyor... </span> ) : ( 'Sepete Ekle' )} </button> ); }
Server Components'in Avantajları ve Kullanım Senaryoları
1. Performans İyileştirmeleri ve Bundle Size Optimizasyonu
Server Components, bundle size'ı önemli ölçüde azaltır çünkü sunucu tarafında çalışan kodlar client bundle'a dahil edilmez. Bu özellikle büyük dependencies kullanan komponentler için çok önemlidir.
// Geleneksel yaklaşım - Tüm dependencies client'a gönderilir import { marked } from 'marked'; // 1MB import { prism } from 'prismjs'; // 2MB import { chartjs } from 'chart.js'; // 800KB import { pdfLib } from '@pdf-lib/full'; // 2.5MB // Server Component yaklaşımı - Dependencies sunucuda kalır async function ReportGenerator({ data, template }) { // Ağır işlemler sunucuda yapılır const markdown = await marked(template); const highlighted = prism.highlight(data.code, 'javascript'); const chart = await generateChart(data.metrics); const pdf = await generatePDF(data.content); return ( <div className="report-container"> <div dangerouslySetInnerHTML={{ __html: markdown }} /> <div className="code-block">{highlighted}</div> <div className="chart-container"> <Image src={chart} alt="Metrics Chart" /> </div> <a href={pdf} download> PDF İndir </a> </div> ); } // Yardımcı fonksiyonlar async function generateChart(metrics) { const chart = new Chart(/* ... */); return chart.toBase64Image(); } async function generatePDF(content) { const pdfDoc = await PDFDocument.create(); // ... PDF generation logic return pdfDoc.save(); }
2. Veri Erişimi, Güvenlik ve Database Operasyonları
Server Components, veritabanına doğrudan erişim sağlar ve hassas bilgileri client'a göndermeden işleyebilir. Bu özellik, güvenlik ve performans açısından büyük avantajlar sağlar.
// app/components/AdminDashboard.server.tsx async function AdminDashboard() { // Kompleks database sorguları ve joins const dashboardData = await prisma.$transaction([ prisma.user.count(), prisma.order.findMany({ where: { status: 'PENDING', createdAt: { gte: subDays(new Date(), 7) } }, include: { items: true, customer: { select: { name: true, email: true, membership: true } } } }), prisma.revenue.aggregate({ _sum: { amount: true }, where: { createdAt: { gte: startOfMonth(new Date()) } } }) ]); // Hassas verileri filtrele ve dönüştür const [userCount, pendingOrders, revenue] = dashboardData; const safeOrderData = pendingOrders.map(order => ({ id: order.id, customerName: order.customer.name, total: order.items.reduce((sum, item) => sum + item.price, 0), status: order.status, date: order.createdAt })); return ( <div className="admin-dashboard"> <DashboardStats userCount={userCount} orderCount={pendingOrders.length} revenue={revenue._sum.amount} /> <PendingOrdersList orders={safeOrderData} /> <RevenueChart data={safeOrderData} client /> </div> ); } // app/components/RevenueChart.client.tsx 'use client'; import { Line } from 'react-chartjs-2'; function RevenueChart({ data }) { const chartData = useMemo(() => ({ labels: data.map(order => format(order.date, 'MMM dd')), datasets: [{ label: 'Günlük Gelir', data: data.map(order => order.total), borderColor: 'rgb(75, 192, 192)', tension: 0.1 }] }), [data]); return ( <div className="p-4 bg-white rounded-lg shadow"> <h3 className="text-lg font-semibold mb-4">Gelir Grafiği</h3> <Line data={chartData} options={chartOptions} /> </div> ); }
3. SEO ve İlk Yükleme Performansı Optimizasyonu
Server Components, içeriği sunucu tarafında render ettiği için SEO açısından önemli avantajlar sağlar ve ilk yükleme performansını optimize eder.
// app/pages/blog/[slug]/page.tsx import { Metadata } from 'next'; interface BlogPostParams { params: { slug: string; }; } // Dinamik metadata generation export async function generateMetadata( { params }: BlogPostParams ): Promise<Metadata> { const post = await getBlogPost(params.slug); return { title: `${post.title} | Blog`, description: post.excerpt, openGraph: { title: post.title, description: post.excerpt, images: [post.coverImage], type: 'article', article: { publishedTime: post.publishedAt, authors: [post.author.name], tags: post.tags } }, twitter: { card: 'summary_large_image', title: post.title, description: post.excerpt, images: [post.coverImage] } }; } // Statik sayfa üretimi için paths export async function generateStaticParams() { const posts = await getAllBlogPosts(); return posts.map((post) => ({ slug: post.slug, })); } // Blog post sayfası async function BlogPost({ params }: BlogPostParams) { const post = await getBlogPost(params.slug); const relatedPosts = await getRelatedPosts(post.tags); return ( <article className="max-w-4xl mx-auto px-4 py-12"> <BlogHeader title={post.title} author={post.author} date={post.publishedAt} readingTime={post.readingTime} tags={post.tags} /> <div className="prose prose-lg dark:prose-invert mt-8"> <MDXContent content={post.content} /> </div> <div className="mt-12"> <RelatedPosts posts={relatedPosts} /> </div> <div className="mt-8"> <CommentSection client postId={post.id} /> </div> </article> ); }
Server Components ile Modern Uygulama Mimarisi
1. Hybrid Rendering ve Kompozisyon Stratejileri
Server ve Client Components'i etkili bir şekilde birleştirmek için stratejik yaklaşımlar ve kompozisyon patterns:
// app/layout/AppLayout.server.tsx async function AppLayout({ children }) { const user = await getCurrentUser(); const notifications = await getUnreadNotifications(user?.id); const preferences = await getUserPreferences(user?.id); return ( <div className="min-h-screen bg-gray-50 dark:bg-gray-900"> <Header> <Navigation items={navigationItems} /> {/* Server Component */} <SearchBar client /> {/* Client Component */} <UserMenu client user={user} notifications={notifications} /> {/* Client Component */} </Header> <Sidebar> <NavigationMenu items={navigationItems} /> {/* Server Component */} <ThemeToggle client /> {/* Client Component */} </Sidebar> <main className="flex-1"> <Suspense fallback={<PageSkeleton />}> {children} </Suspense> </main> <Footer /> {/* Server Component */} </div> ); } // app/components/UserMenu.client.tsx 'use client'; function UserMenu({ user, notifications }) { const [isOpen, setIsOpen] = useState(false); const menuRef = useRef(null); useClickOutside(menuRef, () => setIsOpen(false)); return ( <div ref={menuRef} className="relative"> <button onClick={() => setIsOpen(!isOpen)} className="flex items-center space-x-2" > <Avatar user={user} /> {notifications.length > 0 && ( <span className="absolute -top-1 -right-1 w-4 h-4 bg-red-500 rounded-full text-xs text-white flex items-center justify-center"> {notifications.length} </span> )} </button> <AnimatePresence> {isOpen && ( <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 10 }} className="absolute right-0 mt-2 w-64 bg-white dark:bg-gray-800 rounded-lg shadow-lg" > <UserMenuContent user={user} notifications={notifications} /> </motion.div> )} </AnimatePresence> </div> ); }
2. Veri Akışı, State Management ve Server Actions
Modern React uygulamalarında veri akışı ve state yönetimi stratejileri:
// app/lib/actions.ts 'use server'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; export async function updateUserProfile( userId: string, data: UpdateUserProfileData ) { try { // Form validation const validatedData = await updateUserProfileSchema.validate(data); // Database update await prisma.user.update({ where: { id: userId }, data: validatedData }); // Cache invalidation revalidatePath('/profile'); revalidatePath('/dashboard'); return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'An error occurred' }; } } // app/components/ProfileForm.client.tsx 'use client'; function ProfileForm({ user }) { const [isPending, startTransition] = useTransition(); const [error, setError] = useState<string | null>(null); const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); setError(null); const formData = new FormData(event.currentTarget); startTransition(async () => { const result = await updateUserProfile(user.id, { name: formData.get('name'), email: formData.get('email'), bio: formData.get('bio') }); if (!result.success) { setError(result.error); } }); }; return ( <form onSubmit={handleSubmit} className="space-y-6"> {error && ( <div className="p-4 bg-red-50 text-red-700 rounded-lg"> {error} </div> )} <fieldset disabled={isPending}> <div className="space-y-4"> <FormField label="İsim" name="name" defaultValue={user.name} /> <FormField label="E-posta" name="email" type="email" defaultValue={user.email} /> <FormField label="Hakkımda" name="bio" as="textarea" defaultValue={user.bio} /> </div> <button type="submit" className={` mt-6 w-full px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-medium transition duration-200 disabled:opacity-50 `} > {isPending ? 'Kaydediliyor...' : 'Kaydet'} </button> </fieldset> </form> ); }
İleri Seviye Optimizasyon Teknikleri
1. Streaming ve Suspense ile Progressive Rendering
// app/pages/dashboard/page.tsx export default async function DashboardPage() { return ( <div className="dashboard-container"> {/* Kritik UI hemen yüklenir */} <header className="dashboard-header"> <h1>Dashboard</h1> <QuickActions client /> </header> <div className="dashboard-grid"> {/* Ana metrikler öncelikli olarak yüklenir */} <Suspense fallback={<MetricsSkeleton />}> <DashboardMetrics /> </Suspense> {/* Grafik verisi daha sonra stream edilir */} <Suspense fallback={<ChartSkeleton />}> <RevenueChart /> </Suspense> {/* Tablo verisi en son yüklenir */} <Suspense fallback={<TableSkeleton />}> <RecentTransactions /> </Suspense> </div> </div> ); } // app/components/DashboardMetrics.tsx async function DashboardMetrics() { // Parallel data fetching const [revenue, orders, users] = await Promise.all([ getRevenueMetrics(), getOrderMetrics(), getUserMetrics() ]); return ( <div className="grid grid-cols-1 md:grid-cols-3 gap-6"> <MetricCard title="Toplam Gelir" value={formatCurrency(revenue.total)} trend={revenue.trend} icon={<CurrencyIcon />} /> <MetricCard title="Siparişler" value={orders.count} trend={orders.trend} icon={<ShoppingCartIcon />} /> <MetricCard title="Aktif Kullanıcılar" value={users.active} trend={users.trend} icon={<UsersIcon />} /> </div> ); }
2. Error Handling ve Recovery Stratejileri
// app/components/ErrorBoundary.client.tsx 'use client'; function ErrorBoundary({ children, fallback, onError, }: { children: React.ReactNode; fallback: React.ReactNode; onError?: (error: Error) => void; }) { const [error, setError] = useState<Error | null>(null); useEffect(() => { if (error && onError) { onError(error); } }, [error, onError]); if (error) { return ( <div className="error-container"> {typeof fallback === 'function' ? fallback(error) : fallback} <button onClick={() => setError(null)} className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg" > Yeniden Dene </button> </div> ); } return ( <ErrorBoundaryComponent onError={(error) => setError(error)} > {children} </ErrorBoundaryComponent> ); } // Kullanım örneği <ErrorBoundary fallback={error => ( <div className="p-4 bg-red-50 rounded-lg"> <h3 className="text-red-800 font-medium"> Bir hata oluştu </h3> <p className="text-red-600 mt-1"> {error.message} </p> </div> )} onError={(error) => { // Error reporting service'e gönder reportError(error); }} > <ComplexDataVisualization data={data} /> </ErrorBoundary>
Performans Monitoring ve Optimizasyon
1. Core Web Vitals ve Real User Monitoring
// app/components/PerformanceMonitor.client.tsx 'use client'; function PerformanceMonitor() { useEffect(() => { if (typeof window !== 'undefined') { // Core Web Vitals monitoring const vitalsObserver = new PerformanceObserver((list) => { list.getEntries().forEach((entry) => { const metric = { name: entry.name, value: entry.value, rating: getRating(entry.name, entry.value), timestamp: new Date().toISOString() }; // Metric'i analytics service'e gönder reportWebVital(metric); }); }); vitalsObserver.observe({ entryTypes: ['largest-contentful-paint', 'fid', 'cls', 'ttfb'] }); // Resource timing monitoring const resourceObserver = new PerformanceObserver((list) => { list.getEntries().forEach((entry) => { if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') { const timing = { name: entry.name, duration: entry.duration, startTime: entry.startTime, initiatorType: entry.initiatorType }; // API timing metriklerini kaydet reportApiTiming(timing); } }); }); resourceObserver.observe({ entryTypes: ['resource'] }); // Memory usage monitoring if ('memory' in performance) { setInterval(() => { const memory = { jsHeapSizeLimit: performance.memory.jsHeapSizeLimit, totalJSHeapSize: performance.memory.totalJSHeapSize, usedJSHeapSize: performance.memory.usedJSHeapSize }; reportMemoryUsage(memory); }, 60000); // Her dakika } return () => { vitalsObserver.disconnect(); resourceObserver.disconnect(); }; } }, []); return null; } // Yardımcı fonksiyonlar function getRating(metric: string, value: number): 'good' | 'needs-improvement' | 'poor' { switch (metric) { case 'LCP': return value <= 2500 ? 'good' : value <= 4000 ? 'needs-improvement' : 'poor'; case 'FID': return value <= 100 ? 'good' : value <= 300 ? 'needs-improvement' : 'poor'; case 'CLS': return value <= 0.1 ? 'good' : value <= 0.25 ? 'needs-improvement' : 'poor'; default: return 'good'; } }
2. Development Tools ve Debug Utilities
// app/utils/debug.ts export function createDebugger(namespace: string) { return { log: (...args: any[]) => { if (process.env.NODE_ENV === 'development') { console.log(`[${namespace}]`, ...args); } }, warn: (...args: any[]) => { if (process.env.NODE_ENV === 'development') { console.warn(`[${namespace}]`, ...args); } }, error: (...args: any[]) => { if (process.env.NODE_ENV === 'development') { console.error(`[${namespace}]`, ...args); } }, group: (label: string) => { if (process.env.NODE_ENV === 'development') { console.group(`[${namespace}] ${label}`); } }, groupEnd: () => { if (process.env.NODE_ENV === 'development') { console.groupEnd(); } }, time: (label: string) => { if (process.env.NODE_ENV === 'development') { console.time(`[${namespace}] ${label}`); } }, timeEnd: (label: string) => { if (process.env.NODE_ENV === 'development') { console.timeEnd(`[${namespace}] ${label}`); } } }; } // Kullanım örneği const debug = createDebugger('ServerComponents'); async function ComplexOperation() { debug.time('operation'); debug.group('Complex Operation'); try { debug.log('Starting operation...'); // ... işlemler debug.log('Operation completed'); } catch (error) { debug.error('Operation failed:', error); } finally { debug.groupEnd(); debug.timeEnd('operation'); } }
Sonuç
React Server Components, modern web uygulamaları geliştirme yaklaşımımızı önemli ölçüde değiştiriyor. Bu teknoloji:
- Bundle size optimizasyonu
- Daha iyi SEO
- Gelişmiş performans
- Güvenli veri erişimi
- Daha iyi geliştirici deneyimi
- Progressive enhancement
- Streaming rendering
- Otomatik code splitting
gibi avantajlar sunarak, React ekosistemini daha da güçlendiriyor.
Bu yaklaşımları kendi projelerinizde kullanarak:
- Daha hızlı sayfa yüklemeleri
- Daha iyi kullanıcı deneyimi
- Daha az sunucu maliyeti
- Daha kolay bakım
- Daha iyi SEO
elde edebilirsiniz.
İlgili Etiketler: #React #ServerComponents #NextJS #WebDevelopment #Performance #Frontend #ReactPatterns #WebOptimization