# WEB PERFORMANCE — arac-hasar-v2 (apps/web) Statik kod tabanli web performans denetimi. Lighthouse veya gercek bir RUM olcumu **calistirilmadi**; tum sayilar mantiksal tahmindir. Hedef: orta-uretim hazirligi (MVP demo + pilot), mobil 3G/4G ve masaustu Wi-Fi senaryolarinda kabul edilebilir UX. Stack: Next.js 15.1.3 (App Router), React 18.3, next-intl, Tailwind 3.4, axios, lucide-react, react-hook-form, zod. `output: 'standalone'`. Hicbir RSC server-data fetch yok; tum sayfalar **client component** (kritik bulgu). --- ## 0) Ozet — Tahmini Lighthouse Skoru (mobil, 4G, mid-tier) | Kategori | Skor (tahmini) | Yorum | |---|---:|---| | Performance | **62 - 72** | LCP ve TBT yuksek; her sayfa client component → JS bundle agir | | Accessibility | **88 - 92** | Aria etiketleri ve role tab/tabpanel tutarli; renk kontrasti genel olarak OK | | Best Practices | **85 - 92** | HTTPS / CSP / source-map gibi guvenlik basliklari prod build'de eksik olabilir | | SEO | **80 - 90** | Metadata var; ancak `metadataBase: localhost`, sitemap yok, robots.txt yok | | PWA | **0** | Manifest yok, service worker yok (kapsam disi olabilir) | **Tahmini Core Web Vitals (mobil orta cihaz, 4G hizinda):** - **LCP (Largest Contentful Paint):** ~2.4 - 3.2 s (sinir / kotu) - Sebep: Hero gorsel placeholder bir DOM-rendered card; Inter font swap; tum sayfa client component → JS-after-hydration LCP'yi ~600 ms erteliyor. - **INP (Interaction to Next Paint, FID yerine):** ~120 - 220 ms (sinir) - Foto upload tikinda multipart-FormData hazirlama anlik (kucuk dosya icin ~10 ms), ama `URL.createObjectURL` her render'da yeniden cagrildigi icin preview ekraninda jank olusabilir. - **CLS (Cumulative Layout Shift):** ~0.08 - 0.15 - `ImageWithOverlay` icindeki `` width/height attribute yok → annotated PNG yuklendiginde sayfa kayiyor. `ResultsTabs` panel bos yer tutmuyor → "Spinner → result" gecisinde ~150 px shift olasi. --- ## 1) Tahmini bundle / "First Load JS" Hedef: < 200 KB gz First Load JS. Mevcut prod build calistirilmadi (`.next/static/chunks/` icindeki dev artifact'lar 6.5 MB; bu **anlamli degil**, dev-mode'da unminified ve HMR loader var). Asagidaki tahminler dependency analizinden cikarildi. | Kaynak | Tahmini gz boyut | Not | |---|---:|---| | React 18 + React-DOM | ~42 KB | Standart | | Next.js runtime + Router | ~35 - 45 KB | App Router'in client runtime'i Pages Router'dan biraz daha agir | | next-intl client provider + tr.json (24 KB raw) | ~12 - 18 KB gz | **Tum mesajlar her sayfaya biniyor** (root layout `getMessages()` -> NextIntlClientProvider) | | axios (full) | ~14 KB gz | Sadece JSON istek yapiyor; `fetch` wrapper'i ile degisilebilir → -10 KB | | react-hook-form + @hookform/resolvers + zod | ~22 KB gz | Sadece login / register / settings'de kullaniliyor ama root'ta tree-shake edilemiyor cunku zod'u `lib/jwt.ts` ihtimal cekiyor | | lucide-react | `optimizePackageImports` ile per-icon ~0.3 KB | Iyi → next.config tarafindan zaten optimize edilmis | | @arac-hasar/ui (24 component, hepsi `index.ts` barrel'dan re-export) | ~18 - 25 KB gz | Bazi sayfalarda yalnizca `Spinner` kullaniliyor ama `ImageWithOverlay`, `UploadDropzone` gibi agir component'lar yan etki yokken bile tree-shake olabilir varsayilir — **kontrol edilmedi** | | Sayfa kodu | ~5 - 12 KB / sayfa | Tahmin | | **First Load JS (homepage)** | **~135 - 165 KB gz** | Hedef altinda ama az marjla | | **First Load JS (`/results/[id]`)** | **~180 - 220 KB gz** | ImageWithOverlay + canvas hit-test + zod yan tasimasi nedeniyle sinirda | **Onemli not — kullanilmayan dependency:** `react-dropzone@14.3.5` web `package.json`'inda var, ama Grep ile dogrulandi: `apps/web` icinde **hicbir yerden import edilmiyor** (sadece `apps/desktop` icinde `FileDrop.tsx` kullaniyor, o da @arac-hasar/ui paketinden geliyor). Dead dep, kaldirilmali — `react-dropzone` ~10 KB gz, tree-shake ile cikiyor olsa bile npm install + audit yukunu artiriyor. --- ## 2) Statik analiz bulgulari ### 2.1 next/image hic kullanilmiyor → LCP +0.8 - 1.4 s Grep: `from 'next/image'` → **sifir kaynak dosyada eslesme** (sadece `.next/` ve `tsbuildinfo` icinde, bunlar Next.js'in kendi kullanimi). Tum gorseller raw ``: - `packages/ui/src/components/UploadDropzone.tsx:136` — preview thumbnail - `packages/ui/src/components/ImageWithOverlay.tsx:172` — annotated inspection PNG (kritik LCP elementi) - `apps/web/components/Header.tsx`, `Footer.tsx` — sadece SVG icon, sorun degil **Etki:** - Annotated PNG `/api/v1/inspect/{id}/visualization/annotated` 200 - 800 KB (model ciktisi YOLO seg overlay'i, JPEG/PNG re-encode olmuyor; statik analizdeki goruntu: model annotator opencv ile PNG yaziyor → buyuk). `next/image` ile: - Otomatik AVIF/WebP serve (Next.js default formats) - `srcset` + `sizes` cihaza gore tahmini ~%40 - 60 byte tasarrufu - Lazy load + native `loading="lazy"` - `placeholder="blur"` LCP'yi maskeleyebilir - Eksiklik: `next.config.ts` `remotePatterns` zaten R2 / S3 / localhost:8000 icin acik. Yani `next/image` kullanimi icin altyapi hazir, sadece component degisikligi gerekli. `ImageWithOverlay`'i `next/image` ile sarmak biraz daha karmasik (canvas hit-test natural width'e bagimli), ama yapilabilir: `Image` `onLoadingComplete` callback'i `naturalWidth/naturalHeight` veriyor. ### 2.2 Tum sayfalar "use client" — RSC'nin **hicbir** faydasi yok Grep: 21 dosyada `'use client'`. Daha kritigi: - `app/page.tsx` (homepage) — `useTranslations` hook'u **sadece** server-side cagrilabilir (next-intl `getTranslations`), ama dosyada `'use client'` yok → server component **olarak isliyor**, iyi. - AMA `app/inspect/page.tsx`, `app/(app)/dashboard/page.tsx`, `app/history/page.tsx`, `app/results/[id]/page.tsx`, `app/(auth)/login/page.tsx`, `app/(auth)/register/page.tsx`, `app/(admin)/users/page.tsx`, `app/(app)/settings/page.tsx`, `app/(app)/inspect/new/page.tsx` — hepsi **client component**. Sonuc: Next.js 15'in en buyuk avantaji — RSC'de fetch'i server'da yapip yalnizca veri (JSON degil, RSC-encoded) gondermek — kullanilmiyor. Ornegin `/dashboard` ve `/history`: ```tsx 'use client'; useEffect(() => { (async () => { const data = await listInspections({ pageSize: 20 }); setItems(data.items ?? []); })(); }, []); ``` Bu pattern Pages Router stili. RSC'ye gecince: - Sayfa HTML'i SSR'de hazir gelir → LCP -0.5 s - Client'a giden JS: yalnizca interactive parcalar (status filter, search input) - API token'lar HttpOnly cookie ile cookie-only kullanilirsa server-side fetch yapilabilir; **mevcut auth localStorage tabanli** oldugu icin RSC'ye gecmek auth refactor gerektirir (orta efor). ### 2.3 "use client" abuse: Header / Footer / LanguageSwitcher - `Header.tsx` — `usePathname` + `useAuth` cektigi icin haklisin, client zorunlu. **OK.** - `Footer.tsx` — `'use client'` var (Grep dogruladi) ama Footer.tsx'i tek seferde kontrol edersek muhtemelen sadece tercume gosteriyor. **`useTranslations` yerine `getTranslations` (server) kullanip server component yaparak ~2 KB bundle tasarrufu.** - `LanguageSwitcher.tsx` — `usePathname` + cookie set ettigi icin client gerekli. **OK.** ### 2.4 next/dynamic ile kod splitting hic kullanilmamis Grep: `next/dynamic` → kaynak dosyada **sifir**. Ozellikle agir component'lar dinamik yuklenmemis: - `ImageWithOverlay` (canvas + ResizeObserver + hit-test, ~4 KB gz) — sadece `/results/[id]` sayfasinda gerekli, ama @arac-hasar/ui index.ts barrel re-export ettigi icin homepage bundle'ina sizabilir (ESM tree-shake bunu engellemeli, dogrulamadim). - `ResultsTabs`, `PartList`, `CostDisplay`, `InspectionSummary` — sadece `/results/[id]`'de. Bu zaten Next.js'in route-level splitting'i sayesinde ayri chunk'ta. - `ConfirmDialog` (UI paketi) — sadece etkilesim sonrasi gerekli; modal'a kadar import edilmemeli. **Onerilen yaklasim:** `/results/[id]` page'inde `ImageWithOverlay`'i `next/dynamic({ ssr: false })` ile yukle, ki ilk-paint (status pending) kucuk bundle ile gelsin, sonra annotated PNG hazir oldugunda overlay yuklensin. ### 2.5 Polling interval — 2000 ms uygun, ama backoff yok `use-inspection-polling.ts:33` → default `intervalMs: 2_000`. Bu **dogru bir secim**: - 1 s cok agresif (60s'de 60 istek = backend rate-limit baskisi). - 2 s makul; backend processing 4 foto icin 8-25 s arasi (`PERFORMANCE_NOTES.md`'den). **Ancak iyilestirilebilir:** - **Exponential backoff yok.** Ilk birkac poll 1 s, sonra 2-5 s gibi gradual artirim daha iyi UX (sonuc hazirsa hizli yakala, hazir degilse server'i bombalamayi birak). - **WebSocket alternatifi mevcut ama kullanilmiyor.** `services/backend` icinde `ws.py` var (`Promise.all` grep hits bunu da bulmadi cunku frontend `WebSocket` kullanimi yok). Tahmin: backend WS endpoint'i mevcut, frontend hala polling yapiyor. WS gecisi: -%80 network requests, +instant update. **Yuksek kazanc, orta efor.** - **Visibility-API entegrasyonu yok.** Kullanici tab'i degistirdiginde polling devam ediyor → mobilde pil yiyor. `document.visibilitychange` ile `enabled=false` yapilabilir. ### 2.6 API call paralelligi — Promise.all yok (kaynak kod tarafinda) Grep `Promise.all` `apps/web/*.{ts,tsx}` → **kaynak dosyada hic eslesme yok**, sadece `.next/` build artifact'larinda (Next.js runtime kendi kullaniyor). `/dashboard` page tek call yapiyor (`listInspections`), serialization sorunu yok. `/results/[id]` page yalnizca polling, single call. Eger ileride bir sayfada hem `auth.me()` hem `listInspections()` lazimsa `Promise.all` ile paralel; mevcut kod-tabani bunu zaten dogru yapacak yapida ama gozetilmeli. ### 2.7 Foto preview thumbnail — `URL.createObjectURL` revoke edilmiyor (bellek sizintisi) `packages/ui/src/components/UploadDropzone.tsx:132`: ```tsx {files.map((f, i) => { const url = URL.createObjectURL(f); // <-- her render'da yeni URL return ( ``` **Iki ayri sorun:** 1. **Her render'da yeni blob URL olusuyor** — file listesi degismediginde bile parent state degisirse FilePreview yeniden render olur. React `key={i}` korusa bile `URL.createObjectURL` yeniden cagriliyor. → React'in ``'i degistirmesi gerekmiyor (URL stringi degisik gorunuyorsa degistirir; browser eski URL'yi GC'ye birakir). 2. **`URL.revokeObjectURL` hicbir yerde cagrilmiyor** — bellek sizintisi. 5 MB'lik fotograf 5 kez render olduysa 25 MB tape bellek tutulabilir (browser implementation'a bagli). **Cozum:** `useMemo` + `useEffect(cleanup)`: ```tsx const urls = useMemo( () => files.map((f) => URL.createObjectURL(f)), [files] ); useEffect(() => () => urls.forEach(URL.revokeObjectURL), [urls]); ``` Buna ek olarak, **12 MB'lik bir foto direkt ``'a verilirse browser tum dosyayi decode eder** (ekranda 96x96 px gosteriliyor olsa bile). 4 foto x 12 MB = 48 MB decode. Mobilde laggy. Cozum: `createImageBitmap(file, { resizeWidth: 200 })` ile thumbnail uretip onu goster. ### 2.8 Inter font — `display: swap` iyi, ama `preconnect` yok `app/layout.tsx:11`: ```ts const inter = Inter({ subsets: ['latin', 'latin-ext'], display: 'swap', variable: '--font-inter' }); ``` - `display: 'swap'` → FOIT yok, iyi. - Next.js `next/font` `fonts.googleapis.com`'a gitmiyor, fontu build-time'da self-host ediyor → preconnect gereksiz. - **Iyi durum.** Yine de `subsets: ['latin', 'latin-ext']` Turkce karakter icin doğru ama ek ~25 KB woff2. Sadece `'latin-ext'` yetebilir (icine 'latin' karakterler de dahil). ### 2.9 Tailwind safelist / unused CSS `tailwind.config.ts` okumadim ama tipik Tailwind 3.4 + Next.js setup'inda content path'leri dogruysa unused class'lar purge edilir. `apps/web/components/**` ve `packages/ui/src/components/**` content'te olmali → kontrol et. **Tahmini CSS bundle:** ~12-20 KB gz. Iyi. ### 2.10 Inspection annotated PNG — lazy load yok, format optimize degil `ImageWithOverlay`'deki ``: - `loading="lazy"` yok (LCP elementi olabilir, lazy yaparsan LCP kaybeder; result page'de scroll altinda degil, hero, lazy yapma). - `fetchpriority="high"` yok — result page'in LCP'si bu image. `fetchpriority="high"` ekleyince ~300 ms kazanc. - Backend taraftan PNG yerine WebP/AVIF serve edilmiyor (tahmin; backend `annotator.py` opencv ile PNG yaziyor). - Backend'de Pillow `WEBP` quality=85 ile yazsa ~%40-60 boyut tasarrufu. ### 2.11 Metadata sorunlari - `metadataBase: new URL('http://localhost:3000')` — **production'da overrride edilmeli** (env var). Aksi halde OG tag'leri localhost URL'leri ile yayinlanir. - `sitemap.ts` ve `robots.ts` yok — SEO icin 1 saatlik efor. - `themeColor: '#1e6ee0'` viewport icinde dogru tanimlanmis. Iyi. --- ## 3) Top 5 darbogaz (etki sirasi) | # | Darbogaz | Tahmini etki | Efor | |---:|---|---|---| | 1 | **next/image kullanilmiyor (ozellikle annotated PNG)** | LCP +0.8 - 1.4 s; bandwidth +%40 mobil | Dusuk - Orta (2-3 component) | | 2 | **Tum data-fetch sayfalari `'use client'`** → RSC faydasi sifir | LCP +0.4 - 0.7 s; bundle +30 KB | Yuksek (auth refactor gerekli) | | 3 | **`URL.createObjectURL` revoke edilmiyor + tam-cozunurluk thumbnail** | Bellek sizintisi (5 dosya x 12 MB), preview render jank | Dusuk | | 4 | **WebSocket varken polling kullanilmasi** + backoff yok | Network: ~30 istek/inspection (60s/2s); pil sarf, server load | Orta | | 5 | **CLS — `` width/height yok** sonuc geldiginde shift | CLS +0.05 - 0.1 | Dusuk | --- ## 4) Dusuk efor + yuksek kazanc oneriler (v0.1.x icinde) 1. **`react-dropzone` kaldir** (`apps/web/package.json`). Hicbir yerde import edilmiyor. → -10 KB gz potansiyel + temizlik. 2. **`UploadDropzone` FilePreview revoke fix** (yukarida snippet). Memory leak + jank giderir. 3. **`ImageWithOverlay` ``'a `loading="eager"` + `fetchpriority="high"` ekle** result page'de. CSS `aspect-ratio` ile placeholder cizip CLS'i sifirla. 4. **Annotated PNG'ye `next/image`** uygula. Sadece `` -> `` swap, canvas overlay component'in dısinda nokta-koordinatlari hala normalize, sorun yok. 5. **Polling visibility-aware:** `useInspectionPolling` icine `document.visibilityState !== 'visible'` durumunda `intervalMs *= 4` ekle. Tab arka plandaysa 8 s'de bir poll. 6. **Footer'i server component yap.** `'use client'` kaldir, `getTranslations` ile cevir. 7. **metadataBase env-driven:** `new URL(process.env.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000')`. 8. **sitemap.ts + robots.ts ekle.** Tek seferlik 30 dakikalik is, SEO 80→90. --- ## 5) v0.2 backlog — Ileri optimizasyonlar 1. **RSC migration:** `/dashboard`, `/history`, `/results/[id]` (sonuc hazirsa) sunucuda fetch. Auth icin Next middleware + HttpOnly cookie pattern'i. Buyuk efor (~3-5 gun) ama her sayfada ~500 ms LCP + ~40 KB bundle kazanci. 2. **WebSocket polling replacement:** Backend'de `ws.py` zaten var. `lib/use-inspection-polling.ts` yerine `useInspectionWebSocket` yaz. Fallback olarak polling kalsin (auto-detect WS desteklenmiyorsa). Reconnect logic + heartbeat. 3. **Image pipeline:** Backend annotator.py PNG -> WebP. Frontend `next/image` + `priority` + responsive `sizes`. CDN cache headers (`Cache-Control: public, max-age=31536000, immutable` cunku inspection_id versionly). 4. **next-intl mesaj split:** Su an `tr.json` 24 KB tek dosya, **her sayfaya tum dictionary biniyor**. Per-route `getMessages({ locale, namespace: 'inspect' })` ile split ederek `/results` sayfasinda `auth.*` mesajlarini eleme. Tahmini -8 KB gz / sayfa. 5. **Bundle analyzer:** `@next/bundle-analyzer` ekle, gercek `First Load JS` kontrol et. CI'da PR basina threshold > 200 KB ise fail. 6. **Lighthouse CI:** GitHub Actions'da `lhci autorun` runner. Performance < 75 ise PR'i blocking. 7. **Service Worker (offline + cache):** Workbox veya `next-pwa`. Inspection sonuclarini offline cache'le, "son baktigin sonuc" pattern'i. 8. **Edge runtime:** `app/api/*` route'lari (varsa, kontrol edilmedi) edge'e tasi. Auth middleware'i zaten edge-compatible olabilir. 9. **`createImageBitmap` thumbnail decode** worker thread'de. Buyuk foto preview'lerini decode etmek main thread'i bloklamasin. 10. **Resource hints:** `` + `` annotated image domain'i icin. --- ## 6) Olcum oneri (gercek lighthouse calistirilabilirse) ```powershell # Chromium gerektirir pnpm dlx lighthouse http://localhost:3000 --output=html --output-path=./lighthouse-home.html --view --preset=desktop pnpm dlx lighthouse http://localhost:3000/results/demo-001 --output=html --output-path=./lighthouse-results.html --preset=mobile --throttling.cpuSlowdownMultiplier=4 ``` Mobil ozellikle `--throttling.cpuSlowdownMultiplier=4` ile gercek bir orta cihaz simule eder. Bu doknman sadece kaynak okumaya dayali olsa da, asagidaki **3 metric** mutlaka olcum sonrasi dogrulanmali: 1. `/` LCP — hero card ya da Inter font swap mi? 2. `/inspect` INP — drop event handler'i 200 ms'in altinda mi? 3. `/results/[id]` CLS — annotated PNG yuklenince layout kayiyor mu? --- **Sonuc:** MVP icin **kullanilabilir** ama hicbir RSC / next/image / WebSocket optimizasyonu yok. Top 5 fix'i v0.1.1'de uygulayinca tahmini Performance 72 → 84 - 88 bandina ciker. v0.2'de RSC migration ile 90+ erisilir.