E-Ticaret Sitesi Geliştirme: İlk Adımlarım

E-Ticaret Sitesi Geliştirme: İlk Adımlarım

eticaret·20 Mayıs 2026·5 dk okuma
nextjsprismacheckoutsepetgtmanalyticspostgresql
PaylaşLinkedInXWhatsApp

E-Ticaret Projesi — Şu Ana Kadar Yapılanlar

Bugünkü durum: Çalışan bir Next.js 15 e-ticaret iskeleti — gerçek veriye bağlı katalog, fonksiyonel sepet, sipariş oluşturma. Tek eksik: ödeme entegrasyonu (iyzico, C adımı).


1. Adım — Next.js iskeleti + GTM tracking altyapısı

Ne kuruldu:

BileşenSürüm / Detay
FrameworkNext.js 15.5.18 (App Router, React 19)
DilTypeScript 5.7
StylingTailwind v4
UI KitShadcn UI — Button/Input/Card (manuel, npx ESM hatası nedeniyle CLI atlandı)
Paket yöneticisipnpm 11.1.2
Klasör yapısısrc/ (app/, components/{ui,common,product,cart,checkout}, lib/, hooks/)

KRAL KURAL altyapısı kuruldu:

  • src/lib/analytics/gtm.tsinitDataLayer(), pushToDataLayer() (try-catch sarmalı, hata uygulamayı kırmaz)
  • src/lib/analytics/events.tstrackViewItem, trackAddToCart, trackBeginCheckout, trackPurchase helper'ları + GA4 best practice ecommerce: null reset
  • src/lib/analytics/types.ts — GA4 Enhanced Ecommerce tipleri
  • src/components/common/GTMScript.tsx — Container script + noscript iframe
  • app/layout.tsx — Türkçe <html lang="tr">, metadata, GTM yerleştirme
  • app/providers.tsx — Client provider katmanı, mount'ta dataLayer init

2. Adım — Prisma + PostgreSQL şeması

Veritabanı: Neon Postgres (Frankfurt) — pooled + direct URL ayrımı (pgBouncer)
ORM: Prisma 6.19.3 (v7'deki breaking change'ler nedeniyle v6'da kaldı)

prisma/schema.prisma — 14 model:

DomainModel'ler
KullanıcıUser, Address (billing/shipping)
KatalogCategory (nested tree), Product, ProductVariant, Inventory, ProductImage
SepetCart (guest + user, sessionId), CartItem
SiparişOrder (status state machine), OrderItem (variant snapshot), Payment
PazarlamaCoupon (PERCENT/FIXED), Review (moderasyonlu)

Tasarım kararları:

  • Tüm tutarlar Decimal(12,2) (float yuvarlama hatası önlemek için)
  • OrderItem variant snapshot içerir — variant silinse bile sipariş bozulmaz
  • customerEmail Order'da ayrıca tutulur (Meta CAPI hash'lemesi için)

Seed verisi: 3 kategori (Elektronik / Giyim / Ev & Yaşam), 10 ürün, 23 variant, 2 kupon (HOSGELDIN10, KARGOFREE). Idempotent.

db scripts (package.json):

pnpm db:generate / db:push / db:migrate / db:seed / db:reset / db:studio

Hepsi dotenv-cli -e .env.local -- prefix'i ile çalışır (Prisma CLI .env.local okumadığı için).


3. Adım — Katalog sayfaları (KRAL KURAL'ın ilk gerçek event'leri)

Server-side query katmanısrc/lib/queries/products.ts (server-only):

  • getFeaturedProducts(limit), getProductsByCategorySlug(slug), getProductBySlug(slug), getAllCategories(), getAllProductSlugs()
  • Decimal → number serialization burada (Server → Client Component prop güvenliği)

Sayfalar:

RouteTipNe yapar
/SSG (1h ISR)Featured ürünler + kategori chip'leri
/kategori/[slug]SSG, generateStaticParams3 kategori pre-render
/urun/[slug]SSG, generateStaticParams10 ürün pre-render, breadcrumb, gallery, variant selector

Components:

  • ProductCard + ProductGrid (Server) — fiyat aralığı (min – max), kategori, marka
  • ProductGallery (Client) — ana görsel + thumbnail navigation
  • ProductPurchaseBox (Client) — KRAL KURAL'ın ilk gerçek uygulaması: - Mount'ta view_item event (1 kez) - Variant seçimine göre fiyat/stok dinamik güncelleme - Stok ≤ 5 ise "Son N adet!" uyarısı

Header Server Component'e çevrildi — kategorileri Prisma'dan canlı çekiyor.


A Adımı — Sepet (Cart Context + localStorage)

src/lib/cart/store.tsx — domain'in kalbi:

  • CartProvider + useCart() hook
  • Reducer pattern (add / setQuantity / remove / clear / hydrate)
  • localStorage key: cart:v1 (versioned — gelecek migration'lar için)
  • Hydration-safe: SSR'da isHydrated=false, mount sonrası storage'tan yüklenir
  • cartLineToGA4Item() — sepet satırını GA4 item formatına çevirir (begin_checkout / purchase için reuse edilecek)

Components:

  • CartBadge — sepet ikonunun sayaç badge'i (SSR'da null → hydration mismatch yok)
  • CartLineRow — qty +/-, sil, satır toplamı
  • CartSummary — ara toplam + "Ödemeye geç" CTA
  • CartContents — skeleton → boş state → satırlar+özet 3-path orchestrator

/sepetCartContents kullanıyor, "Sepetin boş" empty state'i var.

