Skip to main content

Ne zaman: yeni proje kur; sıfırdan Next.js + Supabase uygulaması ya da erken aşama temeli · Girdi: {{proje_yolu}} {{proje_adi}} {{urun_turu}} {{diller}} {{uygula}} {{musteri}} {{duzelt}} · Çıktı: proje kartı + M0-M5 + DoD · anahtar: kurulum, iskelet / new Next.js Supabase project, scaffold

0
Oct 30, 2026
v2

License: CC BY 4.0. The author shares the prompts of this repo under these terms.

Import with the CLI:ple import ple:zeynepaslierhan/ortak-web/4a8e3ca5
139 lines
# Web sıfırdan kurulum (Next.js + Supabase)

Hedef: {{#if proje_yolu}}`{{proje_yolu}}`{{else}}`.`{{/if}} içindeki {{#if proje_adi}}"{{proje_adi}}"{{else}}uygulama{{/if}} için profesyonel temeli BAŞTAN kur. {{#if uygula}}Mod UYGULA: M0→M5 sırayla yürüt; {{#if musteri}}her adımı ayrı yama (`git diff` çıktısı) ya da dal önerisi olarak ver, commit/push YOK{{else}}her adımı ayrı commit (yalnız açık dosya yolu) + o adımın doğrulama komutuyla bitir{{/if}}; kırmızıda sonraki adıma geçme.{{else}}Mod PLAN: hiçbir dosyayı değiştirme; aşağıdaki iskeletle yalnız plan üret.{{/if}}
{{#if musteri}}Müşteri projesi: commit/push YOK. Her adımı yama (diff) ya da dal önerisi olarak yaz; sonunda teknik olmayan müşteri özeti ekle.{{/if}}
{{#if duzelt}}Mevcut kodda P0/P1 bulursan (PLAN modunda da yalnız bunlar için) minimal diff + test ile düzelt{{#if musteri}} (yalnız yama önerisi){{/if}}; P2/P3'ü "Bilinçli ertelenenler"e yaz.{{/if}}
{{#if standart_dizini}}"{{standart_dizini}} içindeki index.md'yi oku, ilgili kural tablolarını da uygula."{{/if}}

Kurallar: `.env*` ve anahtar DEĞERİNİ okuma/yazdırma (yalnız ad + konum). Stage ederken yalnız açık dosya yolu ver. Sürüm/eşik uydurma; tarihli bilgiyi komutla teyit et. Tablolardaki `\|` markdown kaçışıdır, kabukta `|` yaz. Bu prompt denetim yapmaz: denetim M5'te "Web tam denetim ve teslim kapısı" ile koşar, oradaki maddeleri burada tekrar etme.

## Adım 0 — yığın tespiti
| Bak | Komut | Karar |
|---|---|---|
| Next sürümü + router | `node -p "require('next/package.json').version"` (kurulu sürüm; `package.json` aralığı değil); `ls app src/app pages 2>/dev/null` | Yeni proje: Next 16.x + App Router. 15.x ise geçiş planı yaz (15 hattının güvenlik desteği 2027-01-31'de biter; `https://nextjs.org/support-policy` ile teyit et) |
| Güvenlik tabanı | `npm view next version`; `npm audit --omit=dev --audit-level=high` | En güncel yamalı Next 16.x ile kur (2026-10-30 tabanı 16.4.1); sürüm/CVE eşiklerinin sahibi «Web güvenlik denetimi (Next.js + Supabase)» Adım 1, burada tekrar edilmez |
| Supabase | `jq '.dependencies' package.json \| grep supabase`; `ls supabase/migrations`; `supabase --version` | `@supabase/ssr` + `@supabase/supabase-js`; `auth-helpers` varsa ssr'a taşı |
| Auth yöntemi | `grep -rn "signInWith" src app 2>/dev/null` | e-posta+parola / magic link / OAuth sağlayıcıları listele |
| Barındırma | `ls vercel.json .vercel 2>/dev/null` | Vercel varsayımı; ödeme/reklam/ücretli müşteri işi varsa Hobby planda olmaz (Pro+) |
| i18n / ödeme | `grep -rlnE "next-intl\|i18n" --exclude-dir={node_modules,.next,.git} .`; `grep -rlnE "stripe\|lemonsqueezy\|iyzico\|paddle" --exclude-dir={node_modules,.next,.git} .` | Uygulanmayan maddeyi "Bilinçli ertelenenler"e "geçerli değil" notuyla yaz |
{{#if urun_turu}}Ürün türü: {{urun_turu}}.{{else}}Ürün türünü README ve route'lardan çıkar (saas · icerik · eticaret · panel · topluluk) ve gerekçesini tek satır yaz.{{/if}}
{{#if ortak_backend}}Backend ortak: şema, RLS, migration, Storage ve Supabase Auth ayarları `{{ortak_backend}}` reposunda; bu repo kiracı anahtarıyla (ör. `site_key`) bağlanır. 1.1, 1.3–1.8, 1.12–1.14'ün panel/şema kısmı, 4.3'ün `supabase` adımları, 4.5 ve 4.7'yi bu repoda UYGULAMA: her birini "sahip: {{ortak_backend}}" notuyla iş olarak backend sahibine yönlendir, DoD'de "— sahibinde" yaz. Bu repoda yalnız: kiracı anahtarı sunucu env'inden gelir (istemci/URL/çerezden değil); her okuma ve yazma kiracıyla kapsanır; tipler backend reposundan üretilir; 2.3 GET dahil her route'ta geçerli (RLS'e güvenip yetki atlanmaz). Doğrulama: yerelde env'e başka kiracının anahtarı verilince bu kiracının satırı 0; kiracı anahtarı istekten okunuyorsa P0.{{else}}Şema/RLS başka repodaysa (paylaşılan backend, kiracı anahtarı) bu promptu `--var ortak_backend=<backend repo>` ile yeniden çek.{{/if}}

## Proje kartı
| Alan | Değer |
|---|---|
| Ad · ürün türü | {{#if proje_adi}}{{proje_adi}}{{else}}(package.json name){{/if}} · (Adım 0 sonucu) |
| Yığın | Next (sürüm) · React (sürüm) · Supabase (Free/Pro) · barındırma + plan · ödeme sağlayıcısı ya da "yok" |
| Diller | {{#if diller}}{{diller}}{{else}}tek dil (varsayılan tr){{/if}} |
| Ortamlar | local → preview/staging (ayrı Supabase projesi ya da branch) → production; her birinin proje ref'i (yalnız ref, anahtar değil) |
| Alan adı | apex A + www CNAME, değerler Vercel proje domain kartından (doğrulama 4.8) |
| Canlı | {{#if canli_url}}{{canli_url}} — repo canlıdan ilerideyse (dağıtılmamış commit) kartta yaz{{else}}henüz yok{{/if}} |

## Kurulum sırası
Her satırı şu biçimde yaz: ne · neden (1 satır) · doğrulama (komut + beklenen).

### M0 — İskelet
| # | Ne | Neden | Doğrulama |
|---|---|---|---|
| 0.1 | Boş dizinde `npx create-next-app@latest <ad> --typescript --app --eslint --src-dir` (mevcut repoda atla); Node sürümünü `.nvmrc` + `engines` ile sabitle | Tekrarlanabilir kurulum | `npm ci && npx tsc --noEmit` hatasız |
| 0.2 | Klasörler: `src/app`, `src/components`, `src/lib/supabase/{server,client,proxy}.ts`, `src/lib/env.ts`, `supabase/migrations`, `tests`, `e2e`, `docs` | Sunucu/istemci sınırı yapısal görünür | `ls` çıktısı plana uyar |
| 0.3 | `docs/HARITA.md`: route listesi, tablolar, env ADLARI, dış servisler, kararlar | Yeni ajan/geliştirici 5 dakikada yön bulur | Dosya var; her route/tablo listede |
| 0.4 | `.env.example` (yalnız ad + açıklama), `.env*` `.gitignore`'da; `src/lib/env.ts` zod ile env'i açılışta doğrular | Eksik env build'de patlar, canlıda değil | `git check-ignore .env.local`; env silinince build kırmızı |
| 0.5 | `NEXT_PUBLIC_` yalnız tarayıcıya gidecek değerlerde; `npm i server-only`, sunucu modülleri `import 'server-only'`; istemcide env yalnız statik `process.env.NEXT_PUBLIC_X` yazımıyla okunur (`process.env[key]`, `const env = process.env`, `parse(process.env)` build'de inline edilmez → tarayıcıda undefined) | Sır istemci bundle'ına sızmaz; dinamik erişim sessizce boş döner | `grep -rn "NEXT_PUBLIC_" .env.example src \| grep -iE "SERVICE_ROLE\|SECRET\|ADMIN"` = 0; `grep -rlE "^[[:space:]]*['\"]use client" src \| xargs grep -nE 'process\.env\[\|= process\.env;?$\|\(process\.env\)'` = 0 (≠0 → P1; sunucu modülünde serbest; istemcinin import ettiği ortak modülü elle bak) |
| 0.6 | Server Component varsayılan; `'use client'` yalnız en dar yaprakta (page/layout'ta değil) | Bundle küçük, veri sunucuda | `grep -rlE "^[[:space:]]*['\"]use client" src/app --include=page.tsx --include=layout.tsx` = 0 (yorumdaki geçiş sayılmaz) |
| 0.7 | Dev sunucusu dışa açılmaz (`-H 0.0.0.0` ya da tünel yok) | 2026-09-30 Next.js güvenlik sürümü (CVE-2026-94486): dev sunucusu kaynak/route/log sızdırıyordu | `grep -nE "0\.0\.0\.0\|--hostname\|ngrok\|cloudflared" package.json` = 0 |
| 0.8 | Test tabanı: `vitest` + `@playwright/test` + `@axe-core/playwright`; `npx playwright install chromium`; `package.json`'da `test` ve `test:e2e` script'leri | CI kapısı ve smoke'un önkoşulu | `npx vitest run` ve `npx playwright test --list` çıkış kodu 0 |

### M1 — Veri + auth
| # | Ne | Neden | Doğrulama |
|---|---|---|---|
| 1.1 | Ortam başına ayrı Supabase projesi (ya da branch); geliştirme makinesinde prod anahtarı yok; prod anahtarı yalnız Vercel/CI secret store'unda | Yanlış ortama yazma ve sızıntı yarıçapı | Prod ref'ini `supabase projects list` ile al; `grep -c "<prod-ref>" .env.local` = 0 (yalnız sayı, değer yazdırılmaz); 0 değilse P0 |
| 1.2 | İstemcide yalnız `sb_publishable_`; `sb_secret_` yalnız sunucuda; legacy `eyJ…` anon/service_role kullanma (son tarih 2026-12-31; `supabase.com/docs/guides/api/api-keys` ile teyit et) | RLS bypass yalnız sunucuda mümkün kalır | `grep -rnE 'eyJ[A-Za-z0-9_-]{20,}' src .env.example` = 0; `next build` sonrası `grep -rl sb_secret_ .next/static` = 0 |
| 1.3 | Şema yalnız `supabase/migrations/<zaman>_<ad>.sql` ile; panelden açılan her şey repoya geri taşınır | Şema git'ten sıfırdan kurulur | `supabase start && supabase db reset` hatasız; `supabase db diff --linked` boş (panelde elle açılmış tablo/policy yok) |
| 1.4 | Migration idempotent (`if not exists`); fonksiyon imzası değişince önce `drop function`; geri dönüşsüz DDL iki deploy'a bölünür | İkinci koşu ve geri alma güvenli | Reset sonrası her dosyayı aynı yerel DB'ye ikinci kez uygula: `psql "$LOCAL_DB_URL" -v ON_ERROR_STOP=1 -f <dosya>` hata 0 (`LOCAL_DB_URL` = `supabase status` çıktısındaki yerel DB URL); `select proname,count(*) from pg_proc where pronamespace='public'::regnamespace group by 1 having count(*)>1` boş |
| 1.5 | Her tabloda migration içinde `enable row level security`; policy `to authenticated` + `(select auth.uid())`; filtre/FK kolonlarına indeks | Varsayılan kapalı veri; RLS performansı | `select relname from pg_class where relrowsecurity=false and relnamespace='public'::regnamespace and relkind='r'` = 0; `grep -nE 'auth\.uid\(\)' supabase/migrations/*.sql \| grep -v 'select auth.uid()'` = 0; Dashboard > Advisors: Security ERROR = 0, Performance'ta 0003 / 0006 / 0001 = 0 (ölçüm yolu: güvenlik denetimi 3.2) |
| 1.6 | Policy'ler `user_metadata`'ya bakmaz; rol/plan kolonları sütun düzeyinde GRANT ile korunur; view'lar `security_invoker = true` | Kullanıcı kendi yetkisini yükseltemez; view varsayılan RLS'i atlar | Kullanıcı oturumuyla `update ... set role='admin'` → permission denied; `grep -nE 'user_meta_data\|user_metadata' supabase/migrations/*.sql` policy satırında = 0; `select relname from pg_class where relkind='v' and relnamespace='public'::regnamespace and coalesce(array_to_string(reloptions,','),'') not like '%security_invoker=true%'` = 0 satır |
| 1.7 | Para `numeric(12,2)` ya da kuruş `bigint`; zaman `timestamptz` (UTC sakla, gösterimde yerel TZ) | Yuvarlama ve saat dilimi hatası | `information_schema.columns` içinde para kolonunda float = 0, `timestamp without time zone` = 0 |
| 1.8 | `supabase gen types typescript --local > src/lib/database.types.ts`; istemciler bu tiple | Şema-kod uyuşmazlığı derlemede yakalanır | Migration sonrası tip üret + `git diff --exit-code src/lib/database.types.ts` + `tsc --noEmit` yeşil |
| 1.9 | `@supabase/ssr`: `server.ts` (cookies), `client.ts` (tarayıcı), oturum yenileme `proxy.ts` ile (`export function proxy`, statik dosyaları dışlayan `matcher`; Next 16'da `middleware.ts` deprecated); dosya `app` ile aynı seviyede: `src/app` varsa `src/proxy.ts`, kökte DEĞİL (kökteki dosya sessizce yok sayılır: oturum yenilenmez, guard çalışmaz) | Oturum her istekte tazelenir | `ls middleware.ts src/middleware.ts` yok; `src/app` varsa `src/proxy.ts` var, kökte `proxy.ts` yok; oturumsuz `curl -s -o /dev/null -w '%{http_code}' <url>/<korumalı-sayfa>` = 3xx (200 + korumalı içerik → P0; 3xx sayfadaki `redirect()`'ten de gelir, proxy kanıtı değil); Playwright: giriş → sayfa yenile → oturum korunur |
| 1.10 | Sunucuda yetki kararı `getUser()` ya da `getClaims()`; `getSession().user` yetkide YOK; yetki proxy'de değil veri katmanında da kontrol edilir | Çerez sahteciliği ve proxy atlama | `grep -rn "getSession()" src` her eşleşme authz dışı |
| 1.11 | Auth yaşam döngüsü: kayıt, e-posta doğrulama, giriş, parola sıfırlama, e-posta değiştirme (iki uçlu onay), parola değiştirme, tüm oturumlardan çıkış (`signOut({ scope: 'global' })`), hesap silme (sunucu route'u: auth kullanıcısı + tablolar + Storage'daki kullanıcı klasörleri) | Eksik akış = destek talebi + KVKK silme hakkı | Playwright ile her akış uçtan uca; silme sonrası kullanıcıya ait satır ve dosya = 0 |
| 1.12 | Parola: en az 15 karakter (MFA varsa 8), en az 64 karakteri kırpmadan kabul, karmaşıklık kuralı ve periyodik değişim yok; planda varsa sızdırılmış parola koruması açık | NIST SP 800-63B-4 | Zod şeması + Supabase Auth min uzunluk ayarı aynı; 14 karakterlik parola kayıtta reddedilir, 64 karakterlik kabul edilir; parola şemasında karmaşıklık regex'i yok (`grep -rnF '(?=.*[' src` = 0) |
| 1.13 | OAuth/e-posta yönlendirme izin listesi: prod Site URL tam alan adı, preview yalnız kendi deseniyle; prod'da genel joker yok | Açık yönlendirme ve token sızıntısı | Supabase Auth > URL Configuration listesi kartta; `**` yalnız preview deseninde, `redirectTo=https://baska-alan.example` ile giriş reddedilir ya da Site URL'ye düşer |
| 1.14 | Storage bucket'ları varsayılan private; policy yol önekiyle `(storage.foldername(name))[1] = (select auth.uid())::text`; tür ve boyut sınırı | Başkasının dosyasına erişim | Kullanıcı A'nın oturumuyla B'nin yolunu oku → reddedilir |

1.9 ek kanıt (kabukta): `npm run build 2>&1 | grep -iE 'proxy|middleware'` → route listesinde Proxy satırı (çıktı metni sürüme göre değişebilir, doğrulanmadı; asıl kanıt yukarıdaki curl).

### M2 — Güvenlik
| # | Ne | Neden | Doğrulama |
|---|---|---|---|
| 2.1 | Başlıklar: HSTS `max-age=63072000; includeSubDomains`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`, kullanılmayan özellikler kapalı `Permissions-Policy` | OWASP Secure Headers tabanı | `curl -sI <url> \| grep -ciE '^(strict-transport-security\|x-content-type-options\|referrer-policy\|permissions-policy):'` = 4 |
| 2.2 | CSP: istek başına nonce `proxy.ts`'te, `script-src`'de `unsafe-inline` yok, `frame-ancestors 'none'`; önce `Report-Only`, ihlaller temizlenince zorunlu | XSS ve clickjacking; nonce'lu route dinamik render olur | `curl -sI <url> \| grep -i content-security-policy` iki kez: nonce farklı, `unsafe-inline` script-src'de yok; Playwright'ta konsol CSP ihlali 0 |
| 2.3 | Her Server Action ve route handler kendi içinde authn + authz + zod doğrulaması yapar; geçersiz girdi 400 | Action public HTTP endpoint'tir | UI'yi atlayıp curl ile oturumsuz POST → 401/403 |
| 2.4 | Hız sınırlama: auth, form, e-posta tetikleyen ve maliyetli uç noktalarda IP + hesap başına; aşımda 429; Supabase Auth'un kendi sınırlarını da gözden geçir | Kaba kuvvet ve fatura saldırısı | Yerel/preview'da sınır+1 istek döngüsü: `for i in $(seq 1 <sınır+1>); do curl -s -o /dev/null -w '%{http_code}\n' -X POST <url>/<uç>; done \| tail -1` → 429 |
| 2.5 | Sır taraması CI'da (gitleaks/trufflehog, git geçmişi dahil; içerik yazdırılmaz, yalnız dosya:satır); Dependabot security updates açık; CI'da `npm audit --omit=dev --audit-level=high` | Sızan sır silinmekle değil rotasyonla kapanır | CI adımı yeşil; `.github/dependabot.yml` var ya da repo ayarında açık |

### M3 — Ürün temeli
| # | Ne | Neden | Doğrulama |
|---|---|---|---|
| 3.1 | `not-found.tsx`, `error.tsx` (segment başına), `global-error.tsx`; yavaş veri yalnız ilgili bileşende `<Suspense>`; kökte (`app/loading.tsx`) ve `notFound()` çağıran segmentte `loading.tsx` YOK | Kırık ekran yerine kontrollü durum; stream başlayınca durum 200'de kilitlenir, sonraki `notFound()` 404 veremez | `ls src/app/loading.tsx app/loading.tsx 2>/dev/null` boş; `curl -s -o /dev/null -w '%{http_code}' <url>/olmayan-sayfa-xyz` ve `<url>/<dinamik-segment>/olmayan-slug-xyz` = 404 (200 → P1); her `page.tsx` dizininde ya da üst segmentte `error.tsx` var |
| 3.2 | SEO: kök layout `metadataBase` (env'den, fallback localhost DEĞİL) + `title.template`; her route title + description + tam `openGraph` (iç içe obje birleşmez, route'ta tam yaz); `alternates.canonical` route'ta (kök layout'ta değil: miras kalır, 404/utility sayfası ana sayfayı kanonik gösterir); `app/sitemap.ts` (yalnız 200 + indexlenebilir URL); `app/robots.ts` (Sitemap satırı; admin/auth sayfaları `noindex`) | Paylaşım kartı ve indeks baştan doğru | Prod build'de `curl -s <url> \| grep -E 'og:image\|og:url\|canonical'` hepsi `https://` + prod alanı |
| 3.3 | Hukuk sayfaları footer'dan: KVKK aydınlatma (veri sorumlusu, amaç, alıcılar, toplama yöntemi + hukuki sebep, m.11 hakları), gizlilik, kullanım şartları; satış varsa mesafeli satış sözleşmesi + ön bilgilendirme formu; veri toplayan her formda gönder düğmesinden önce aydınlatma linki. Hukuki danışmanlık değildir, metni uzman onaylar | Yasal asgari | 5/5 unsur başlığı grep; her formda link |
| 3.4 | Çerez onayı: envanter (zorunlu / rızaya bağlı), Kabul ve Reddet eşit ağırlıkta tek tık, önceden işaretli kutu yok; analitik ve 3. taraf script yalnız onaydan sonra yüklenir | KVK Kurulu kararı + ölçüm güvenilirliği | Reddet sonrası Network'te analitik isteği = 0 |
| 3.5 | Font `next/font` ile self-host; Türkçe içerikte `subsets` içinde `latin-ext`; Google Fonts CDN'e rızasız istek yok; Tailwind v4'te `next/font` `variable` sınıfları `<html>`'e konur ya da `@theme inline` kullanılır (`:root`'taki `@theme` body'deki değişkeni göremez) | ş/ğ/İ doğru çizilir; rızasız CDN fontu AB'de yaptırım konusu oldu (LG München 3 O 17493/20) | `grep -rn subsets src/app`; Network'te `fonts.gstatic.com` = 0; Playwright'ta `getComputedStyle(document.body).fontFamily` next/font adını içerir (yoksa P2) |
| 3.6 | Erişilebilirlik temeli: `<html lang>` doğru, skip link, landmark'lar, sayfa başı tek h1, görünür label, hata mesajı `aria-live`/`role="alert"`, görünür odak, dokunma hedefi ≥ 24px, `prefers-reduced-motion` | Sonradan eklemek pahalı | Playwright + `@axe-core/playwright` smoke'ta kritik ihlal 0 |
| 3.7 | i18n hazırlığı: {{#if diller}}{{diller}} için `app/[locale]` yönlendirmesi, `alternates.languages` (hreflang), metinler sözlük dosyasında{{else}}metinler tek modülde toplanır; kütüphane 2. dil yayına girerken kurulur{{/if}} | Dil eklemek yeniden yazım gerektirmez | Her locale route 200 + doğru `lang` |
| 3.8 | İşlem e-postası: Supabase Auth için custom SMTP (varsayılan SMTP üretim için değil); gönderici alanda SPF + DKIM (≥ 1024 bit, öneri 2048) + DMARC; şablonlar {{#if diller}}her dilde{{else}}ürün dilinde{{/if}} | Doğrulama/sıfırlama e-postası spam'e düşmez | `dig +short TXT <alan>`; `dig +short TXT <selector>._domainkey.<alan>`; `dig +short TXT _dmarc.<alan>` |
| 3.9 | Ödeme varsa: fiyat ve ürün sunucuda yeniden okunur; webhook imzası ham gövde üzerinden HMAC + timing-safe; imza geçersizse iş mantığı çalışmaz (fail-closed); `webhook_events.event_id` UNIQUE (ikinci geliş NOOP); abonelik yalnız sağlayıcı olayıyla aktifleşir | Çift tahsilat, sahte olay, bedava abonelik | Bozuk imza → 401 + DB değişimi 0; aynı olay iki kez → tek etki |

3.2 ek kanıt (kabukta): `curl -s <url>/olmayan-sayfa-xyz | grep -c 'rel="canonical"'` = 0 (≠0 → P2).

### M4 — Gözlem + yayın
| # | Ne | Neden | Doğrulama |
|---|---|---|---|
| 4.1 | Sentry istemci + sunucu + edge; `sendDefaultPii: false`, `beforeSend` ile e-posta/parola/çerez/Authorization temizliği; Replay varsa metin maskeli | Hata görünür, kişisel veri loga girmez | Test hatası panelde görünür; olay gövdesinde PII 0 |
| 4.2 | Harici uptime monitörü (öneri ≤ 5 dk aralık) ana sayfa + `/api/health`; alarm kanalı test edilmiş | Kesintiyi kullanıcıdan önce öğren | Monitör panelinde kayıt + test alarmı ulaştı |
| 4.3 | CI kapıları (PR'da zorunlu): `tsc --noEmit`, lint, `vitest run`, `next build`, Playwright smoke (ana sayfa, giriş, korumalı route yönlendirmesi, 404), `supabase db reset` + `supabase test db` (pgTAP: anon ve başka kullanıcı satırı okuyamaz/yazamaz) | Kırık kod main'e girmez | Kasıtlı kırık PR'da CI kırmızı |
| 4.4 | Vercel: Preview → staging Supabase, Production → prod; her değişkenin scope'u bilinçli; `.env.example` ADLARI ile `vercel env ls production` diff'i 0 | En sık hata: Development'ta var, Production'da yok | Ad listesi diff'i (değerler okunmaz) boş |
| 4.5 | Migration önce staging'de; PR'da staging kanıtı; sonra prod | Canlı veriyi bozmadan dene | `supabase migration list` staging = prod sırası |
| 4.6 | Dağıtım sonrası smoke (her deploy + bağımlılık yükseltmesi): ana sayfa 200, korumalı API'ler oturumsuz 401/403 (5xx değil), `og:image` ve canonical mutlak https | Yükseltme sonrası sessiz 500 | `curl -s -o /dev/null -w '%{http_code}' <url>/api/<korumalı>` → 401/403 |
| 4.7 | Yedek: Free planda otomatik yedek yok → zamanlanmış `supabase db dump` off-site; Pro 7 gün; ödeme verisi varsa PITR kararı yazılı; izole projeye geri yükleme provası yapılıp tarihiyle `docs/HARITA.md`'ye yazılır | Yedek, geri yüklenene kadar varsayımdır | Prova kaydı (tarih + süre + sonuç) var |
| 4.8 | Alan adı: apex A + www CNAME; değerler `vercel domains inspect <alan>` çıktısından (başka projeden kopyalanmış IP yok) | Yanlış projeye ya da ölü IP'ye yönlenme | `dig +short <alan> A` ve `dig +short www.<alan> CNAME` inspect çıktısıyla aynı; apex'te CNAME = 0 |
| 4.9 | Olay müdahalesi: kişisel veri ihlalinde Kurul'a tespitten itibaren en geç 72 saatte bildirim; sorumlu ve adımlar `docs/HARITA.md`'de | KVKK yükümlülüğü | Belge bölümü var |

### M5 — Teslim kapısı
| # | Ne | Neden | Doğrulama |
|---|---|---|---|
| 5.1 | Teslim kapısını PLE'den çek ve çıktısını uygula: `ple get "Web tam denetim ve teslim kapısı" --var proje_yolu="{{#if proje_yolu}}{{proje_yolu}}{{else}}$(pwd){{/if}}" --var canli_url="{{#if canli_url}}{{canli_url}}{{else}}<preview-url>{{/if}}" --var kapi=canliya-cikis{{#if musteri}} --var musteri=1{{/if}}`; `ple` yoksa durumu «PLE geri bildirim:» satırına yaz, DoD tablosunu tek başına kanıt sayma. P0 = 0 ve sahipsiz P1 = 0 olmadan yayın yok | Kurulumun denetimden geçtiğinin tek kanıtı | Kapı kararı GEÇ |

## Tamamlanma tanımı
Tabloyu doldur; durum = ✅ geçti · ❌ kaldı · ⏳ ölçülemedi (neden) · — geçerli değil.
| Madde | Doğrulama komutu | Durum |
|---|---|---|
| Temiz kurulum | `npm ci && npx tsc --noEmit && npm run lint && npx vitest run && npm run build` | |
| Şema git'ten kurulur, iki kez koşar | `supabase db reset` + her migration'ı `psql -v ON_ERROR_STOP=1 -f` ile ikinci kez (1.4) | |
| RLS kapalı tablo 0, Advisor ERROR 0 | pg_class sorgusu + Advisors > Security ERROR sayısı + `supabase test db` | |
| İstemcide sır 0 | `grep -rl sb_secret_ .next/static`; NEXT_PUBLIC_ grep'i | |
| Auth akışlarının hepsi | `npx playwright test e2e/auth` | |
| Güvenlik başlıkları + CSP | `curl -sI <url>` | |
| Korumalı uçlar oturumsuz 401/403 | curl döngüsü | |
| SEO temeli | `curl -s <url>/sitemap.xml`, `/robots.txt`, og/canonical grep | |
| Hukuk + çerez onayı | footer linkleri + reddet sonrası Network | |
| Sentry + uptime | test hatası + test alarmı | |
| Alan adı + e-posta DNS | 4.8'in iki `dig`'i + 3.8'in üç `dig`'i | |
| Env ad diff'i 0 | `.env.example` ↔ `vercel env ls production` | |
| Yedek + geri yükleme provası | prova kaydı | |
| Ödeme (varsa) | bozuk imza + çift olay testi | |
| Teslim kapısı | 5.1 kapı kararı | |

## Bilinçli ertelenenler
Kapsam dışı bıraktığın ve "geçerli değil" bulduğun her maddeyi yaz; boş bırakma.
| Madde | Neden | Ne zaman tekrar |
|---|---|---|

## Sıradaki 3 adım
En yüksek riskli üç açık madde: madde no · tek cümle iş · doğrulama komutu.

Çıktı iskeleti (bu sırayla): ## Proje kartı · ## Kurulum sırası (M0–M5, her satır ne · neden · doğrulama{{#if uygula}} · {{#if musteri}}yama adı{{else}}commit kısaltması{{/if}} · test sonucu{{/if}}) · ## Tamamlanma tanımı · ## Bilinçli ertelenenler · ## Sıradaki 3 adım{{#if musteri}} · ## Müşteri özeti (teknik olmayan, en çok 6 satır){{/if}}.

Geri bildirim: Bu prompt bir kontrolü kaçırdıysa, yanlış eşik verdiyse ya da PLE'de bir aksaklık gördüysen tek satır yaz: ple feedback --type prompt --prompt "Web sıfırdan kurulum (Next.js + Supabase)" "<ne eksik → ne olmalı>" (PLE aksaklığında --type bug ya da missing; komut yoksa raporun sonuna «PLE geri bildirim:» satırı ekle).

Related prompts