195 lines
9.1 KiB
Markdown
195 lines
9.1 KiB
Markdown
# Faz 2 Planı — Marmaris Local
|
||
|
||
**Önceki:** `prd.md` (MVP — tamamlandı)
|
||
**Bu doküman:** MVP sonrası geliştirme sırası
|
||
|
||
Sıralama mantığı: önce trafik getiren şey (içerik/SEO), sonra geleni tutan şey (etkileşim), sonra parayı getiren şey (gelir modeli — ama listeleme sayısı bir eşiğe ulaşmadan anlamsız), en altta her an eklenebilecek düşük efor altyapı işleri.
|
||
|
||
---
|
||
|
||
## Faz 2.1 — İçerik / SEO Motoru (öncelik: yüksek, ilk başlanacak)
|
||
|
||
Directory sitesi tek başına trafik çekmez — kürasyonlu içerik olmadan sadece Google'ın zaten indekslediği işletmelerin bir kopyası olursun. Bu faz olmadan diğerlerinin anlamı sınırlı.
|
||
|
||
### Blog
|
||
|
||
```prisma
|
||
model BlogPost {
|
||
id String @id @default(cuid())
|
||
slug String @unique
|
||
titleTr String
|
||
titleEn String
|
||
titleRu String
|
||
contentTr String // MDX/rich text
|
||
contentEn String
|
||
contentRu String
|
||
coverImage String? // Openinary
|
||
tags String[]
|
||
relatedListingIds String[] // Listing'lere internal link için
|
||
|
||
publishedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
deletedAt DateTime?
|
||
}
|
||
```
|
||
|
||
- Route: `/blog`, `/blog/[slug]`
|
||
- Admin: `/admin/blog` — rich text editör (Tiptap öneri, shadcn ile uyumlu)
|
||
- Her yazıda ilgili `Listing`'lere otomatik link kartı (internal linking = SEO)
|
||
- `schema.org Article` structured data
|
||
|
||
**İlk içerik önerisi (mock/gerçek karışık başlanabilir):** "Marmaris'te Nerede Yenir 2026", "Gün Batımı İçin 5 Mekan", "İçmeler mi Yat Limanı mı — Nerede Kalınır"
|
||
|
||
### Kürasyon Listeleri (Koleksiyonlar)
|
||
|
||
```prisma
|
||
model Collection {
|
||
id String @id @default(cuid())
|
||
slug String @unique
|
||
titleTr String
|
||
titleEn String
|
||
titleRu String
|
||
descriptionTr String
|
||
descriptionEn String
|
||
descriptionRu String
|
||
coverImage String?
|
||
|
||
listings Listing[] @relation("CollectionListings")
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
```
|
||
|
||
- Route: `/seckiler`, `/secki/[slug]`
|
||
- Admin: `/admin/collections` — listeleme seç (multi-select), sürükle-bırak sıralama
|
||
- Örnek koleksiyonlar: "Aile Dostu 5 Restoran", "Bütçe Dostu Mekanlar", "Deniz Kenarında Kahvaltı"
|
||
- Bu, "Yerel Onaylı" mührünün doğal uzantısı — tekil rozetten küratörlü gruplamaya geçiş
|
||
|
||
### RSS
|
||
|
||
- `/feed.xml` — blog yazıları + yeni eklenen "Yerel Onaylı" listelemeler
|
||
- Öncelik gerekçesi: kendi başına trafik kanalı değil ama Google News / ileride newsletter otomasyonu (n8n) için altyapı — blog ile aynı sprintte, 1 saatlik ek iş
|
||
|
||
---
|
||
|
||
## Faz 2.2 — Etkileşim (öncelik: orta, 2.1 ile paralel gidebilir)
|
||
|
||
Auth gerektirmeyen, dönüşümü ucuza artıran işler.
|
||
|
||
### Favorilere Ekle
|
||
|
||
- **Backend yok** — client-side localStorage, `savedListingIds: string[]`
|
||
- Route: `/kaydedilenler` — localStorage'daki ID'leri okuyup listeler
|
||
- Paylaşılabilir link: `/kaydedilenler?ids=abc,def,ghi` — arkadaşla link paylaşımı, auth şart değil
|
||
|
||
### WhatsApp Paylaş
|
||
|
||
- Her listeleme detay sayfasında `wa.me/?text=...` deep link buton
|
||
- Şema değişikliği yok, ~30 dakikalık iş
|
||
|
||
### Instagram Feed (her listeleme sayfasında)
|
||
|
||
Sadece anasayfa değil — Instagram hesabı olan **her** listeleme (kategori fark etmez) kendi detay sayfasında son gönderilerini gösterir + profile giden link.
|
||
|
||
Veri kaynağı: RapidAPI üzerinden bir Instagram scraper endpoint'i (ör. "Instagram Scraper API2" / "Instagram Data" — RapidAPI marketplace'te birden fazla sağlayıcı var, fiyat/limit karşılaştırıp seçilecek). Instagram'ın resmi Graph API'si business-verified hesap + Facebook app review gerektirdiği için, işletme sahiplerinden bu izni almak pratik değil — RapidAPI scraper daha az sürtünmeli.
|
||
|
||
**Canlı istekte RapidAPI çağırma — YAPMA.** Her sayfa görüntülemesinde çağırırsan hem yavaş hem RapidAPI faturası trafiğe bağlı, öngörülemez olur. Bunun yerine 24 saatte bir çalışan bir arka plan job'ı ile önbelleğe al:
|
||
|
||
```prisma
|
||
model InstagramFeedCache {
|
||
id String @id @default(cuid())
|
||
listingId String @unique
|
||
handle String
|
||
posts Json // [{ imageUrl, caption, permalink, postedAt }]
|
||
fetchedAt DateTime @default(now())
|
||
|
||
listing Listing @relation(fields: [listingId], references: [id])
|
||
}
|
||
```
|
||
|
||
- `Listing.instagram` alanı zaten PRD'de vardı (handle/URL) — bunu kaynak olarak kullan
|
||
- Senkron mekanizması: n8n'de günlük (24h) tetiklenen bir workflow — `instagram` dolu olan tüm `Listing`'leri gez, her biri için RapidAPI'yi çağır, sonucu `POST /api/cron/instagram-sync` (secret header ile korunan internal endpoint) üzerinden `InstagramFeedCache`'e yaz. n8n zaten elinde (auto2.ayris.tech), bu tam onun işi.
|
||
- Frontend sadece DB'den okur, hiçbir zaman canlı RapidAPI çağrısı yapmaz — sayfa yükü hızlı kalır
|
||
- `fetchedAt` 24 saatten eskiyse UI'da sessizce eski veriyi göstermeye devam et (job bir sonraki döngüde tazeleyecek), kullanıcıya hata gösterme
|
||
|
||
### Menüyü Gör Butonu
|
||
|
||
Restoran/bar/club tarzı kategorilerde, işletmenin kendi menü sayfası/PDF'i varsa, listeleme detay sayfasında "Menüyü Gör" butonu → yeni sekmede işletmenin kendi sitesine/menü linkine gönderir.
|
||
|
||
```prisma
|
||
// Listing modeline eklenecek alan
|
||
menuUrl String?
|
||
```
|
||
|
||
- Kategoriye göre kod içinde zorlama yok — buton sadece `menuUrl` doluysa görünür, admin doğal olarak bunu sadece ilgili kategorilerde dolduracak
|
||
- Admin formunda (`/admin/listings/[id]`) yeni alan: "Menü Linki (opsiyonel)"
|
||
|
||
### "Yeni Eklenenler" Widget
|
||
|
||
- Şema değişikliği yok — `Listing.findMany({ orderBy: createdAt desc, take: 6 })`
|
||
- Anasayfa + kategori sayfalarında taze içerik sinyali
|
||
|
||
---
|
||
|
||
## Faz 2.3 — Gelir Modeli: Öne Çıkan Listeleme (öncelik: koşullu — belirli bir listeleme sayısına ulaşınca anlamlı)
|
||
|
||
**Ön koşul:** En az ~30-40 aktif "Yerel Onaylı" listeleme olmadan bu faza girmeye gerek yok — az listelemeyle "öne çıkan" kavramının hiçbir ayırt edici gücü olmaz.
|
||
|
||
```prisma
|
||
// Listing modeline eklenecek alanlar
|
||
isFeatured Boolean @default(false)
|
||
featuredUntil DateTime? // otomatik süre dolumu için
|
||
```
|
||
|
||
- Kategori/anasayfa sıralamasında `isFeatured` önce gelir
|
||
- Admin toggle + tarih seçici (`/admin/listings/[id]`)
|
||
- Gelir mekanizması: Ayris Tech'in mevcut 40+ müşteri ilişkisiyle doğal satış kanalı — ayrı bir ödeme altyapısı MVP'de gerekmez, manuel faturalama yeterli
|
||
|
||
---
|
||
|
||
## Faz 2.4 — Altyapı (öncelik: düşük efor, istenilen an araya sokulabilir)
|
||
|
||
### PWA
|
||
|
||
- `manifest.json` + service worker (`next-pwa` veya manuel)
|
||
- "Ana ekrana ekle" — turist telefonda kullanıyor, pratik fark yaratır
|
||
|
||
### Analytics
|
||
|
||
- Kendi VPS'teki self-hosted panel (Plausible-tarzı, zaten var) — yeni SaaS ödemesi gerekmiyor
|
||
- Takip edilecek event'ler: favori ekleme, WhatsApp paylaşım tıklaması, QR kaynak trafiği, kategori bazlı arama
|
||
|
||
---
|
||
|
||
## Önerilen Sıra
|
||
|
||
1. **Blog + Koleksiyonlar** (2.1) — SEO motoru olmadan geri kalanının önemi düşük
|
||
2. **Menü butonu + Instagram feed + Favoriler + WhatsApp paylaş + Yeni Eklenenler** (2.2) — düşük-orta efor, hızlı kazanım, 2.1 ile aynı sprint'te bile yapılabilir. Instagram feed'in n8n cron tarafı ayrı bir iş parçası olarak planlanmalı (senkron job önce kurulmalı, sonra frontend)
|
||
3. **PWA + Analytics** (2.4) — ne zaman uygun olursa, bağımsız işler
|
||
4. **Öne Çıkan Listeleme** (2.3) — listeleme sayısı eşiğe ulaşınca devreye al
|
||
|
||
## Env Değişkenleri (Faz 2 eklentileri)
|
||
|
||
```env
|
||
RAPIDAPI_KEY=""
|
||
RAPIDAPI_INSTAGRAM_HOST="" # seçilen sağlayıcıya göre (ör. instagram-scraper-api2.p.rapidapi.com)
|
||
CRON_SECRET="" # /api/cron/instagram-sync endpoint'ini korumak için
|
||
```
|
||
|
||
## Kararlar (önceki açık sorulardan)
|
||
|
||
- **Blog içeriği:** DeepSeek ile üretilip otomasyonla (n8n) yayınlanacak — akış detayı ayrıca kurulacak
|
||
- **Koleksiyon kapak görselleri:** stok veya yerinde çekim fark etmiyor, ikisi de kullanılabilir
|
||
- **Öne çıkan listeleme fiyatlandırması:** aylık sabit ücret (süre bazlı değil)
|
||
- **RapidAPI sağlayıcısı:** free-tier bir model kullanılacak
|
||
|
||
**Free-tier RapidAPI konusunda dikkat edilecek nokta:** RapidAPI'deki Instagram scraper'ların çoğunda (ör. "Instagram Scraper API2", "Instagram Looter2") free/basic plan genelde aylık ~50-100 istek civarında sınırlı. 24 saatte bir senkron ile bile, listeleme sayısı arttıkça (30-40 listeleme × günlük = ~900-1200 istek/ay) free tier hızla yetersiz kalır. Pratik yaklaşım:
|
||
- Az listelemeyle (MVP/Faz 2 başlangıcı) free tier rahatça yeter
|
||
- Listeleme sayısı büyüdükçe ya senkron sıklığını düşür (24h yerine 48-72h), ya sadece `isLocalApproved` olanları senkronla, ya da o noktada ücretli tiere geç
|
||
- İlk kurulumda 2-3 free-tier sağlayıcıyı denemek (aynı endpoint şeklini farklı sağlayıcıdan alacağın için `InstagramFeedCache` şeması sağlayıcıdan bağımsız kalacak şekilde tasarlandı) makul bir başlangıç
|
||
|
||
## Açık Sorular
|
||
|
||
- [ ] Free-tier adaylarından hangisi seçilecek — birkaçını test edip (aynı listeleme üzerinde) veri kalitesi/limit karşılaştırması yapılabilir |