ProductPurchaseBox entegrasyonu:

  • addLine() çağrısı + add_to_cart event'i her tıklamada
  • maxQuantity=stock — sepete eklerken stok aşımı engelleniyor

Güvenlikler:

  • localStorage hata → console.warn, uygulama çalışmaya devam
  • Negatif quantity → satırı kaldırır
  • Stok aşımı → clamp
  • Versioned key → gelecek schema değişiklikleri için migration kapısı

B Adımı — Checkout + begin_checkout event + Order create

Form altyapısı: react-hook-form 7.76 + zod 4.4 + @hookform/resolvers

src/lib/checkout/schema.ts — Zod schemas (client + server tek source of truth):

  • checkoutSchema — e-posta, ad-soyad, telefon (TR 10-11 hane regex), line1/line2, şehir, ilçe, posta kodu (5 hane), not
  • cartLineSnapshotSchema — client → server geçişi
  • createOrderInputSchema — toplu validation

src/app/actions/checkout.ts — Server Action createOrder:

  1. Zod parse (defansif)
  2. Fiyat & stok DB re-validation — client snapshot'a güvenilmiyor
  3. User.upsert (email-based guest checkout)
  4. Address.create (SHIPPING)
  5. Order + OrderItems transaction
  6. Order number retryYYYY-XXXXXX base36, P2002 unique violation'da 3 kez retry

src/lib/queries/orders.tsgetOrderByNumber(orderNumber) confirmation page için.

UI:

ComponentSorumluluk
CheckoutFormRHF + zodResolver, TR autoComplete attribute'ları, inline hata mesajları
CheckoutSummarySticky sidebar, sepet item'ları + ara toplam
CheckoutClientBoş sepet → /sepet redirect, begin_checkout (useRef ile bir kez), submit handler

Sayfalar:

  • /odemeCheckoutClient render eder
  • /siparis/[orderNumber] — Server, force-dynamic, robots: noindex, "Siparişin alındı" + items + adres + status badge

Akış:

/sepet → /odeme → mount'ta begin_checkout event
                 → form submit → createOrder server action
                                → Order PENDING create
                                → cart.clear() + localStorage temizle
                                → router.push(/siparis/2026-XXXXXX)

E2E test sonucu (Neon'a karşı): Order 2026-SROXR2 başarıyla oluştu, items + address join çalışıyor, confirmation sayfası canlı render ediyor.


Mevcut Stack — Hızlı Referans

KatmanTeknoloji
FrameworkNext.js 15.5.18 + React 19 + TypeScript 5.7
StyleTailwind v4
UIShadcn UI (Button, Input, Card — manuel) + lucide-react ikonlar
State (cart)React Context + Reducer + localStorage
Formreact-hook-form + zod
ORMPrisma 6.19.3
DBNeon Postgres (Frankfurt, pooled + direct URL)
TrackingGTM + GA4 (dataLayer), Meta CAPI altyapısı hazır (C'de devreye girecek)
Paket yöneticisipnpm 11.1.2

KRAL KURAL Durumu

EventDurumNerede tetikleniyor
view_item✅ ÇalışıyorProductPurchaseBox mount, ilk variant ile
add_to_cart✅ Çalışıyor"Sepete ekle" tıklama, seçili variant ile
begin_checkout✅ Çalışıyor/odeme mount, tüm sepet item'ları ile (1 kez, ref guard)
purchase⏳ C adımıiyzico webhook → server (Meta CAPI) + client (/siparis?paid=1 query) eşzamanlı

Dosya Haritası (özet)

src/
├── app/
│   ├── (shop)/
│   │   ├── page.tsx                        # Anasayfa, featured + kategori chip'leri
│   │   ├── kategori/[slug]/page.tsx        # Kategori listesi
│   │   ├── urun/[slug]/page.tsx            # Ürün detay
│   │   ├── sepet/page.tsx                  # Sepet
│   │   ├── odeme/page.tsx                  # Checkout
│   │   └── siparis/[orderNumber]/page.tsx  # Onay
│   ├── actions/
│   │   └── checkout.ts                     # createOrder server action
│   ├── layout.tsx                          # GTM script + metadata
│   └── providers.tsx                       # CartProvider + dataLayer init
│
├── components/
│   ├── common/{Header,Footer,GTMScript}.tsx
│   ├── product/{ProductCard,ProductGallery,ProductPurchaseBox}.tsx
│   ├── cart/{CartBadge,CartLineRow,CartSummary,CartContents}.tsx
│   ├── checkout/{CheckoutClient,CheckoutForm,CheckoutSummary}.tsx
│   └── ui/{button,input,card}.tsx          # Shadcn primitives
│
├── lib/
│   ├── analytics/{gtm,events,types}.ts     # KRAL KURAL altyapısı
│   ├── cart/store.tsx                      # CartProvider + useCart
│   ├── checkout/schema.ts                  # Zod schemas
│   ├── queries/{products,orders}.ts        # server-only Prisma queries
│   ├── server/orderNumber.ts               # YYYY-XXXXXX generator
│   ├── db.ts                               # Prisma singleton
│   ├── constants.ts                        # SITE_NAME, GTM_ID, DEFAULT_CURRENCY
│   ├── format.ts                           # formatCurrency(value, "TRY")
│   └── utils.ts                            # cn() helper
│
└── prisma/
    ├── schema.prisma                       # 14 model
    ├── seed.ts                             # 3 kat + 10 ürün + 23 variant
    └── migrations/20260520134448_init/


Sıradaki: C Adımı

← Blog Yazıları
WhatsApp'tan Bize Sorun