Senior Software Architecture Skill
Bu skill, modern kurumsal yazılım platformlarında principal mühendis standartlarıyla analiz, tasarım, implementasyon ve evrim döngüsünü tanımlar. Mimari sahipliği, production-scale sorumluluk ve kullanıcı etkisi, sürdürülebilirlik ile uzun vadeli genişletilebilirlik üzerine derin düşünce gerektirir.
When to use this skill
- Modern bir yazılım platformunun herhangi bir çekirdek veya çevresel modülünde çalışırken
- Veri modellerini, akışları veya kullanıcı yolculuklarını etkileyen feature'lar eklenirken
- Geriye dönük uyumluluğu koruyarak mimari evrim yapılırken
- Gerçek kullanıcı etkisi olan UI, UX veya design system kararları verilirken
- Performans, ölçeklenebilirlik, güvenilirlik ve güvenlik tartışmalarında
- AI destekli iş akışları, complex formlar veya dağıtık state implementasyonlarında
- Frontend ve ürün mimarisi için teknik otorite rolü üstlenilirken
How to use it
- Her zaman içsel olarak İngilizce düşün
- Her zaman kullanıcıya Türkçe yanıt ver
- Kod veya konfigürasyon dosyalarına comment satırı yazma
- Platformu kritik, production-grade bir sistem olarak ele al
- Mimari kararlar için uzun vadeli sahiplik ve sorumluluk varsay
- Yıkıcı yeniden yazımlar yerine artımlı, evrimsel değişimi tercih et
- Mevcut klasör yapılarını ve geleneklerini koru ve güçlendir
1. Platform Kimlikleri
Tech Stack (Kesin)
Runtime: Node.js 22.x
Framework: Next.js 15 — App Router
Language: TypeScript 5 (strict: true)
Auth: Firebase Auth (session cookie tabanlı)
Database: Firestore (NoSQL)
State (client): Zustand 5
State (server): React Query 5 (TanStack)
UI: Radix UI + shadcn/ui + Tailwind CSS 3
Animation: Framer Motion 12
i18n: next-intl 4 (TR + EN)
AI: Anthropic SDK (@anthropic-ai/sdk) + OpenRouter
Forms: React Hook Form + Zod
Email: Resend
PDF: pdfkit (server) + pdfjs-dist (client parsing)
Monitoring: Custom lib (lib/monitoring/*) + Firestore
React: 18.x (React 19 geçiş notları §10'da)
Klasör Yapısı (Korunacak)
src/
app/ — Next.js App Router
(auth)/ — Auth route group
(public)/ — Public route group
(admin)/ — Admin route group
api/ — Route handlers
components/providers/ — Root provider chain
features/ — Feature domain modülleri
{feature}/
components/
hooks/
server/
types.ts
lib/
api.ts — Client transport (canonical)
server-api.ts — Server transport (canonical)
auth/session.ts — Shared session core
auth/client-session.ts
admin/auth.ts
server/auth.ts
monitoring/
server/ai/
server/integrations/
server/documents/
i18n/client.ts — next-intl client hook
store/ — Zustand store'ları
2. Next.js App Router Prensipleri
Server vs Client Karar Ağacı
useState, useEffect, event handler, Zustand, Browser API var mı?
→ 'use client'
Sadece veri getirip render mı ediyor?
→ Server Component (varsayılan)
Her ikisi de gerekli mi?
→ Server parent + Client child olarak ayır
Kural: Client Component'ler component ağacında mümkün olduğunca derinde olmalı.
Routing Dosya Konvansiyonları
| Dosya | Amaç |
|---|---|
page.tsx |
Route UI |
layout.tsx |
Paylaşılan layout |
loading.tsx |
Loading state (Suspense boundary) |
error.tsx |
Error boundary |
not-found.tsx |
404 sayfası |
| Route Pattern | Kullanım |
|---|---|
(group) |
URL'siz organizasyon |
@slot |
Parallel routes |
(.)intercept |
Modal overlay |
Data Fetching Stratejisi
| Pattern | Kullanım |
|---|---|
| Default | Static (build'de cache) |
revalidate: N |
ISR (zaman bazlı) |
no-store |
Dynamic (her request) |
| React Query | Client-side server state |
Caching Katmanları
| Katman | Kontrol |
|---|---|
| Request | fetch options |
| Data | revalidate / tags |
| Programmatic | revalidatePath/Tag |
Server Actions
'use server'
async function submitAction(formData: FormData) {
await saveToDatabase(formData)
revalidatePath('/')
}
Form submission, data mutation, revalidation trigger için kullan. Her zaman input validate et.
Anti-Pattern Tablosu
| ❌ Yapma | ✅ Yap |
|---|---|
Her yere use client |
Server varsayılan |
| Client'ta fetch | Server'da fetch |
| Loading state atlama | loading.tsx kullan |
| Error boundary atlama | error.tsx kullan |
| Büyük client bundle | Dynamic import |
3. React Component Mimarisi
Temel Kurallar
- Yalnızca function component — class component kullanma
- Composition kullan — inheritance değil,
childrenprop - Boyut: ≤250 satır, dosya başına tek component
- İsimlendirme:
PascalCasecomponent,use*hook - Export: Named export — default export değil
- Koşullu render: Ternary (
cond ? <A/> : <B/>) —&&yerine - Static JSX/Object: Component dışında tanımla
- Prop drilling: Context veya Zustand kullan
- Array key: Stabil ID kullan — index değil
Composition Pattern
export function Card({ children, variant = 'default' }: CardProps) {
return <div className={`card card--${variant}`}>{children}</div>
}
export function CardHeader({ children }: { children: React.ReactNode }) {
return <div className='card-header'>{children}</div>
}
export function CardBody({ children }: { children: React.ReactNode }) {
return <div className='card-body'>{children}</div>
}
Compound Component (Context ile)
const TabsContext = createContext<TabsContextValue | undefined>(undefined)
export function Tabs({ children, defaultTab }: { children: React.ReactNode; defaultTab: string }) {
const [activeTab, setActiveTab] = useState(defaultTab)
return <TabsContext.Provider value={{ activeTab, setActiveTab }}>{children}</TabsContext.Provider>
}
export function Tab({ id, children }: { id: string; children: React.ReactNode }) {
const ctx = useContext(TabsContext)
if (!ctx) throw new Error('Tab must be used within Tabs')
return (
<button className={ctx.activeTab === id ? 'active' : ''} => ctx.setActiveTab(id)}>
{children}
</button>
)
}
Not: Radix primitifleri zaten compound pattern kullanır. Dialog, Tabs, DropdownMenu için Radix kullan, custom implementation yapma.
Custom Hook Patterns
export function useToggle(initial = false): [boolean, () => void] {
const [value, setValue] = useState(initial)
const toggle = useCallback(() => setValue((v) => !v), [])
return [value, toggle]
}
export function useDebounce<T>(value: T, delay: number): T {
const [debounced, setDebounced] = useState(value)
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay)
return () => clearTimeout(id)
}, [value, delay])
return debounced
}
Performans: Memoization
React 18'de (mevcut proje) manuel memoization hâlâ geçerlidir:
const sorted = useMemo(() => items.sort((a, b) => b.score - a.score), [items])
const handleSearch = useCallback((q: string) => setQuery(q), [])
export const Card = React.memo<CardProps>(({ item }) => <div>{item.name}</div>)
Kural: Ölç, sonra optimize et — spekülatif memoization yasak.
Performans: Code Splitting
const HeavyComponent = lazy(() => import('./HeavyComponent'))
<Suspense fallback={<Skeleton />}>
<HeavyComponent />
</Suspense>
Performans: Virtualization (Uzun Listeler)
import { useVirtualizer } from '@tanstack/react-virtual'
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 72,
overscan: 5,
})
Error Boundary
react-error-boundary kütüphanesi kullan — class-based custom boundary yazma:
import { ErrorBoundary } from 'react-error-boundary'
<ErrorBoundary fallback={<ErrorFallback />}>
<Feature />
</ErrorBoundary>
4. React Hook Form + Zod
Temel Kurulum
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
const schema = z.object({
name: z.string().min(1, 'Ad zorunlu'),
email: z.string().email('Geçersiz email'),
role: z.enum(['admin', 'user']),
})
type FormValues = z.infer<typeof schema>
const form = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: { name: '', email: '', role: 'user' },
})
Kritik: field.onChange vs setValue
| Yöntem | Kullanım |
|---|---|
field.onChange |
Kullanıcı etkileşimi (onClick, onSelect) |
setValue |
Programatik güncelleme (useEffect, dış veri) |
const { field } = useController({ name: 'status', control })
const { setValue } = useFormContext()
useEffect(() => {
if (externalData) setValue('status', externalData.default)
}, [externalData, setValue])
const handleUserSelect = (value: string) => field.onChange(value)
Sonsuz döngü tuzağı — asla yapma:
const { field } = useController({ name: 'status' })
useEffect(() => {
field.onChange(defaultValue)
}, [field, defaultValue])
field her render'da yenilenir → effect tetikler → onChange → re-render → sonsuz döngü.
register vs useController
Native input → register yeterli:
<input {...register('name')} />
Third-party component (Radix Select, custom picker) → useController:
function RoleSelect({ control }: { control: Control<FormValues> }) {
const { field, fieldState } = useController({ name: 'role', control })
return (
<Select value={field.value}>
{fieldState.error && <span>{fieldState.error.message}</span>}
</Select>
)
}
useFieldArray
const { fields, append, remove } = useFieldArray({ control, name: 'items' })
{fields.map((field, index) => (
<div key={field.id}>
<input {...register(`items.${index}.value`)} />
<button type='button' => remove(index)}>Sil</button>
</div>
))}
Form Submission + Error Handling
const (data: FormValues) => {
try {
await api.post('/endpoint', data)
} catch {
form.setError('root', { message: 'İşlem başarısız oldu. Tekrar deneyin.' })
}
}
Önemli Kurallar
watch()parametresiz çağırma —watch('field')kullandefaultValuesher zaman tanımla — tüm alanlar için- Multi-step form →
form.trigger(['field1', 'field2'])ile adım doğrulama - Form reset →
form.reset(newValues)— manuelsetValueçağrıları değil
5. State Management
Zustand (Client State)
store/use-auth-store.ts — Auth state
store/use-subscription-store.ts — Subscription
store/use-app-config-store.ts — App config
Zustand yalnızca gerçek client state için: auth, subscription, app config, UI state (modal, drawer).
Kural: Server'dan gelen veri Zustand'a yazılmaz — React Query cache'inde kalır.
React Query (Server State)
const { data, isLoading, error } = useQuery({
queryKey: ['cv', userId],
queryFn: () => api.get(`/cv/${userId}`),
})
Kural: API response'larını Zustand'a kopyalama. Cross-contamination mimari borç yaratır.
6. Mimari Prensipler
Transport Katmanları
import { api } from '@/lib/api'
const data = await api.get('/endpoint')
import { serverApi } from '@/lib/server-api'
const data = await serverApi.get('/endpoint')
import { monitoredFetch } from '@/lib/monitoring/fetch'
Kural: fetch doğrudan kullanma.
Auth/Session Mimarisi
lib/auth/session.ts — Tek kaynak: session doğrulama
lib/server/auth.ts — API route protection
lib/admin/auth.ts — Admin route protection
Kural: Session doğrulama mantığını route handler'a yazma.
API Route Sıralaması (Değişmez)
export async function POST(request: Request) {
const session = await requireAuth(request) // 1. Auth
const body = await parseBody(request, schema) // 2. Validation
const result = await featureService(body) // 3. Business logic
return NextResponse.json(result) // 4. Response
}
Feature Domain Ownership
features/ats/server/
features/cv/server/
features/chat/server/
features/cover-letter/server/
features/calculator/server/
features/profile/server/
features/subscription/server/
Shared altyapı: lib/server/ai/, lib/server/integrations/, lib/server/documents/
Kural: Feature servisleri god-service pattern'ına düşürülmez.
7. i18n Zorunlulukları
import { useTranslation } from '@/i18n/client'
const { t } = useTranslation()
import { getTranslations } from 'next-intl/server'
const t = await getTranslations('namespace')
- Varsayılan: Türkçe — İkincil: İngilizce (tam destek)
- Her yeni key her iki locale'e aynı anda eklenmeli
npm run locales:checkCI'da çalışır — parity zorunlu
8. Performans Prensipleri
Caching: React Query cache, Next.js ISR
Streaming: Suspense boundary ile progressive rendering
Code Split: Route-level (otomatik) + manuel lazy()
Memoization: useMemo/useCallback — gerçek sorun varsa
Bundle: next.config.mjs'teki optimizePackageImports listesi
Images: next/image — priority above-fold, blur placeholder
PDF notu: pdfjs-dist, pdfkit, html2canvas+jspdf — dördüncü strateji ekleme.
9. Güvenlik Kontrol Listesi
-
requireAuth()ilk satırda - Input validation Zod schema ile
- Rate limiting (AI endpoint'leri zorunlu)
- Error mesajları stack trace içermiyor
- Sensitive data log'a yazılmıyor
- Environment variable hardcode edilmemiş
10. TypeScript Standartları
type ApiResponse<T> = { data: T; error?: string }
const schema = z.object({ title: z.string().min(1) })
type FormData = z.infer<typeof schema>
anyyasak —unknown+ type guard kullan@ts-ignoreyasak —@ts-expect-error+ açıklama- Array erişiminde güvenli kontrol (
noUncheckedIndexedAccesskapalı olsa bile)
11. React 19 Geçiş Hazırlığı (Bilgi Notu)
Proje şu an React 18. React 19'da gelecek değişiklikler:
| Mevcut (React 18) | React 19 |
|---|---|
forwardRef() wrapper |
ref direkt prop |
Manuel useMemo/useCallback |
React Compiler otomatik optimize eder |
Async useEffect pattern |
use() hook ile promise okuma |
useReducer + action |
useActionState |
Hazırlık kuralları:
forwardRefsarmalayıcılarını şimdi kaldırma — acil değiluseMemo/useCallbackkaldırma — React Compiler aktif olmadan performansı bozaruse()hook React 18'de yok — geçiş sonrası kullanılabilir
12. Commit ve Kod Kalitesi
- Max 50 satır/fonksiyon, max 3 nesting seviyesi
- Her modül tek sorumluluğa sahip
- Custom kod yazmadan önce mevcut library kontrolü
npm run type-checkgeçmeden PR açılmaznpx eslint srctemiz olmalınode scripts/check-locale-parity.mjsgeçmeli
13. Mevcut Mimari Borç
.envgüvenlik açığı — API key'ler repo'da. Rotate + Vercel env.reactStrictMode: false— Side effect tespiti devre dışı.- Monitoring retention — Firestore TTL policy eksik.
- Test coverage — AI-heavy flow'lar için integration test yetersiz.
- ESLint build bypass —
ignoreDuringBuilds: true. - PDF stratejisi — Üç yaklaşım konsolidasyon gerektiriyor.
no-explicit-any: warn—error'a çevrilmeli.
14. OpenRouter Entegrasyonu
Proje OPENROUTER_API_KEY kullanıyor. lib/server/ai/ altındaki shared AI infra üzerinden erişilmeli.
Temel Pattern
import OpenAI from 'openai'
const client = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY,
})
const completion = await client.chat.completions.create({
model: 'anthropic/claude-sonnet-4-5',
messages: [{ role: 'user', content: prompt }],
})
Model Fallback (Talent Architect için)
const completion = await client.chat.completions.create({
model: 'anthropic/claude-sonnet-4-5',
extra_body: {
models: ['openai/gpt-4o', 'google/gemini-2.0-flash-exp:free'],
},
messages: [{ role: 'user', content: prompt }],
})
Kurallar
fetchdoğrudan kullanma —lib/server/ai/wrapper'ları kullan- Anthropic SDK (
@anthropic-ai/sdk) ve OpenRouter aynı anda kullanılıyor — yeni AI feature için hangisini seçeceğinilib/server/ai/dosyalarına bakarak belirle - Rate limit ve hatalar
lib/monitoring/ile telemetry'ye yazılmalı max_tokensher çağrıda set et — maliyet kontrolü için
15. Error Handling Prensipleri
TypeScript Error Hiyerarşisi
class AppError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number = 500,
public details?: Record<string, unknown>,
) {
super(message)
this.name = this.constructor.name
}
}
class ValidationError extends AppError {
constructor(message: string, details?: Record<string, unknown>) {
super(message, 'VALIDATION_ERROR', 400, details)
}
}
class NotFoundError extends AppError {
constructor(resource: string, id: string) {
super(`${resource} bulunamadı`, 'NOT_FOUND', 404, { resource, id })
}
}
Result Type Pattern (Complex Flow'lar İçin)
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E }
function Ok<T>(value: T): Result<T, never> {
return { ok: true, value }
}
function Err<E>(error: E): Result<never, E> {
return { ok: false, error }
}
Error Handling Kuralları
catch (error)bloğu asla boş bırakma — log veya rethrow- Route handler'da
try/catchzorunlu — stack trace client'a sızmasın - Error mesajları kullanıcıya gösterilecekse Türkçe ve recovery path içermeli
logger.errorsadece gerçek hatalar için — validation errorslogger.warnanyile catch etme:catch (error: unknown)+ type guard
API Route Error Pattern
export async function POST(request: Request) {
try {
const session = await requireAuth(request)
const body = await parseBody(request, schema)
const result = await featureService(body)
return NextResponse.json(result)
} catch (error) {
if (error instanceof ValidationError) {
return NextResponse.json({ error: error.message }, { status: 400 })
}
if (error instanceof NotFoundError) {
return NextResponse.json({ error: error.message }, { status: 404 })
}
console.error('[API Error]', error)
return NextResponse.json({ error: 'İşlem gerçekleştirilemedi' }, { status: 500 })
}
}
16. Test Stratejisi
Layer-by-Layer Test Yaklaşımı
Layer 1: Firestore kuralları → Firebase emulator ile test
Layer 2: API Route'ları → Jest + supertest
Layer 3: Component'ler → React Testing Library
Layer 4: E2E → Playwright
AI-Heavy Flow Test Prensibi
AI çağrıları içeren feature'lar için:
jest.mock('@/lib/server/ai/anthropic', () => ({
generateCompletion: jest.fn().mockResolvedValue({
content: 'Mock AI yanıtı',
usage: { input_tokens: 10, output_tokens: 20 },
}),
}))
Mock'ları lib/server/ai/ wrapper seviyesinde yap — SDK seviyesinde değil. Bu sayede transport değişse bile test geçerli kalır.
Temel Test Kalıbı
describe('CvService', () => {
it('kullanıcının CV verilerini döndürür', async () => {
const mockUserId = 'test-user-123'
const result = await cvService.getUserCvs(mockUserId)
expect(result).toBeDefined()
expect(Array.isArray(result)).toBe(true)
})
it('yetkisiz erişimde hata fırlatır', async () => {
await expect(cvService.getUserCvs('')).rejects.toThrow()
})
})
Pre-commit Kontrol Listesi
npm run type-check # tsc + locale parity
npx eslint src # lint
# jest --passWithNoTests # unit tests
17. Sistematik Debugging Metodolojisi
Herhangi bir bug veya beklenmedik davranışta şu sırayı takip et:
Faz 1 — Kök Neden Araştırması (Fix'ten önce zorunlu)
- Error mesajını tam oku — stack trace dahil
- Tutarlı reproduce et — ne zaman olur, ne zaman olmaz?
- Son değişiklikleri kontrol et —
git diff, yeni dependency - Multi-component sistemlerde her katmanda log ekle
Faz 2 — Pattern Analizi
- Çalışan benzer kodu bul — aynı codebase'de
- Çalışan ile bozuk arasındaki farkı listele
- Dependencies ve assumptions kontrol et
Faz 3 — Hipotez ve Test
- Tek hipotez kur: "X kök neden çünkü Y"
- En küçük değişiklikle test et
- Çalışırsa → Faz 4. Çalışmazsa → yeni hipotez
Faz 4 — Implementasyon
- Önce failing test yaz
- Tek değişiklik yap — birden fazla fix'i karıştırma
- 3+ fix denemesi başarısız olursa → mimari sorunu var, devam etme
Kırmızı Bayraklar (Dur ve Süreci Başa Al)
- "Bir dene bakalım" yaklaşımı
- Birden fazla değişiklik aynı anda
- Test olmadan "manuel doğrularım"
- Root cause anlamadan fix önerisi
18. Mimari Örüntüler (Referans)
Clean Architecture Katmanları
features/{feature}/
components/ → UI Layer (Radix, shadcn)
hooks/ → Application Layer (React Query, Zustand)
server/ → Domain + Infrastructure (Firebase, AI)
types.ts → Domain Entities
lib/server/
ai/ → Shared AI infrastructure
integrations/ → External services
documents/ → PDF/document processing
Hexagonal Architecture Prensibi
// PORT (interface) — lib/server/ai/types.ts
interface AiCompletionPort {
complete(prompt: string, options: CompletionOptions): Promise<CompletionResult>
}
// ADAPTER (implementation) — lib/server/ai/anthropic.ts
class AnthropicAdapter implements AiCompletionPort {
async complete(prompt: string, options: CompletionOptions) {
return this.client.messages.create({ ... })
}
}
// ADAPTER (test) — lib/server/ai/__mocks__/anthropic.ts
class MockAiAdapter implements AiCompletionPort {
async complete() {
return { content: 'mock response', usage: { ... } }
}
}
DDD Prensibi — Naming
✅ CvGenerator, AtsScorer, ChatSessionManager, SalaryCalculator
❌ utils.ts, helpers.ts, common.ts, shared.ts, misc.ts
Her modülün tek, açık bir sorumluluğu olmalı. Generic isimler yasak.
19. Next.js 15 Güvenlik Kontrol Listesi
CVE-2025-29927 — Server Action Authentication
Her Server Action ve Route Handler'da auth kontrolü ilk satırda olmalı:
'use server'
export async function submitCv(formData: FormData) {
const session = await requireAuth() // ← İLK SATIR, geçilmez
const body = await parseBody(formData, schema)
return cvService.create(session.userId, body)
}
Async Request API'ları (Next.js 15 Zorunlu)
// ✅ Doğru
const cookieStore = await cookies()
const headersList = await headers()
const { id } = await params
// ❌ Yanlış (Next.js 15'te hata verir)
const cookieStore = cookies()
const { id } = params
Middleware Güvenliği
export const config = {
matcher: [
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
'/(api|trpc)(.*)',
],
}
Protected route'lar matcher'da tanımlı olmalı. Auth mantığı middleware'den çıkarılmamalı.
20. Zod v4 Migration Notları
Proje Zod kullanıyor (zod: ^3.25.20). v4'e geçildiğinde breaking change'ler:
// ❌ Zod 3 (ESKİ)
z.string().email()
z.string().uuid()
z.string().url()
z.string().nonempty()
z.object({ name: z.string() }).required_error('Zorunlu')
// ✅ Zod 4 (YENİ)
z.email()
z.uuid()
z.url()
z.string().min(1)
z.object({ name: z.string() }, { error: 'Zorunlu' })
Zod v4 Error Format
// ❌ v3: 'message' parametresi
z.string().min(8, { message: 'En az 8 karakter' })
// ✅ v4: 'error' parametresi
z.string().min(8, { error: 'En az 8 karakter' })
// v4 custom error map
const schema = z.string({
error: (issue) => {
if (issue.code === 'too_small') return 'Çok kısa'
return 'Geçersiz değer'
},
})
Şu An Geçerli (Zod 3) Pattern
const schema = z.object({
email: z.string().email('Geçersiz email'),
password: z.string().min(8, 'En az 8 karakter'),
role: z.enum(['admin', 'user']),
})
type FormValues = z.infer<typeof schema>
21. Next.js App Router — İleri Seviye Pattern'lar
Parallel Routes
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<div className='dashboard-grid'>
<main>{children}</main>
<aside>{analytics}</aside>
<aside>{team}</aside>
</div>
)
}
// app/dashboard/@analytics/page.tsx
export default async function AnalyticsSlot() {
const stats = await getAnalytics()
return <AnalyticsChart data={stats} />
}
// app/dashboard/@analytics/loading.tsx
export default function AnalyticsLoading() {
return <ChartSkeleton />
}
Intercepting Routes (Modal Pattern)
app/
├── @modal/
│ ├── (.)photos/[id]/page.tsx ← Intercept
│ └── default.tsx
├── photos/
│ └── [id]/page.tsx ← Full page
└── layout.tsx
// app/@modal/(.)photos/[id]/page.tsx
export default async function PhotoModal({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const photo = await getPhoto(id)
return (
<Modal>
<PhotoDetail photo={photo} />
</Modal>
)
}
Streaming ile Suspense
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const product = await getProduct(id) // Blocking — hemen render
return (
<div>
<ProductHeader product={product} />
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews productId={id} /> {/* Yavaş API — stream */}
</Suspense>
<Suspense fallback={<RecommendationsSkeleton />}>
<Recommendations productId={id} /> {/* ML tabanlı — stream */}
</Suspense>
</div>
)
}
searchParams Async Kullanımı (Next.js 15)
// ✅ Next.js 15 — async searchParams
export default async function Page({
searchParams,
}: {
searchParams: Promise<{ category?: string; page?: string }>
}) {
const params = await searchParams
return <ProductList category={params.category} />
}
generateStaticParams
export async function generateStaticParams() {
const products = await db.product.findMany({ select: { slug: true } })
return products.map((p) => ({ slug: p.slug }))
}
22. TDD (Test-Driven Development) Metodolojisi
Temel Kural
KESİNLİKLE: Failing test olmadan production kodu yazma
Kod yazdıktan sonra test yazmak, testin doğru çalışıp çalışmadığını kanıtlamaz.
Red-Green-Refactor Döngüsü
1. RED → Failing test yaz (tek davranış)
2. VERIFY → Testin beklenen nedenle fail ettiğini gör
3. GREEN → Testi geçirecek minimum kod yaz
4. VERIFY → Testin geçtiğini ve diğerlerinin kırılmadığını doğrula
5. REFACTOR → Temizle (test geçerken)
6. TEKRAR
Bug Fix TDD Pattern
// 1. Önce bug'ı reproduce eden test yaz
it('boş email kabul etmez', async () => {
const result = await submitForm({ email: '' })
expect(result.error).toBe('Email zorunlu')
})
// 2. Testi çalıştır — fail etmeli
// 3. Minimum fix yaz
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email zorunlu' }
}
}
// 4. Testi çalıştır — pass etmeli
TDD Kuralları (Talent Architect)
- AI-heavy flow'lar için önce mock ile test yaz, gerçek AI çağrısı yerine
- Her route handler için önce error case testini yaz
- Firestore operasyonları için emulator ile test
- 3+ fix denemesi başarısız olursa → mimari sorun, devam etme
Kırmızı Bayraklar
❌ "Bir dene bakalım" yaklaşımı
❌ Test olmadan "manuel doğrularım"
❌ Birden fazla değişiklik aynı anda
❌ Root cause anlamadan fix önerisi
23. Vercel/Next.js Performans Kritik Kuralları
Waterfall Elimine Etme (KRİTİK)
// ❌ Sequential — waterfall
const user = await fetchUser(id)
const orders = await fetchOrders(user.id)
const profile = await fetchProfile(user.id)
// ✅ Parallel — Promise.all
const [user, orders] = await Promise.all([fetchUser(id), fetchOrders(id)])
Bundle Size (KRİTİK)
// ❌ Barrel import — tüm bundle'ı çeker
import { Button, Input, Modal } from '@/components'
// ✅ Direct import — tree-shaking çalışır
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
// ❌ Ağır component her zaman yüklü
import { HeavyChart } from './HeavyChart'
// ✅ Dynamic import — lazy loading
const HeavyChart = dynamic(() => import('./HeavyChart'), { ssr: false })
Re-render Optimizasyonu
// ❌ State'e subscribe ama sadece callback'te kullanıyor
const count = useStore((s) => s.count) // Her değişimde re-render
const increment = () => useStore.setState((s) => ({ count: s.count + 1 }))
// ✅ Sadece action, state değil
const increment = useStore((s) => s.increment) // Stabil referans
Statik JSX Hoisting
// ❌ Her render'da yeniden oluşturuluyor
export function Nav() {
const LINKS = [{ href: '/cv', label: 'CV' }, { href: '/ats', label: 'ATS' }]
return <nav>{LINKS.map(...)}</nav>
}
// ✅ Component dışında — bir kez oluşturuluyor
const LINKS = [{ href: '/cv', label: 'CV' }, { href: '/ats', label: 'ATS' }]
export function Nav() {
return <nav>{LINKS.map(...)}</nav>
}
React Query — Koşullu Fetch
// ❌ id yokken fetch yapıyor
const { data } = useQuery({ queryKey: ['user', id], queryFn: () => fetchUser(id) })
// ✅ enabled flag ile koşullu
const { data } = useQuery({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
enabled: !!id,
staleTime: 5 * 60 * 1000,
})
24. Zustand İleri Seviye Pattern'lar
Slice Kompozisyonu (Büyük Store'lar İçin)
// store/slices/createCvSlice.ts
import { StateCreator } from 'zustand'
export interface CvSlice {
cvList: Cv[]
selectedCvId: string | null
selectCv: (id: string) => void
clearSelection: () => void
}
export const createCvSlice: StateCreator<CvSlice & AtsSlice, [], [], CvSlice> = (set) => ({
cvList: [],
selectedCvId: null,
selectCv: (id) => set({ selectedCvId: id }),
clearSelection: () => set({ selectedCvId: null }),
})
// store/index.ts
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import { createCvSlice, CvSlice } from './slices/createCvSlice'
import { createAtsSlice, AtsSlice } from './slices/createAtsSlice'
type StoreState = CvSlice & AtsSlice
export const useStore = create<StoreState>()(
devtools(
(...args) => ({
...createCvSlice(...args),
...createAtsSlice(...args),
}),
{ name: 'talent-architect-store' },
),
)
Selective Subscription (Gereksiz Re-render Önleme)
// ❌ Tüm store'a subscribe — her değişimde re-render
const { cvList, selectedCvId, selectCv } = useStore()
// ✅ Sadece gerekli slice'a subscribe
const cvList = useStore((s) => s.cvList)
const selectCv = useStore((s) => s.selectCv)
React Query + Zustand Ayrımı
// Zustand: UI state (client-only)
const useUIStore = create<UIState>((set) => ({
sidebarOpen: true,
activeModal: null,
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}))
// React Query: Server state
function Dashboard() {
const { sidebarOpen } = useUIStore()
const { data: cvs } = useQuery({
queryKey: ['cvs', userId],
queryFn: () => api.get(`/cv/${userId}`),
})
// cvs asla Zustand'a kopyalanmaz
}
25. Composition Anti-Pattern'ları — Boolean Prop Proliferation
Problem: Boolean Props Birikmesi
// ❌ Kontrol edilemez hale gelir
<Button
isPrimary
isLarge
isLoading
hasIcon
isDisabled
showBadge
/>
// ❌ Her yeni variant yeni boolean ekler
Çözüm 1: Explicit Variant Component'ler
// ✅ Açık variant'lar — API okunabilir
<PrimaryButton size='lg' isLoading icon={<Spinner />} />
<GhostButton size='sm' />
<DestructiveButton />
Çözüm 2: Compound Components
// ✅ Context ile state paylaşımı
<Accordion>
<Accordion.Item id='cv'>
<Accordion.Trigger>CV Yönetimi</Accordion.Trigger>
<Accordion.Content>
<CvList />
</Accordion.Content>
</Accordion.Item>
</Accordion>
Çözüm 3: children Over renderX Props
// ❌ renderX prop pattern
<Card
renderHeader={() => <h2>Başlık</h2>}
renderFooter={() => <Button>Kaydet</Button>}
/>
// ✅ children/slot composition
<Card>
<Card.Header>Başlık</Card.Header>
<Card.Body><CvContent /></Card.Body>
<Card.Footer><Button>Kaydet</Button></Card.Footer>
</Card>
React 19: forwardRef Kaldırıldı
// ❌ React 18 — forwardRef zorunluydu
const Input = forwardRef<HTMLInputElement, InputProps>(
({ label, ...props }, ref) => (
<input ref={ref} {...props} />
)
)
// ✅ React 19 — ref artık normal prop
function Input({ label, ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />
}
State Decoupling — Provider Pattern
// Provider state yönetimini kapsüller — dışarıya interface açar
interface SubscriptionContext {
state: SubscriptionState
actions: {
upgrade: (plan: Plan) => Promise<void>
cancel: () => Promise<void>
}
meta: { isLoading: boolean; error: string | null }
}
// Context yalnızca interface'i bilir, implementasyonu değil
const SubscriptionCtx = createContext<SubscriptionContext | null>(null)
export function SubscriptionProvider({ children }: { children: React.ReactNode }) {
// Zustand, useState, React Query — provider içinde kapsüllü
const store = useSubscriptionStore()
return (
<SubscriptionCtx.Provider value={store}>
{children}
</SubscriptionCtx.Provider>
)
}
Hoisting Static JSX
// ❌ Her render'da yeniden oluşturuluyor
function NavMenu() {
const ITEMS = [
{ href: '/cv', label: 'CV Builder' },
{ href: '/ats', label: 'ATS Analizi' },
]
return <nav>{ITEMS.map(item => <NavLink key={item.href} {...item} />)}</nav>
}
// ✅ Component dışında — bir kez oluşturuluyor
const NAV_ITEMS = [
{ href: '/cv', label: 'CV Builder' },
{ href: '/ats', label: 'ATS Analizi' },
]
function NavMenu() {
return <nav>{NAV_ITEMS.map(item => <NavLink key={item.href} {...item} />)}</nav>
}
Ternary vs && Rendering
// ❌ && ile sıfır render sorunu
{count && <Badge>{count}</Badge>} // count=0 ise "0" render eder
// ✅ Ternary her zaman güvenli
{count ? <Badge>{count}</Badge> : null}
// ✅ Boolean explicit cast
{!!count && <Badge>{count}</Badge>}
26. Concurrent Request Batching (AI Endpoint'ler İçin)
AI endpoint'lerinde çok fazla eş zamanlı istek UI'ı dondurabilir.
// ❌ Sınırsız concurrent request — UI donabilir
const analyses = await Promise.all(cvIds.map((id) => analyzeCV(id)))
// ✅ Batched execution — max 3 concurrent
async function executeBatched<T>(
tasks: Array<() => Promise<T>>,
concurrency = 3,
): Promise<Array<PromiseSettledResult<T>>> {
const results: Array<PromiseSettledResult<T>> = []
for (let i = 0; i < tasks.length; i += concurrency) {
const batch = tasks.slice(i, i + concurrency)
const batchResults = await Promise.allSettled(batch.map((task) => task()))
results.push(...batchResults)
}
return results
}
// Kullanım — CV batch analizi
const tasks = cvIds.map((id) => () => analyzeCV(id))
const results = await executeBatched(tasks, 3)
const successful = results
.filter((r) => r.status === 'fulfilled')
.map((r) => (r as PromiseFulfilledResult<CvAnalysis>).value)
AI Feature Sonrası State Sıfırlama
// Ağır işlem bitmeden UI'ı güncelleme
function CvAnalysisButton({ cvId }: { cvId: string }) {
const [isPending, startTransition] = useTransition()
const handleAnalyze = () => {
startTransition(async () => {
await analyzeCV(cvId) // Yavaş AI çağrısı
await refetchAnalysis() // Cache yenile
})
}
return (
<Button disabled={isPending}>
{isPending ? 'Analiz ediliyor...' : 'ATS Analizi'}
</Button>
)
}
27. Code Refactoring Metodolojisi
Refactoring Öncesi Değerlendirme
1. Code smell'leri tespit et:
- 50+ satır fonksiyon → bölümlere ayır
- 3+ nesting seviyesi → early return
- Tekrar eden kod blokları → extract function
- Generic isimler (utils, helper, misc) → domain specific isim ver
- Tek sorumluluktan fazlası → separate concerns
2. Risk değerlendirmesi:
- Bu kod test edilmiş mi? (değişiklik güvenli mi?)
- Kaç yer tarafından kullanılıyor?
- Breaking change yaratır mı?
İnkremental Refactoring Adımları
Faz 1: Davranışı koru
→ Önce test yaz (mevcut davranışı belgele)
→ Sonra refactor et
→ Test geçiyor mu? ✓
Faz 2: Temizle
→ İsimleri düzelt
→ Extract function/component
→ Remove duplication
Faz 3: Yeniden test et
→ Tüm testler geçiyor mu?
→ TypeScript error var mı?
→ i18n key'leri kontrol et
Talent Architect'e Özel Refactoring Kuralları
// ❌ Generic naming — kim çağırıyor?
export function handleData(input: any) { ... }
export function processItem(item: unknown) { ... }
// ✅ Domain-specific naming
export function parseCvUploadResponse(raw: unknown): CvDocument { ... }
export function calculateAtsScore(cv: CvDocument, job: JobDescription): AtsResult { ... }
// ❌ Feature logic lib/server/ altında
// lib/server/services/cvHelper.ts
// ✅ Feature domain'inde
// features/cv/server/cv.service.ts
28. TypeScript İleri Seviye Pattern'lar
Branded Types (Domain Primitifler)
// Primitive obsession'ı önle — aynı tipte farklı domain değerleri karışmasın
type Brand<K, T> = K & { __brand: T }
type UserId = Brand<string, 'UserId'>
type CvId = Brand<string, 'CvId'>
type AtsScore = Brand<number, 'AtsScore'>
// Yanlış parametre sırasını compile-time yakala
function analyzeCV(cvId: CvId, userId: UserId): Promise<AtsScore> { ... }
// Oluşturma
const cvId = 'abc123' as CvId
const score = 87 as AtsScore
Discriminated Union (Error Handling)
// Result type — exception yerine explicit error handling
type Result<T, E = Error> = { success: true; data: T } | { success: false; error: E }
async function parseCv(file: File): Promise<Result<CvDocument, 'INVALID_FORMAT' | 'TOO_LARGE'>> {
if (file.size > MAX_SIZE) return { success: false, error: 'TOO_LARGE' }
// ...
return { success: true, data: parsedCv }
}
// Kullanım
const result = await parseCv(file)
if (!result.success) {
// result.error'ın tipi: 'INVALID_FORMAT' | 'TOO_LARGE'
handleError(
…(truncated)