# Sunucu Taraflı Render

İçeriği HTML içinde yayınlama, arama motoru görünürlüğü
Kaynak: https://selwise.com/docs/product-enrichment/server-side

Zenginleştirme içeriğini tarayıcıda değil, kendi sunucunuzda oluşturabilirsiniz. İçerik o zaman sayfanın HTML'inin içinde doğar; widget onu tanır, bir daha basmaz ve yalnızca gösterim ile tıklama ölçümünü devralır.

Bu sayfa neyin ne olduğunu, ne zaman kullanılacağını, mimariyi ve panel ayarını anlatır. HTTP ayrıntısı için [Sunucu Taraflı Render API](/docs/api-reference/ssr-api), kopyalanıp yapıştırılacak kod için [Sunucu Taraflı Render Örnekleri](/docs/product-enrichment/server-side-recipes) sayfalarına bakın.

## Panelde nerede

Sol menüde **Kişiselleştirme → Ürün Zenginleştirme** (Personalization → Product Enrichment) → bir kuralı açın (ya da **New rule**) → **Rules** (Kurallar) adımı → **Where it goes** (Nereye yerleşir) bölümü. Orada **Where it renders** (Nerede oluşturulur) seçimi ve seçime bağlı **Slot name** (Yuva adı) alanı bulunur. Türkçe arayüzde **Nerede oluşturulur** ve **Yuva adı**. Yuva adı alanının altında, şablonunuza yazacağınız satır hazır gösterilir.

Bu ayar yalnızca **web kanalında** vardır: bir uygulama kanalı sayfa şablonu taşımaz ve onun karşılığı [Mobil Uygulamada Zenginleştirme](/docs/product-enrichment/mobile-app)'dir.

## Ne zaman kullanılır

Selwise'in normal yolu şudur: sayfa yüklenir, widget yapılandırmayı çeker, zenginleştirme parçasını indirir, içeriği ister, sonra sayfaya yazar. Dört ardışık ağ adımı. Bir alışverişçi bunu fark etmez, yer önceden rezerve edildiği için sayfa da kaymaz. Bir tarayıcı botu için tablo farklıdır:

| İstemci | JavaScript çalıştırır mı | İçeriği görür mü |
| --- | --- | --- |
| Googlebot | Evet, ama ikinci bir geçişte | Genelde evet, gecikmeli |
| Bingbot, Yandex | Kısıtlı | Güvenilmez |
| GPTBot, ClaudeBot, PerplexityBot | Hayır | Hayır |
| WhatsApp, Slack, Facebook önizleme | Hayır | Hayır |

Zenginleştirmenin ürünü tam olarak ürün sayfasındaki **okunabilir metin**. O metnin JavaScript arkasında kalması, alt üç satırdaki istemciler için hiç var olmaması demek; ve ürün bilgisinin yapay zekâ cevaplarına girmesi organik trafikle yarışan bir kanal hâline geldi. İkinci, daha küçük kazanç: içerik ilk boyamada geldiği için blok içindeki görsel de daha erken istenir.

**Kullanmayın:**

