# Yayın Render’ı

> TecofRender ile yayın — prop’lar, sayfaya bastığı stil/tema/font çıktısı, CMS şablon sayfaları ve token’lar, TecofPicture ve odak noktası, Powered by bandı, yapım aşaması sayfası ve etkileşim çalışma zamanı.
>
> Kaynak: https://tecof-app-dev.vercel.app/editor-render · Bölüm: Tema geliştirme · Son güncelleme: 2018-10-20

`TecofRender`, editörün kaydettiği sayfayı yayında çizer. Veri getirmez: sayfayı sunucuda çekip renderer’a verirsiniz. Editörle **aynı** `StudioConfig`’i kullanmalısınız; config’te olmayan `type`’lar sessizce atlanır.

```tsx title="components/RenderClient.tsx"
"use client";

import { TecofRender, type PuckPageData } from "@tecof/theme-editor";
import { editorConfig } from "@/app/editor-config";

export default function RenderClient({ data, cmsData }: { data: PuckPageData; cmsData?: Record<string, unknown> | null }) {
  return <TecofRender data={data} config={editorConfig} cmsData={cmsData} />;
}
```

| Prop | Tip | Açıklama |
| :--- | :--- | :--- |
| `data` | `PuckPageData` | Yayınlanacak doküman (`content`, `root`, `zones`) |
| `config` | `StudioConfig` | Editörle aynı bileşen kaydı |
| `cmsData` | `Record<string, unknown> \| null` | CMS şablon sayfasının kaydı; bileşenlere `puck.metadata.cmsData` olarak iletilir |
| `className` | `string?` | Kök elemana ek sınıf |

## Renderer ne yapar

Her render’da:

1. `config.migrations` varsa dokümanı yükseltir ([İzinler ve migration](/editor-permissions#veri-migrationi)).
2. Katmanlar’da gizlenen (`_hidden`) düğümleri atlar; yalnız gizli çocukları olan slot boş sayılır.
3. Her düğümün `_tecofStyles` token’larını sınıflara derler ve `className`’e ekler.
4. Slot alanlarını hazır slot elemanları olarak bileşene verir; `repeatSource`’lu slotları satır başına tekrarlar.
5. Sayfaya şunları basar:

| Çıktı | İçerik |
| :--- | :--- |
| `<style data-tecof-styles>` | Dokümanda kullanılan tüm stil sınıflarının CSS’i (masaüstü-öncelikli, bkz. [Stil editörü](/editor-styles#cssi-kim-uretir)) |
| `<style data-tecof-theme>` | Çözülmüş temanın `--theme-*` değişkenleri (karanlık mod açıksa `:root.dark` bloğu da) |
| `<link>` + `<style data-tecof-font-faces>` | Google Fonts ve yüklenen fontlar |
| `<style data-tecof-animations>` | Giriş animasyonu anahtar kareleri |
| `<script data-tecof-interactions>` | Etkileşim kuralları (yalnız kullanılıyorsa) |
| `<script data-tecof-darkmode>` | Karanlık mod ayarı (yalnız `config.darkMode` açıksa) |

İstemcide küçük bir çalışma zamanı **görününce animasyon** (`reveal`), **parallax**, **etkileşimler** ve **karanlık mod** düğmesini bağlar. Sayfayı `TecofRender` olmadan kendiniz render ediyorsanız aynı davranış için `initInteractions(document)` ve `initDarkMode(document)` dışa aktarılır.

> [!IMPORTANT] Slot yerleşim CSS’i temada olmalı
> `<Slot layout="…">` / `defineSection` slot’larının yerleşim sınıfları (`.tecof-slot-row`, `.tecof-slot-row-wrap`, `.tecof-slot-col`, `.tecof-slot-contents`) yayın sayfasında paket CSS’i yüklenmediği için temanın global CSS’inde (`@layer base`) tanımlı olmalıdır. Tecof çekirdek teması bunları içerir; türev temada kaldırmayın.

## Sayfa verisini çekmek

Tecof temaları sayfayı vitrin ucundan alır:

```ts title="lib/api/store.ts (tecof-theme-core, özet)"
const THEME_ID = process.env.NEXT_PUBLIC_THEME_ID!;
const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/store/render?themeId=${THEME_ID}&locale=${locale}`, {
  method: "POST",
  headers: { "Content-Type": "application/json", "x-secret-key": process.env.NEXT_PUBLIC_MERCHANT_SECRET_KEY! },
  body: JSON.stringify({ slug, themeId: THEME_ID, locale, showDraftData, previewToken }),
});
const json = await res.json();
const pageData = showDraftData ? json.data.draftData : json.data.publishedData;
const cmsData = json.data._cmsData ?? null;
```

Taslak önizleme (`showDraftData`) backend’de imzalı `previewToken` ister; token geçersizse yanıt sessizce yayınlanmış içeriğe düşer. Vitrin anahtarı tarayıcıda gezdiği için tek başına taslak okuma yetkisi sayılmaz.

Yanıt sayfa verisinin yanında SEO alanlarını (`metaTitle`, `metaDescription`, `ogImage`, `noIndex`), sayfaya özel kodu (`customHeadCode`, `customBodyCode`) ve çok dilli adres bilgisini (`localizedSlug`, `slugAlternates`, `redirectTo`) taşır; ayrıntı: [Tema SEO’su](/seo-geo) ve [Public API](/public-apis). Aynı akış için `TecofApiClient.getPublishedPage(slug, locale)` de kullanılabilir ([API istemcisi](/editor-api-client)).

## CMS şablon sayfaları

Bir koleksiyonun şablon sayfası `/<koleksiyon>/<içerik>` adresinde render edilirken **backend** şablondaki token’ları kayıt verisiyle doldurur ve yanıtla birlikte ham kaydı `_cmsData` olarak döner. Renderer token’ları yeniden çözmez; kaydı bileşenlere `puck.metadata.cmsData` olarak iletir.

| Token | Değer |
| :--- | :--- |
| `{{ data.<alan> }}` ya da `{{ <alan> }}` | Kaydın alanı (çok dilli alan istek diline göre çözülür) |
| `{{ url }}` | Render edilen sayfanın tam adresi |
| `{{ siteName }}` | Mağaza adı (ör. `"{{ name }} \| {{ siteName }}"` meta başlığı) |
| `{{ locale }}`, `{{ itemSlug }}`, `{{ collectionSlug }}` | İstek dili ve o dildeki adres parçaları |
| `{{ publishedDate }}` | Okunur yayın tarihi (istek dilinde) |
| `{{ _refs.<alan>.slug }}` | Referans alanının tam nesnesi (alanın kendisi etiket olarak çözülür) |

- Prop’un değeri **yalnız** token ise (`"{{ data.gallery }}"`) dizi/nesne değeri olduğu gibi yerleştirilir; metne gömülü token metne çevrilir, bulunamayan gömülü token boş metne döner.
- Meta başlık ve açıklama da aynı çözümden geçer.
- Editörde metin alanlarındaki `{ }` düğmesi token’ı ekler ([CMS veri bağlama](/editor-fields#cms-veri-baglama-metin-alanlari)); `{{ item.* }}` token’ları ise repeat zone’a aittir ve kütüphane tarafından çözülür ([Repeat zone](/editor-slots#repeat-zone-oge-sablonu)).

Bileşen ham kayda ihtiyaç duyarsa:

```tsx
render: ({ puck, className }) => {
  const cms = puck.metadata?.cmsData as { title?: unknown } | null;
  return <article className={className}>{/* cms?.title … */}</article>;
}
```

## TecofPicture

`UploadedFile` kaydından doğru medya adresini seçer, görsel ve videoyu ayırt eder, boyut varyantlarını (`thumbnail`, `medium`, `large`, `full`) kullanır.

```tsx
import Image from "next/image";
import { TecofPicture } from "@tecof/theme-editor";

<TecofPicture data={file} alt="Ürün görseli" size="large" />
<TecofPicture data={file} fill />
<TecofPicture data={file} ImageComponent={Image} imageProps={{ quality: 85, priority: true }} />
<TecofPicture data={file} fancybox fancyboxName="product-gallery" />
```

| Prop | Tip | Açıklama |
| :--- | :--- | :--- |
| `data` | `UploadedFile \| null \| undefined` | Dosya kaydı (UploadField değerinin bir öğesi) |
| `alt` | `string \| null` | Alternatif metin |
| `size` | `thumbnail \| medium \| large \| full` | Boyut varyantı |
| `fill` | `boolean` | Üst kapsayıcıyı kaplar (`position: absolute`) |
| `loading` | `lazy \| eager` | Yükleme stratejisi |
| `width`, `height` | `number` | Boyutlar |
| `className`, `imgClassName`, `style`, `imgStyle` | — | Kapsayıcı ve `img` için sınıf/stil |
| `usePlaceholder`, `blurDataURL` | — | Bulanık yer tutucu |
| `ImageComponent`, `imageProps` | — | Özel görsel bileşeni (ör. Next.js `Image`) ve ek prop’ları |
| `fancybox`, `fancyboxName` | — | Lightbox galerisi |

- **Odak noktası:** dosyada `focalPoint: { x, y }` varsa `object-position: "X% Y%"` uygulanır; `object-fit: cover` kırpmalarında seçilen nokta kadrajda kalır. Kendi `imgStyle.objectPosition` değeriniz kazanır; odak yoksa ya da merkezdeyse çıktı değişmez. Yardımcılar: `focalPointToObjectPosition`, `clampFocalPoint`, `isDefaultFocalPoint`, `DEFAULT_FOCAL_POINT`.
- CMS referans kaydı (`type: "image/reference"`) dosya değildir; `null` render edilir. Stok görsel gibi harici dosyalar kayıttaki mutlak `url`’i kullanır.

## Powered by bandı

Panelde (Ayarlar → Powered by bandı) tanımlanan “Powered by Tecof” bandını basar. Temada elle yazmayın; bileşeni kullanın.

```tsx
import { PoweredBy } from "@tecof/theme-editor";

// Layout (SSR): sunucudaki merchant-info ile ilk boyama; askı/yapım aşamasında null verin
const band = merchantInfo.isSuspended || merchantInfo.isUnderConstruction ? null : merchantInfo.poweredBy ?? null;
<PoweredBy initialData={band} defaultLanguage={merchantInfo.defaultLanguage} locale={locale} />

// <TecofProvider> içinde: kendisi çeker (SWR, 10 dk tekilleştirme)
<PoweredBy />
```

| Prop | Açıklama |
| :--- | :--- |
| `locale` | Dil; verilmezse Studio dili → `<html lang>` → varsayılan dil → ilk dil |
| `defaultLanguage` | SSR’da `merchantInfo.defaultLanguage`; verilmezse dil flaşı olabilir |
| `mode` | `light` / `dark`; verilmezse `<html class="dark">` → `prefers-color-scheme` |
| `initialData` | SSR’dan gelen `merchantInfo.poweredBy`; `null` = band yok, `undefined` = kendisi çeker |
| `apiUrl`, `secretKey` | Provider dışında çekmek için |
| `renderBoth` | İki varyantı da basar, seçimi CSS yapar (JS’siz sıfır flaş) |
| `revalidateOnMount`, `className` | — |

Hak yoksa (`showPoweredBy=false`), mağaza askıdaysa ya da yapım aşamasındaysa bileşen hiçbir şey basmaz (bu bayrakları yalnız kendi çektiği `merchant-info`’dan okur; SSR yolunda süzgeci tema uygular). Kendi işaretlemenizi basmak için `usePoweredBy(options)` → `{ band, html, css, js, visible, isLoading, error, refresh, … }`.

## Yapım aşaması sayfası

```tsx
import { UnderConstruction } from "@tecof/theme-editor/under-construction";

<UnderConstruction title="Çok yakında" description="Mağazamız hazırlanıyor." logoUrl={logo} accentColor="#9ae600" />
```

Prop’lar: `title`, `description`, `subtitle?`, `logoUrl?`, `accentColor?`.

## Etkileşimler

Inspector’ın **Etkileşimler** sekmesinde tanımlanan kurallar (`click` / `hover` → `scrollTo`, `toggleClass`, `show`, `hide`, `toggle`; hedef bir düğüm, `self` ya da sayfa başı `top`) düğümün `_interactions` prop’unda saklanır. `_startHidden` işaretli düğüm bir eylem onu gösterene kadar gizli başlar. `TecofRender` kuralları kendisi bağlar; özel render için `collectInteractionRegistry(doc)` + `initInteractions(document)` kullanılır.
