Ürün önerilerini mağazanın kendi tasarımında göstermek
Önerilen ürünleri mağazanın kendi kart tasarımıyla göstermek için kartı kopyalayan, veriyi dolduran ve mevcut kaydırma bileşenini kullanan motor.

Şikayet hep aynı cümlenin bir versiyonuydu: "bizim siteye benzemiyor."
İlk öneri scriptimiz (V1) kendi HTML (web sayfasının yapısını tanımlayan işaretleme dili) string'lerini (metin değeri) üretiyor, kendi slider'ını (kartları yatay kaydırarak gösteren bileşen) getiriyordu. Ürünler doğruydu. Etraflarındaki kart bizimdi. Farklı font kalınlığı, farklı fiyat düzeni, farklı oklar. Ürün grid'ine aylarca emek vermiş bir mağazada bizim blok, birinin araya yapıştırdığı bir reklam gibi duruyordu.
Aralık 2025 ile Temmuz 2026 arasında render (veriyi ekranda gösterilecek görünüme dönüştürme) tarafını sıfırdan yazdım. 60 commit (kod deposuna kaydedilen değişiklik paketi), hepsi benim, yaklaşık 12 bin satır düz JavaScript. Ana fikir tek satır: mağazanın kendi kartını kopyala.
Baştan bir şeyi netleştireyim, çünkü devamı buna bağlı. Stratejiler ve modeller (kim "benzer ürünler" görür, "trend" ne demek, sıralama nasıl yapılır) tamamen backend'de (uygulamanın sunucuda çalışan bölümü). Onları ben yazmadım. Benim işim "API (yazılımların birbirine istek göndermesini sağlayan arayüz) 10 ürün döndü" ile "alışveriş yapan biri birine tıkladı" arasında kalan her şeydi: widget (sayfaya eklenen küçük işlevsel bileşen) nereye girecek, nasıl görünecek, hangi sayfada hangi strateji istenecek, tıklama kime yazılacak.
İşi zorlaştıran ne
Her mağaza başkasının kodu. Onlara build (kaynak kodu çalıştırılabilir veya yayımlanabilir çıktıya dönüştürme) adımı gönderemezdim, template'lerine dokunamazdım, markup'ları (sayfanın öğe yapısını tanımlayan işaretleme) hakkında tek bildiğim haber vermeden değişeceğiydi.
Buradan erkenden üç kural çıktı ve hiç değişmedi:
- Tek bir izole root (bileşenin bütün öğelerini içinde tutan kök alan) ekle, sadece onun içindeki DOM'a (tarayıcının sayfadaki öğeleri temsil ettiği yapı) dokun.
- Sitenin kendi slider'larını asla yeniden başlatma, yok etme, hatta elleme.
- Widget'ın eklediği her şey (node, stil, listener (bir olayı bekleyip gerçekleşince kod çalıştıran dinleyici))
destroy()ile geri çıkmalı.
Bir de yeni bir markaya kurulumu yapan kişiler çoğunlukla mühendis değildi. Projenin son üçte birini bu, teknik kısıtlardan daha çok şekillendirdi.
Karar 1: kartı kopyala, sonra içindeki runtime'ı sök
Motor sayfada zaten var olan bir referans slider'ı okuyor (mesela mağazanın kendi "yeni gelenler" satırı), içinden bir ürün kartı alıyor ve onu template yapıyor. Sonra o kartın kopyalarını bizim ürünlerle dolduruyor.
İlk denemem fazla şey kopyaladı. Çalışan bir carousel (kartları sırayla veya kaydırarak gösteren bileşen) runtime (kodun çalıştığı ortam) state ile dolu: track genişlikleri, transform'lar, sonsuz döngü için klonlanmış slide'lar, active ve current class'ları, index attribute'ları. Bunları kopyalarsan eski track'in verdiği genişlikte takılı kalmış bir kart ya da hâlâ var olmayan bir carousel'e ait olduğunu sanan bir slide elde ediyorsun.
Bu yüzden render'dan önce çalışan bir temizleme adımı var:
// pre-init: template geldiği slider'ı unutsun
removeClasses(card, ['slick-active', 'slick-current', 'slick-cloned']);
card.removeAttribute('data-slick-index');
card.style.width = '';
card.style.transform = '';Ardından hydration geliyor: görsel, başlık, fiyat, eski fiyat, indirim rozeti, etiketler. Her alanın config'te (uygulamanın davranışını belirleyen ayarlar) bir selector'ı (sayfadaki bir öğeyi bulmak için kullanılan seçici) var; selector bulunamazsa o alan sessizce atlanıyor. Bozuk bir rozet küçük bir hata. Boş bir widget büyük bir hata.
Config, markanın yanlış node'u göstermesine de izin veriyor. Biri carousel'in kendisi yerine iç stage wrapper'ını (başka bir bileşeni sararak ek davranış kazandıran katman) seçiyordu ve klon yanlış class ailesiyle, yanlış boyutta çıkıyordu. Bu düzeltmeyi core'a taşıdım: selector bir runtime wrapper'a denk gelirse motor, bir şey çıkarmadan önce gerçek carousel root'una kadar yukarı çıkıyor. Tek düzeltme, bütün markalar.
Karar 2: üç carousel kütüphanesini desteklemek
Çalıştığımız mağazalar Slick, Owl ya da Swiper kullanıyordu. Bilerek sadece Slick ile başladım (ilk kapsam dokümanında diğer ikisi açıkça kapsam dışı yazıyor). Bu, ilk Owl mağazasına kadar sürdü.
Her kütüphanenin kendi option isimleri ve "4 ürün" ne demek konusunda kendi fikri var. Owl items istiyor, Swiper slidesPerView. Owl, durdurmazsan referans slider'ın breakpoint'lerini seninkilerle seve seve birleştiriyor. Normalizasyon (farklı yazımları veya veri biçimlerini ortak kurala dönüştürme) ekledim: config Owl için de slidesPerView yazabiliyor, config'te açık bir responsive haritası varsa sayfadan okunanın tamamen yerine geçiyor.
Mağazanın kendi oklarını da referans slider'dan kopyaladım. Özel ok class'ları, sadece varsayılan class isimlerini bilen "kontroller yerinde mi?" kontrolümü bozdu; artık kontrol kopyalanan okları da arıyor.
Kopyalama başarısız olursa (o sayfada referans slider yoksa, template dolmuyorsa, init hata verirse) motor kendi kart tasarımı olan native moda düşüyor. Native güvenlik ağı. Hedef her zaman clone.
Karar 3: senaryoları yönlendir, boş cevaba hazırlıklı ol
V1 her sayfaya bir widget koyuyordu. V2 bir matristen senaryo listesi seçiyor: sayfa tipi (anasayfa, kategori, ürün, sepet, sipariş) ile ziyaretçinin tanınıp tanınmadığı (5+ ürün görüntüleme) ya da misafir olması. Her satır bir backend strateji tipi, bir context (yok, kategori, ürün ya da sepet) ve bir limit içeriyor.
En çok zamanı iki detay aldı.
Kategori sayfalarında motor breadcrumb'ı (ana kategoriden mevcut sayfaya uzanan gezinme yolu) okuyup en derin kategoriyi gönderiyor. Boş dönerse üst kategoriyle, sonra onun üstüyle tekrar deniyor. Üç ürünlük minik bir alt kategori sayfada delik bırakmamalı.
Sepet sayfalarında sepetteki ürünler için birden fazla strateji isteyip sonuçları birleştiriyor. Birleştirme tekrar eden ürünleri ayıklıyor ve sepette zaten olanları çıkarıyor. Birinin az önce sepete attığı ürünün aynısını önermek, bozuk görünmenin en hızlı yolu.
Markalar matrisi kendi yerleşimleriyle de ezebiliyor: bir kampanya, bir selector, bir hedef kitle. Aynı sayfada iki farklı noktada iki widget'ın aynı root ID için kavga etmeden durması böyle oluyor.
Karar 4: atıflamayı gerçekten kırıldığı yerde düzelt
Bu düpedüz bir bug'dı (yazılım hatası), hem de kötüsünden. Önerilerden gelen siparişler checkout'ta kampanyasız, medium'u (orta) "other" olarak görünüyordu.
V2 widget'ı kaynak cookie'lerini (tarayıcıda saklanan küçük kayıt, yani çerez) yazmak için global bir kaydetme fonksiyonu çağırıyordu. O fonksiyon sadece V1 scriptinde tanımlıydı. V1'in yüklenmediği sayfalarda çağrı sessizce hiçbir şey yapmıyordu.
Düzeltme: V2 cookie'leri kendisi yazıyor, hem de tıklamayı beklemeden, widget yüklenir yüklenmez. Ömürleri 7 gün. Strateji tipini sessizce kampanya ID'si olarak kullanan ve veriyi yanlış kampanyaya yazan bir fallback'i (ilk seçenek işe yaramadığında kullanılan yedek yol) de kaldırdım. Artık bir Playwright testi hem aktivite isteğini hem cookie'leri kontrol ediyor.
Karar 5: ağır sayfalarda sessiz kal
Mağaza sayfaları zaten script (belirli bir işi yapan kod dosyası) dolu. Sayfanın takılmasının sebebi widget olmamalı.
- Acil olmayan işler (native kartın arka plan tonu için ürün görsellerinden ortalama renk çıkarmak)
requestIdleCallbackiçinde, 350 ms timeout vesetTimeoutyedeğiyle çalışıyor. - MutationObserver'lar (sayfa yapısındaki değişiklikleri izleyen tarayıcı aracı) debounce (peş peşe gelen işlemleri kısa süre bekleyip tek seferde çalıştırma) ve cooldown ile çalışıyor, widget'ın kendi root'larındaki değişiklikleri yok sayıyor.
- Bir render limiti, carousel başlamadan önce kaç kart üretileceğini sınırlıyor.
- Skeleton (veri gelene kadar gerçek içeriğin yerini tutan yükleme iskeleti) gerçek kartla aynı geometriye sahip, böylece yer değiştirme layout'u (öğelerin ekrandaki yerleşimi) kaydırmıyor. Bir kategori sayfasında kaymayı sıfıra indirmek için skeleton'ı float (ondalıklı sayıları temsil eden veri türü) edip bir dengeleyici eleman eklemem gerekti.
Mühendis olmayanların kurabilmesi
Her markanın bir config dosyası var. Motor aynı kalıyor. Sadece config ile kurulum rehberi yazdım ve küçük bir Docsify sitesi olarak yayınladım: API isteğinin sayfa sayfa nasıl oluştuğu, config'teki her bloğun ne yaptığı ve bir SSS.
İçindeki en işe yarar kural kısa. Çalışan config'i koru. Markanın dosyasını aç, sadece ihtiyacın olan objeyi ekle (native preset (önceden hazırlanmış ayar veya görünüm şablonu), kategori context'i, kampanya yerleşimi) ve selector'ları değiştir. Bir junior bunu, zaten çalışan bir mağazayı bozmadan yapabiliyor.
Farklı yapacaklarım
Core tek bir 12 bin satırlık dosya. Oraya dürüstçe, mağaza mağaza geldi, ama bazı markaya özel korumalar (mobil görsel repaint düzeltmeleri, overlay tekrarını ayıklama) içine girdi. Baştan modüllere bölüp bir bundler (kod dosyalarını tarayıcıya gönderilecek paketlerde birleştiren araç) arkasına koyardım; arama widget'ının esbuild ile kurulduğu gibi.
Bir de CDN edge (kullanıcıya yakın sunucu ağı) cache'ine (yeniden kullanmak için saklanan geçici veri, yani önbellek) saatler kaybettim. Düzeltme origin'de canlıydı ama edge eski dosyayı vermeye devam ediyordu; yani çalışmayan bir kodu debug (yazılımdaki hatanın nedenini bulma) ediyordum. Her config ve script URL'inde cache-busting (dosya adresini değiştirerek tarayıcının güncel sürümü almasını sağlama) parametresi ilk günden olmalıydı.
Widget hâlâ mağazanın hep bir parçasıymış gibi görünmek zorunda. Bütün test bu.
Revotas'taki diğer çalışmalarıma göz atın.
Bu yazı İngilizce aslından Türkçeye çevrilmiştir.
- İşi zorlaştıran ne
- Karar 1: kartı kopyala, sonra içindeki runtime'ı sök
- Karar 2: üç carousel kütüphanesini desteklemek
- Karar 3: senaryoları yönlendir, boş cevaba hazırlıklı ol
- Karar 4: atıflamayı gerçekten kırıldığı yerde düzelt
- Karar 5: ağır sayfalarda sessiz kal
- Mühendis olmayanların kurabilmesi
- Farklı yapacaklarım


Bu yazıya bırakılan notlar
Katıl
Yükleniyor…
Bir yorum bırak.
Bu yazıya not eklemek için giriş yap.
Sadece adın ve avatarın kullanılır.