React ekosistemiyle SPA, SSR ve SSG destekli modern frontend uygulamaları geliştiriyoruz. Next.js App Router ile sunucu bileşenleri, TanStack Query ile akıllı veri yönetimi ve Zustand ile lightweight state management tercih ediyoruz.
React'ın bileşen bazlı mimarisi, büyük ölçekli uygulamaları izole, test edilebilir ve yeniden kullanılabilir parçalara ayırarak yönetmeyi kolaylaştırır. Next.js 15'in App Router'ı ile Server Components, Streaming ve Parallel Routes gibi modern rendering stratejilerini aynı proje içinde harmanlıyoruz.
TanStack Query v5 ile sunucu durumunu önbellekleme ve senkronizasyon; Zustand ile minimal ama güçlü client-side state yönetimi; React Hook Form + Zod ile tip güvenli form validasyonu uyguluyoruz. Tüm kod tabanı TypeScript ile yazılır, CI'da strict mod kontrolleri zorunludur.
Core Web Vitals optimizasyonu, Image & Font optimizasyonu, Edge Runtime desteği ve next-intl ile çok dilli yapı; üretim kalitesindeki her projede standart olarak yer alır.
import {
useQuery,
useMutation,
useQueryClient,
keepPreviousData,
} from '@tanstack/react-query';
import { ordersApi } from '@/lib/api/orders';
import type { Order, OrderFilters, CreateOrderDto } from '@/types/order';
// ─── Query Keys ──────────────────────────────────────────
export const orderKeys = {
all: ['orders'] as const,
lists: () => [...orderKeys.all, 'list'] as const,
list: (f: OrderFilters) => [...orderKeys.lists(), f] as const,
details: () => [...orderKeys.all, 'detail'] as const,
detail: (id: string) => [...orderKeys.details(), id] as const,
};
// ─── useOrders Hook ──────────────────────────────────────
export function useOrders(filters: OrderFilters) {
return useQuery({
queryKey: orderKeys.list(filters),
queryFn: () => ordersApi.getAll(filters),
placeholderData: keepPreviousData,
staleTime: 30_000, // 30 sn taze kabul et
select: (data) => ({
orders: data.items,
total: data.total,
hasMore: data.page < data.totalPages,
}),
});
}
// ─── useCreateOrder Hook ─────────────────────────────────
export function useCreateOrder() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (dto: CreateOrderDto) => ordersApi.create(dto),
// Optimistic update: sipariş anında listeye eklenir
onMutate: async (newOrder) => {
await queryClient.cancelQueries({ queryKey: orderKeys.lists() });
const snapshot = queryClient.getQueryData<{ items: Order[] }>(
orderKeys.list({})
);
queryClient.setQueryData(orderKeys.list({}), (old: any) => ({
...old,
items: [
{ ...newOrder, id: 'temp-' + Date.now(), status: 'pending' },
...(old?.items ?? []),
],
}));
return { snapshot };
},
// Hata durumunda snapshot'a geri dön
onError: (_err, _vars, ctx) => {
if (ctx?.snapshot) {
queryClient.setQueryData(orderKeys.list({}), ctx.snapshot);
}
},
// Başarı veya hata sonrası listeyi yenile
onSettled: () => {
queryClient.invalidateQueries({ queryKey: orderKeys.lists() });
},
});
}
// ─── useDeleteOrder Hook ─────────────────────────────────
export function useDeleteOrder() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => ordersApi.delete(id),
onSuccess: (_data, deletedId) => {
// Detay cache'ini temizle
queryClient.removeQueries({ queryKey: orderKeys.detail(deletedId) });
// Liste cache'ini invalidate et
queryClient.invalidateQueries({ queryKey: orderKeys.lists() });
},
});
}
import { Suspense } from 'react';
import { notFound } from 'next/navigation';
import type { Metadata, NextPage } from 'next';
import { getServerSession } from '@/lib/auth/session';
import { ordersService } from '@/server/services/orders';
import { statsService } from '@/server/services/stats';
import { OrdersTable } from './_components/OrdersTable';
import { OrdersTableSkeleton } from './_components/OrdersTableSkeleton';
import { OrdersStats } from './_components/OrdersStats';
import { OrdersFilters } from './_components/OrdersFilters';
// ─── Metadata ────────────────────────────────────────────
export const metadata: Metadata = {
title: 'Siparişler | Dashboard',
description: 'Tüm sipariş yönetimi.',
};
// ─── Props ───────────────────────────────────────────────
interface OrdersPageProps {
searchParams: Promise<{
page?: string;
status?: 'pending' | 'processing' | 'shipped' | 'delivered';
q?: string;
from?: string;
to?: string;
}>;
}
// ─── Page (async Server Component) ───────────────────────
const OrdersPage: NextPage<OrdersPageProps> = async ({ searchParams }) => {
const session = await getServerSession();
if (!session) notFound();
const params = await searchParams;
const page = Number(params.page ?? 1);
const status = params.status;
const query = params.q ?? '';
// Paralel veri çekimi — iki fetch aynı anda başlar
const [ordersData, statsData] = await Promise.all([
ordersService.getPaginated({
page,
perPage: 20,
status,
query,
dateFrom: params.from,
dateTo: params.to,
userId: session.user.id,
}),
statsService.getOrderSummary({ userId: session.user.id }),
]);
return (
<div className="space-y-6">
{/* Başlık */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold tracking-tight">Siparişler</h1>
<p className="text-muted-foreground mt-1">
Toplam {ordersData.total} sipariş listeleniyor.
</p>
</div>
</div>
{/* İstatistik kartları — server'da hesaplandı */}
<OrdersStats stats={statsData} />
{/* Filtreler (Client Component) */}
<OrdersFilters defaultValues={{ status, q: query }} />
{/* Tablo — Suspense ile streaming */}
<Suspense
key={`${page}-${status}-${query}`}
fallback={<OrdersTableSkeleton rows={20} />}
>
<OrdersTable
initialData={ordersData}
page={page}
filters={{ status, query }}
/>
</Suspense>
</div>
);
};
export default OrdersPage;
import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { authApi } from '@/lib/api/auth';
// ─── Types ───────────────────────────────────────────────
interface User {
id: string;
email: string;
name: string;
role: 'admin' | 'manager' | 'staff';
avatar?: string;
tenantId: string;
}
interface AuthState {
user: User | null;
token: string | null;
isLoading: boolean;
error: string | null;
}
interface AuthActions {
login: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
updateProfile: (data: Partial<Pick<User, 'name' | 'avatar'>>) => void;
clearError: () => void;
}
type AuthStore = AuthState & AuthActions;
// ─── Store ───────────────────────────────────────────────
export const useAuthStore = create<AuthStore>()(
devtools(
persist(
immer((set) => ({
// Initial state
user: null,
token: null,
isLoading: false,
error: null,
// ── Actions ─────────────────────────────────────
login: async (email, password) => {
set((s) => { s.isLoading = true; s.error = null; });
try {
const { user, token } = await authApi.login({ email, password });
set((s) => {
s.user = user;
s.token = token;
s.isLoading = false;
});
} catch (err: any) {
set((s) => {
s.error = err?.response?.data?.message ?? 'Giriş başarısız.';
s.isLoading = false;
});
}
},
logout: async () => {
try { await authApi.logout(); } catch { /* sessizce geç */ }
set((s) => { s.user = null; s.token = null; });
},
updateProfile: (data) => {
set((s) => {
if (s.user) Object.assign(s.user, data);
});
},
clearError: () => set((s) => { s.error = null; }),
})),
{
name: 'auth-storage',
partialize: (s) => ({ user: s.user, token: s.token }), // sadece bu alanlar persist edilir
}
),
{ name: 'AuthStore' }
)
);
// ─── Selector Hooks (gereksiz re-render önleme) ───────────
export const useCurrentUser = () => useAuthStore((s) => s.user);
export const useIsAdmin = () => useAuthStore((s) => s.user?.role === 'admin');
export const useAuthToken = () => useAuthStore((s) => s.token);
export const useAuthLoading = () => useAuthStore((s) => s.isLoading);
export const useAuthError = () => useAuthStore((s) => s.error);
export const useAuthActions = () =>
useAuthStore((s) => ({
login: s.login,
logout: s.logout,
updateProfile: s.updateProfile,
clearError: s.clearError,
}));
'use client';
import { useFieldArray, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useCreateOrder } from '@/hooks/useOrders';
// ─── Zod Schema ──────────────────────────────────────────
const orderItemSchema = z.object({
productId: z.string().min(1, 'Ürün seçiniz'),
quantity: z.coerce.number().int().min(1, 'En az 1 adet giriniz').max(9999),
unitPrice: z.coerce.number().positive('Geçerli bir fiyat giriniz'),
note: z.string().max(200).optional(),
});
const orderSchema = z.object({
customerId: z.string().uuid('Geçerli bir müşteri seçiniz'),
deliveryAddress: z.object({
street: z.string().min(5, 'Cadde/Sokak zorunludur'),
city: z.string().min(2),
country: z.string().length(2, 'ISO 3166-1 alpha-2 kodu giriniz'),
zip: z.string().regex(/^\d{5}$/, 'Posta kodu 5 haneli olmalıdır'),
}),
priority: z.enum(['low', 'normal', 'high', 'urgent']),
items: z.array(orderItemSchema).min(1, 'En az bir ürün ekleyin'),
discount: z.coerce.number().min(0).max(100).optional(),
notes: z.string().max(500).optional(),
});
type OrderFormValues = z.infer<typeof orderSchema>;
// ─── Component ───────────────────────────────────────────
interface OrderFormProps {
customers: { id: string; name: string }[];
onSuccess?: () => void;
}
export function OrderForm({ customers, onSuccess }: OrderFormProps) {
const createOrder = useCreateOrder();
const form = useForm<OrderFormValues>({
resolver: zodResolver(orderSchema),
defaultValues: {
priority: 'normal',
items: [{ productId: '', quantity: 1, unitPrice: 0 }],
discount: 0,
},
});
const { fields, append, remove } = useFieldArray({
control: form.control,
name: 'items',
});
const onSubmit = async (values: OrderFormValues) => {
await createOrder.mutateAsync(values, {
onSuccess: () => {
form.reset();
onSuccess?.();
},
});
};
return (
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
{/* Müşteri */}
<div>
<label className="label">Müşteri</label>
<select
{...form.register('customerId')}
className="input"
>
<option value="">Seçiniz…</option>
{customers.map((c) => (
<option key={c.id} value={c.id}>{c.name}</option>
))}
</select>
{form.formState.errors.customerId && (
<p className="field-error">{form.formState.errors.customerId.message}</p>
)}
</div>
{/* Öncelik */}
<div>
<label className="label">Öncelik</label>
<select {...form.register('priority')} className="input">
<option value="low">Düşük</option>
<option value="normal">Normal</option>
<option value="high">Yüksek</option>
<option value="urgent">Acil</option>
</select>
</div>
{/* Sipariş Kalemleri */}
<div>
<div className="flex items-center justify-between mb-3">
<h3 className="font-semibold">Ürünler</h3>
<button
type="button"
onClick={() => append({ productId: '', quantity: 1, unitPrice: 0 })}
className="btn btn-ghost"
style={{ fontSize: '.85rem' }}
>
+ Kalem Ekle
</button>
</div>
{fields.map((field, idx) => (
<div key={field.id} className="grid grid-cols-4 gap-3 mb-3 items-start">
<input
{...form.register(`items.${idx}.productId`)}
placeholder="Ürün ID"
className="input col-span-2"
/>
<input
{...form.register(`items.${idx}.quantity`)}
type="number" min={1}
placeholder="Adet"
className="input"
/>
<div className="flex gap-2">
<input
{...form.register(`items.${idx}.unitPrice`)}
type="number" step="0.01"
placeholder="Fiyat"
className="input flex-1"
/>
{fields.length > 1 && (
<button
type="button"
onClick={() => remove(idx)}
className="btn btn-ghost"
aria-label="Kalemi sil"
>✕</button>
)}
</div>
{form.formState.errors.items?.[idx]?.productId && (
<p className="field-error col-span-4">
{form.formState.errors.items[idx]?.productId?.message}
</p>
)}
</div>
))}
{form.formState.errors.items?.root && (
<p className="field-error">{form.formState.errors.items.root.message}</p>
)}
</div>
{/* Gönder */}
<button
type="submit"
className="btn btn-primary btn-lg btn-full"
disabled={createOrder.isPending}
>
{createOrder.isPending ? 'Kaydediliyor…' : 'Siparişi Oluştur'}
</button>
</form>
);
}
Full-stack React framework. App Router, Server/Client Components, Streaming SSR, Edge Runtime ve built-in Image optimizasyonu.
Server state yönetiminin altın standardı. Akıllı önbellek, background refetch, infinite scroll ve optimistic updates.
Minimal ama güçlü global state. DevTools, persist ve immer middleware'leri ile production-ready.
Performanslı form yönetimi ve tip güvenli schema validasyonu. zodResolver ile sıfır boilerplate.
Bildirimsel animasyon kütüphanesi. Layout animations, shared element transitions ve scroll-driven efektler.
Erişilebilir, unstyled headless bileşenler. Tailwind CSS ile özelleştirilmiş shadcn/ui katmanı.
App Router destekli i18n çözümü. Server Component'larda çeviri, locale routing ve pluralization.
Tam entegre kimlik doğrulama. OAuth (Google, GitHub), credential, magic link ve JWT/session stratejileri.
Gerçek zamanlı metrik grafikleri, role-based erişim kontrolü ve ağır veri tablolarıyla yönetim panelleri.
Ürün katalogu, sepet, ödeme akışı ve kişiselleştirilmiş öneri sistemleriyle tam donanımlı alışveriş deneyimi.
Çok kiracılı mimari destekli, tenant bazlı özelleştirme ve karmaşık iş akışlarına sahip SaaS ürün UI'ları.
Hız odaklı statik sayfalar; A/B test entegrasyonu, CMS bağlantısı ve yüksek Lighthouse skoru.
Offline desteği, push notification, install prompt ve native-benzeri kullanıcı deneyimi sunan PWA'lar.
next-intl ile tam i18n: locale routing, RTL desteği, çeviri yönetimi ve SEO dostu URL yapıları.
Mevcut React projenizi Next.js App Router'a migrate edebilir ya da sıfırdan production-ready bir frontend mimarisi inşa edebiliriz.