- Sayfa şablonunuza sunucu kodu yazamıyorsanız (hosted bir platformun kapalı tema alanı): bu yol bir uygulama sunucusu ister. **Yalnızca tarayıcı** ile devam edin.
- İçeriği kişiye göre değişiyorsa (segment hedefli): bir ziyaretçinin segmenti sayfa önbelleğiyle herkese yapışır, bkz. [Kişiye bağlı içerik sunucuda basılmaz](#kişiye-bağlı-içerik-sunucuda-basılmaz).
- Sayfanız tek sayfa uygulamasıysa ve ürün sayfasına tam sayfa yüklemesiyle girilmiyorsa: bkz. [Tek sayfa uygulamalarında](#tek-sayfa-uygulamalarında-spa).

## Mimari

Sunucu taraflı render'da **üç taraf** vardır ve kimin kimi çağırdığı önemlidir:

```text
 Tarayıcı / bot                 Sizin uygulama sunucunuz                     Selwise
 ─────────────                  ────────────────────────                     ───────
 GET /urun/abc-1  ───────────▶  şablon yuvayı çağırır
                                  GET .../ssr/enrichments?code=…&slots=…  ──▶  kuralı çözer,
                                  x-selwise-api-key: swpk_live_…                temizler, kapsamlar
                                  (400 ms zaman aşımı, hata = boş)         ◀──  { slots, css, v }
                                şablon css + html'i basar
 HTML (içerik içinde) ◀───────
 client.js yüklenir ────────────────────────────────────────────────────────▶  GET .../config
                                                                           ◀──  enrichment.v
 widget: kök etiketinde data-selwise-ssr-v == v ?
   evet  → kuralı "çizilmiş" say, ölçümü bağla, bir daha basma
   hayır → kural "tarayıcı yedekli" ise tarayıcıdan taze çiz, bayat kökü sonra kaldır;
           "yalnızca sunucu" ise bayat kökü koru
```

Anahtar noktalar:

- **API çağrısını sizin sunucunuz yapar**, tarayıcı değil. Çağrı site API anahtarı taşır ve anahtar sunucudan çıkmaz.
- **Selwise'in önbelleği ve sizin önbelleğiniz ayrıdır.** Sizin sayfa önbelleğinize (CDN, Varnish, Next.js veri önbelleği) Selwise dokunamaz; panelde bir kuralı düzenlediğinizde o önbelleğe yazılmış sayfa eski içeriği taşımaya devam eder. Bunu kapatan şey damga mekanizmasıdır (aşağıda).
- **Çıktı ziyaretçiye göre değişmez** (`mode: anonymous`): bu, sayfanın herkese aynı önbellekten verilebilmesinin ön koşuludur.

## Nerede oluşturulur: üç seçenek

| Seçenek | Ne yapar | Ne zaman |
| --- | --- | --- |
| **Browser only** (Yalnızca tarayıcı) | Bugünkü davranış. Varsayılan ve mevcut tüm kurallar böyle | Sunucu tarafında geliştirme yapamıyorsanız ya da kural segment hedefliyse |
| **Server, with browser fallback** (Sunucu, tarayıcı yedekli) | Şablonunuz isterse sunucuda, istemezse tarayıcıda | **Önerilen.** Sunucu çağrısı başarısız olsa bile içerik yine görünür |
| **Server only** (Yalnızca sunucu) | Kural widget açılış paketinden tamamen çıkar | Yalnızca widget script'ini hiç yüklemeyen bir kurulumda |

> **Sunucu, tarayıcı yedekli ile başlayın**
>
> Bu seçenek bir güvenlik ağı bırakır: sunucu çağrınız zaman aşımına uğrarsa, hata verirse veya şablonunuz o yuvayı henüz basmıyorsa widget yuvayı eskisi gibi doldurur. **Yalnızca sunucu** seçeneğinde böyle bir yedek yoktur; şablonunuz basmazsa içerik hiçbir yerde görünmez.

## Yuva adı

Sunucu tarafında hedef bir CSS seçici değil, sizin verdiğiniz bir **addır**. Sunucunuzun bir DOM'u yok, bir seçiciyi çözemez; şablonu siz yazıyorsunuz, yuvayı siz koyuyorsunuz ve orada isim seçiciden hem daha sağlam hem daha okunur. Tema güncellemesi bir sınıf adını değiştirdiğinde yuva adı yerinde kalır.

Ad kuralı: küçük harf, rakam ve tek tire; en çok 64 karakter; tireyle başlayıp bitemez. `urun-aciklama-alti`, `sepet-ustu`, `beden-tablosu-alani`. Büyük harf, boşluk veya alt çizgi yazarsanız panel bunları katlar ve **Saved as** (Kaydedilecek hâli) satırında kaydedeceği hâli size gösterir; şablonunuza yazacağınız ad o katlanmış hâldir.

> **Kural iki adres taşır**
>
> Sunucu tarafı açık bir kural CSS seçicisini de korur. Seçici tarayıcı yolunun adresi, yuva adı sunucu yolunun adresi. İkisi aynı kuralın iki adresidir; biri diğerinin yerine geçmez. Aynı yuva adını birden çok kurala verirseniz kurallar o yuva için yarışır: önce **Priority** (Öncelik) yüksek olan, eşitlikte eski olan gelir; **When rules collide** (Kurallar çakıştığında) ayarı tek bloğa ayarlıysa kazanan yuvayı tek başına alır.

## Kurulum

1. **Kuralı hazırlayın.** **Rules** adımında **Where it renders** için **Server, with browser fallback**'ı seçin ve bir **Slot name** verin.
2. **API anahtarı alın.** **Channels** ekranında kanalınızın satırındaki **⋮** menüsünden **API keys**'i açın, **Purpose** olarak **Server-side rendering** seçin ve anahtarı oluşturun. Anahtar `swpk_live_` ile başlar ve **yalnızca bir kez** gösterilir. Bkz. [API Anahtarları](/docs/channels/api-keys).
3. **Sunucu ortamınıza ekleyin.** `SELWISE_SITE_KEY` ve `SELWISE_SSR_API_KEY` iki ortam değişkeni; ikisi de tarayıcıya açılmaz (`NEXT_PUBLIC_` önekli bir değişken **olmaz**).
4. **Şablonunuza ekleyin.** PHP, Node, Next.js ve Django örnekleri: [Sunucu Taraflı Render Örnekleri](/docs/product-enrichment/server-side-recipes). Akinon için hazır parçalar: [Akinon](/docs/platforms/akinon). Ham HTTP sözleşmesi: [Sunucu Taraflı Render API](/docs/api-reference/ssr-api).
5. **Doğrulayın.** Aşağıdaki [Doğrulama](#doğrulama) bölümü.

> **API anahtarı tarayıcıya çıkmaz**
>
> Bu çağrı sunucudan sunucuya yapılır. Anahtarı bir şablona, bir script bloğuna veya `NEXT_PUBLIC_` önekli bir değişkene yazmayın. Sızan bir anahtarı aynı ekrandan döndürebilirsiniz (**Rotate**); eski anahtar anında geçersiz olur. `ssr_read` salt okunurdur: sızması hiçbir şey yazamaz, ama sizin adınıza istek yapılabilir ve hız sınırınızı tüketebilir.

## Hangi içerik sunucuya gelir

Sunucu yanıtı yalnızca **ziyaretçiye göre değişmeyen** kuralları taşır. Bir kural şu koşulların **hepsini** sağlamıyorsa sunucuda görünmez (ama **Server, with browser fallback** ise tarayıcıda yine görünür):

- **Where it renders** ayarı **Browser only** değil, ve bir **Slot name** dolu.
- Etkin (**Active**), başlangıç ve bitiş penceresinin içinde.
- **Test mode** kapalı.
- Segment hedefli değil (tüm ziyaretçileri hedefliyor).
- Ürüne eşleşiyor: kod listesinde, ya da katalog koşuluna uyuyor, ya da **All products**.

### Kişiye bağlı içerik sunucuda basılmaz

Bir segmentle hedeflenmiş kural, kişiye göre değişen bir cevaptır. Sunucuda oluşturulmuş sayfa ise genelde bir önbelleğe girer ve o önbellek herkese servis edilir; yani bir ziyaretçinin segmenti bütün ziyaretçilere yapıştırılır. Satın alma geçmişine dayalı bir segmentte bu yalnızca hata değil veri sızıntısıdır. Bu yüzden panel, segment hedefli bir kuralda sunucu seçeneklerini kabul etmez. Aynı şekilde **test modundaki** kurallar sunucu yanıtına girmez: önbelleğe donan test içeriği herkese gider ve mağazadaki test bayrağı hiç sorulmaz. Bir test kuralını denemek için **Browser only** ya da **Server, with browser fallback** seçip test modunu tarayıcıda açın: [Önizleme ve Test Modu](/docs/product-enrichment/testing).

Aynı sebeple geri sayım, sepet sayacı ve ziyaretçi sayacı gibi zamana ve kişiye göre değişen kampanyalar ve widget'lar hiçbir zaman sunucuda basılmaz.

## Widget sayfayı nasıl devralır

Sunucuda basılmış içeriği widget sayfada bulduğunda şunu yapar, bu sırayla:

1. **Bayat kökü yeniler.** Her sunucu kökü, oluşturulduğu andaki içerik sürümünü taşır (`data-selwise-ssr-v`). Widget bunu `config` yanıtından aldığı güncel sürümle karşılaştırır; uyuşmuyorsa kural **Server, with browser fallback** ise içeriği tarayıcıdan taze olarak basar ve ancak o zaman bayat kökü kaldırır. Kural **Server only** ise tarayıcı yolu olmadığından bayat kök **sayfada kalır** ve ölçümü bağlanır; içerik, sayfa önbelleğiniz yenilenince güncellenir.
2. **Kalanını sahiplenir.** Kökteki kural kimliği (`data-selwise-entity-id`) "çizilmiş kurallar" listesine yazılır: o kural bir daha getirilmez, çizilmez, yerleştirilmez. İçerik hiçbir zaman iki kez görünmez.
3. **Yer ayırmaz.** İçerik ilk boyamada yerinde durduğu için yanına yükseklik rezerve etmek ikinci bir boşluk açardı.
4. **Ölçümü bağlar.** Görünürlük gözlemcisi ve tıklama dinleyicisi köke bağlanır; gösterim ve tıklama sayıları tarayıcıda çizilen blokla aynı akar.

Widget script'i yüklenmemişse (ya da `config` isteği başarısızsa) **hiçbir şey kaldırılmaz**: eksik bir sürümü "bayat" saymak, kendi isteğimizin başarısızlığı yüzünden sizin içeriğinizi silmek olurdu.

> **Sürüm damgası tüm site içindir**
>
> Damga sitedeki **herhangi** bir kuralın ya da bloğun değişmesiyle değişir; yalnızca sayfadaki kuralın değil. Önbelleğinizdeki bir ürün sayfası, siteniz başka bir ürün için yapılan bir düzenlemeden sonra widget'a "bayat" görünür. **Server, with browser fallback** kuralında bu sorun değildir: tarayıcı içeriği taze çizer ve bayat kopyayı kaldırır. **Server only** kuralında ise tarayıcı yolu yoktur; içerik kaybolmaz ama sayfa önbelleğiniz yenilenene kadar **önbellekteki hâliyle** görünür. Sayfa önbelleğinizin ömrünü kısa tutun (en çok birkaç saat).

Tarayıcı botları widget'ı çalıştırmadığı için bu düzeltmeyi görmez: bot, sayfa önbelleğinizde ne varsa onu okur. Bu yüzden önbelleğinizi makul tutmak yine sizin sorumluluğunuzdadır.

## Tek sayfa uygulamalarında (SPA)

Mağazanız sayfa yenilemeden gezinen bir uygulamaysa (PZ Next gibi), widget her URL değişiminde önceki ürünün içeriğini sayfadan temizler; çünkü sunucuda oluşturulmuş blok o eski ürüne aittir. Ardından yeni ürünün içeriğini normal yoldan basar.

**Server, with browser fallback** seçeneğinde bu görünmez: içerik tarayıcı yolundan gelir. **Server only** seçeneğinde ise kuralın tarayıcı yolu yoktur, dolayısıyla içerik bir sonraki tam sayfa yüklemesine kadar görünmez. Arama motoru tarafında kayıp yoktur: tarayıcı botları uygulama içi gezinme yapmaz, her adresi baştan yükler. Yine de bir SPA'da **Server only** seçeneğini kullanmayın.

## Ölçüm

Gösterim ve tıklama sayıları değişmez. Widget sunucuda oluşturulmuş bloğu tanır, yeniden basmaz ama görünürlük gözlemcisini ve tıklama dinleyicisini ona bağlar; rapor eskisi gibi akar. Tek istisna widget script'ini hiç yüklemeyen bir kurulumdur: orada tarayıcıda çalışan hiçbir şey olmadığı için gösterim de gelmez. İçerik canlıdır, panel `0` gösterir.

## Doğrulama

JavaScript çalıştırmayan bir istemciyle sayfayı çekin; içerik HTML'in içinde olmalı:

```bash
# 1. İçerik ilk bayttaki HTML'de mi? (kök sayısı sıfırdan büyük olmalı)
curl -s https://magazaniz.com/urun/abc-1 | grep -o 'data-selwise-ssr="1"' | wc -l

# 2. Kökün sürüm damgası ve yuvası
curl -s https://magazaniz.com/urun/abc-1 | grep -o 'data-selwise-ssr-v="[^"]*" data-selwise-ssr-slot="[^"]*"'

# 3. Bir yapay zekâ botu gibi: kullanıcı aracısını değiştirin, içerikten bir cümle arayın
curl -s -A "GPTBot/1.0" https://magazaniz.com/urun/abc-1 | grep -c "kumaş bakım"
```

Sonuç sıfırdan büyükse içerik HTML'in içinde. Tarayıcıda şunları da kontrol edin:

1. **Sayfa kaynağını görüntüleyin** (`view-source:`; DevTools'taki Elements değil, çünkü o JavaScript sonrası görünümdür). Kök öğeler orada olmalı.
2. DevTools **Elements** içinde `[data-selwise-ssr="1"]` aratın: yuva başına **bir** kök olmalı. İki kök varsa içerik iki kez basılmıştır (aşağıda "İçerik iki kez görünüyor").
3. Kökün `data-selwise-ssr-v` değeri, **Network** sekmesindeki `config` yanıtının `enrichment.v` değeriyle aynı olmalı. Farklıysa sayfanız önbellekte bayat; widget kural tarayıcı yedekliyse taze içeriği basıp bayat kökü kaldırır, yalnızca sunucu kuralında kökü korur.
4. SSR çağrısının kendisi sunucudan yapıldığı için tarayıcı Network sekmesinde görünmez. Anahtarı ve yanıtı kendi makinenizden deneyin:

```bash
curl -i "https://api.selwise.com/api/v1/public/sites/SITE_KEY/ssr/enrichments?code=ABC-1&slots=urun-aciklama-alti" \
  -H "x-selwise-api-key: swpk_live_..."
```

`200`, `Cache-Control: public, max-age=60, stale-while-revalidate=600`, bir `ETag` ve `slots.urun-aciklama-alti.html` dolu olmalı. `html` boşsa: kural segment hedefli, test modunda, **Browser only**, yuva adı farklı, ürün koduna eşleşmiyor ya da kural etkin değil.

## Sorun giderme

| Belirti | Sebep |
| --- | --- |
| `curl` çıktısında `data-selwise-ssr` yok | Şablon yuvayı basmıyor, ad uyuşmuyor, anahtar yok/yanlış, ürün kodu kuralla eşleşmiyor ya da çağrı zaman aşımına uğruyor. Parçalar fail-open yazıldığı için hata sayfayı bozmaz, sessizce boş döner; sunucu günlüğünüzde SSR çağrısının durum kodunu arayın |
| API doğrudan `403` | Anahtarın amacı yanlış (yalnızca **Mobile app** anahtarı bu uca girmez), anahtar iptal edilmiş ya da kanal bir uygulama kanalı |
| API `401` | `x-selwise-api-key` başlığı gitmiyor (proxy sildi, ortam değişkeni boş) |
| İçerik iki kez görünüyor | Dönen HTML'i kendi sanitizer'ınızdan geçirmiş olabilirsiniz: genel amaçlı bir sanitizer `data-selwise` nitelikleri siler, widget bloğu tanıyamaz ve ikinci bir kopya basar. Ya da şablonda yuva iki kez çağrılıyor |
| Sunucuda hiç, tarayıcıda var | Kural segment hedefli veya test modunda. İkisi de sunucu yanıtına bilinçli olarak girmez |
| Sunucuda var, sonra kayboluyor | Sayfa önbelleğinizdeki damga bayat ve kural **Server only**: widget kökü sildi, geri çizecek tarayıcı yolu yok. Kuralı **Server, with browser fallback** yapın |
| Stil uygulanmamış | Yanıtın CSS'i ayrı bir alanda gelir ve sayfaya bir kez basılması gerekir. Hazır parçalar bunu kendisi yapar |
| İçerik eski kalıyor | Önbellek zinciri: sizin sayfa önbelleğiniz (bizim 60 saniyemizden uzun) ya da kendi süreç önbelleğiniz. Önbelleğin ömrünü kısaltın |
| Sayfa yavaşladı | Zaman aşımı çok uzun ya da çağrı önbelleksiz. 400 ms zaman aşımı koyun ve yanıtı 60 saniye tutun |

## Güvenlik

- **Anahtar yalnızca sunucuda.** Şablona, istemci paketine, tarayıcıya sızdırmayın.
- **İçeriği kendiniz temizlemeyin ve değiştirmeyin.** Yanıt zaten yer tutucuları HTML kaçışıyla doldurulmuş, izin listesine göre temizlenmiş ve CSS'i kapsamlanmış hâlde gelir. Genel amaçlı bir temizleyici ya da "güzelleştirici" `data-selwise-*` niteliklerini siler. Şablon motorunuzun kaçışını yalnızca bu çıktı için kapatın (`|safe`, `<%-`, `dangerouslySetInnerHTML`) ve sayfanın başka hiçbir veri parçası için kapatmayın.
- **Ziyaretçi verisi göndermeyin.** İstekte çerez, oturum kimliği, e-posta ya da başka kişisel veri taşımayın; çağrı yalnızca ürün kodu ve yuva adı taşır. Yanıt zaten ziyaretçiye göre değişmez.
- **Çıktıyı kişiye özel bir sayfanın içine gömüyorsanız** (sepetiniz, hesabınız) sayfayı yine herkesle paylaşılan bir önbelleğe yazmayın.

## Uygulamada karşılığı

Mobil uygulama kanalında SSR yoktur ve olamaz (bir uygulamayı tarayıcı botu okumaz). Aynı kurallar, aynı bloklar uygulamada **yuva adıyla** yerleştirilir ve `SelwiseEnrichment` bileşeni ya da `app/enrichments` ucu içeriği hazır HTML olarak verir: [Mobil Uygulamada Zenginleştirme](/docs/product-enrichment/mobile-app), [Zenginleştirme API'si](/docs/api-reference/enrichment-api#appenrichments).

## Sırada ne var

- [Sunucu Taraflı Render API](/docs/api-reference/ssr-api): HTTP sözleşmesi
- [Sunucu Taraflı Render Örnekleri](/docs/product-enrichment/server-side-recipes): PHP, Node, Next.js, Django
- [Ürün Zenginleştirme](/docs/product-enrichment/overview): panelde kural kurma
