feat: add full SEO (generateMetadata, OG, Twitter Card, JSON-LD, sitemap, robots, llms.txt)

This commit is contained in:
mstfyldz
2026-06-05 17:41:41 +03:00
parent 376b620a6a
commit a9a2c08e5f
6 changed files with 415 additions and 15 deletions
+126 -5
View File
@@ -1,6 +1,7 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Playfair_Display, Lato } from "next/font/google"; import { Playfair_Display, Lato } from "next/font/google";
import "../globals.css"; import "../globals.css";
import { prisma } from "@/lib/db";
const playfair = Playfair_Display({ const playfair = Playfair_Display({
variable: "--font-playfair", variable: "--font-playfair",
@@ -13,15 +14,91 @@ const lato = Lato({
weight: ["300", "400", "700", "900"], weight: ["300", "400", "700", "900"],
}); });
export const metadata: Metadata = {
title: "Moy Beach Akyaka | Lüks & Huzur",
description: "Akyaka'nın kalbinde doğayla iç içe lüks bir beach, restoran ve otel deneyimi.",
};
export async function generateStaticParams() { export async function generateStaticParams() {
return [{ lang: 'tr' }, { lang: 'en' }]; return [{ lang: 'tr' }, { lang: 'en' }];
} }
export async function generateMetadata(
{ params }: { params: Promise<{ lang: string }> }
): Promise<Metadata> {
const { lang } = await params;
const settings = await prisma.siteSettings.findFirst();
const isTr = lang === 'tr';
const title = isTr
? (settings?.metaTitleTr || 'Moy Beach Akyaka | Lüks & Huzur')
: (settings?.metaTitleEn || 'Moy Beach Akyaka | Luxury & Serenity');
const description = isTr
? (settings?.metaDescTr || "Akyaka'nın kalbinde doğayla iç içe lüks bir beach, restoran ve otel deneyimi.")
: (settings?.metaDescEn || 'A luxury beach, restaurant and hotel experience immersed in nature at the heart of Akyaka.');
const keywords = isTr
? (settings?.metaKeywordsTr || 'moy beach, akyaka, beach, restoran, otel, muğla, deniz, tatil')
: (settings?.metaKeywordsEn || 'moy beach, akyaka, beach, restaurant, hotel, mugla, sea, holiday');
const siteUrl = settings?.siteUrl || 'https://moybeach.com';
const ogImage = settings?.ogImage || `${siteUrl}/moy-beach.jpg`;
return {
title,
description,
keywords,
authors: [{ name: 'Moy Beach' }],
creator: 'Moy Beach',
publisher: 'Moy Beach',
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
alternates: {
canonical: `${siteUrl}/${lang}`,
languages: {
'tr': `${siteUrl}/tr`,
'en': `${siteUrl}/en`,
},
},
openGraph: {
type: 'website',
locale: isTr ? 'tr_TR' : 'en_US',
alternateLocale: isTr ? 'en_US' : 'tr_TR',
url: `${siteUrl}/${lang}`,
siteName: 'Moy Beach',
title,
description,
images: [
{
url: ogImage,
width: 1200,
height: 630,
alt: title,
},
],
},
twitter: {
card: 'summary_large_image',
title,
description,
images: [ogImage],
...(settings?.twitterHandle ? { creator: `@${settings.twitterHandle}`, site: `@${settings.twitterHandle}` } : {}),
},
verification: {
...(settings?.googleVerify ? { google: settings.googleVerify } : {}),
},
other: {
'theme-color': '#1a1a2e',
},
};
}
export default async function RootLayout({ export default async function RootLayout({
children, children,
params, params,
@@ -30,9 +107,53 @@ export default async function RootLayout({
params: Promise<{ lang: string }>; params: Promise<{ lang: string }>;
}>) { }>) {
const { lang } = await params; const { lang } = await params;
const settings = await prisma.siteSettings.findFirst();
const siteUrl = settings?.siteUrl || 'https://moybeach.com';
// JSON-LD Structured Data
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
'@id': siteUrl,
name: 'Moy Beach',
url: siteUrl,
logo: settings?.logoUrl || `${siteUrl}/logo.png`,
image: settings?.ogImage || `${siteUrl}/moy-beach.jpg`,
description: lang === 'tr'
? (settings?.metaDescTr || "Akyaka'nın kalbinde doğayla iç içe lüks bir beach, restoran ve otel deneyimi.")
: (settings?.metaDescEn || 'A luxury beach, restaurant and hotel experience immersed in nature at the heart of Akyaka.'),
telephone: settings?.phone || '',
email: settings?.email || '',
address: {
'@type': 'PostalAddress',
streetAddress: settings?.addressText1 || '',
addressLocality: 'Akyaka',
addressRegion: 'Muğla',
addressCountry: 'TR',
},
openingHours: settings?.workingHours || '',
sameAs: [
settings?.instagramUrl,
settings?.facebookUrl,
settings?.youtubeUrl,
].filter(Boolean),
servesCuisine: ['Turkish', 'Mediterranean', 'Seafood'],
priceRange: '₺₺₺',
geo: {
'@type': 'GeoCoordinates',
latitude: 37.0437,
longitude: 28.3249,
},
};
return ( return (
<html lang={lang} className="scroll-smooth"> <html lang={lang} className="scroll-smooth">
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
</head>
<body className={`${lato.variable} ${playfair.variable} antialiased`}> <body className={`${lato.variable} ${playfair.variable} antialiased`}>
{children} {children}
</body> </body>
+136 -10
View File
@@ -1,7 +1,7 @@
import { prisma } from '@/lib/db'; import { prisma } from '@/lib/db';
import { getSession } from '@/lib/auth'; import { getSession } from '@/lib/auth';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation'
import { Save, Upload } from 'lucide-react'; import { Save } from 'lucide-react';
import { updateSiteSettings } from '../actions'; import { updateSiteSettings } from '../actions';
import { revalidatePath } from 'next/cache'; import { revalidatePath } from 'next/cache';
import { uploadImage } from '@/lib/cloudinary'; import { uploadImage } from '@/lib/cloudinary';
@@ -32,6 +32,16 @@ export default async function SettingsAdminPage() {
facebookUrl: formData.get('facebookUrl') as string, facebookUrl: formData.get('facebookUrl') as string,
youtubeUrl: formData.get('youtubeUrl') as string, youtubeUrl: formData.get('youtubeUrl') as string,
whatsappNumber: formData.get('whatsappNumber') as string, whatsappNumber: formData.get('whatsappNumber') as string,
// SEO fields
siteUrl: formData.get('siteUrl') as string,
metaTitleTr: formData.get('metaTitleTr') as string,
metaTitleEn: formData.get('metaTitleEn') as string,
metaDescTr: formData.get('metaDescTr') as string,
metaDescEn: formData.get('metaDescEn') as string,
metaKeywordsTr: formData.get('metaKeywordsTr') as string,
metaKeywordsEn: formData.get('metaKeywordsEn') as string,
twitterHandle: formData.get('twitterHandle') as string,
googleVerify: formData.get('googleVerify') as string,
}; };
const logoFile = formData.get('logo') as File; const logoFile = formData.get('logo') as File;
@@ -39,19 +49,22 @@ export default async function SettingsAdminPage() {
data.logoUrl = await uploadImage(logoFile); data.logoUrl = await uploadImage(logoFile);
} }
const ogImageFile = formData.get('ogImage') as File;
if (ogImageFile && ogImageFile.size > 0) {
data.ogImage = await uploadImage(ogImageFile);
}
await updateSiteSettings(data); await updateSiteSettings(data);
revalidatePath('/', 'layout'); revalidatePath('/', 'layout');
redirect('/admin/settings'); redirect('/admin/settings');
}} className="space-y-8"> }} className="space-y-10">
{/* Logo Section */} {/* Logo Section */}
<section> <section>
<h2 className="text-xl font-bold mb-4 border-b pb-2">Logo Ayarları</h2> <h2 className="text-xl font-bold mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">Logo Ayarları</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div> <div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1"> <label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Yeni Logo Yükle</label>
Yeni Logo Yükle
</label>
<input <input
type="file" type="file"
name="logo" name="logo"
@@ -73,7 +86,7 @@ export default async function SettingsAdminPage() {
{/* Contact Section */} {/* Contact Section */}
<section> <section>
<h2 className="text-xl font-bold mb-4 border-b pb-2">İletişim Bilgileri</h2> <h2 className="text-xl font-bold mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">İletişim Bilgileri</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div> <div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Telefon</label> <label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Telefon</label>
@@ -89,7 +102,7 @@ export default async function SettingsAdminPage() {
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">WhatsApp Numarası</label> <label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">WhatsApp Numarası</label>
<input type="text" name="whatsappNumber" defaultValue={settings?.whatsappNumber || ""} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="905344656235 (Sadece rakam, + olmadan)" /> <input type="text" name="whatsappNumber" defaultValue={settings?.whatsappNumber || ""} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="905344656235" />
</div> </div>
<div className="md:col-span-2"> <div className="md:col-span-2">
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Adres Satırı 1</label> <label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Adres Satırı 1</label>
@@ -104,7 +117,7 @@ export default async function SettingsAdminPage() {
{/* Social Media */} {/* Social Media */}
<section> <section>
<h2 className="text-xl font-bold mb-4 border-b pb-2">Sosyal Medya Linkleri</h2> <h2 className="text-xl font-bold mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">Sosyal Medya Linkleri</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div> <div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Instagram URL</label> <label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Instagram URL</label>
@@ -121,6 +134,119 @@ export default async function SettingsAdminPage() {
</div> </div>
</section> </section>
{/* SEO Section */}
<section>
<h2 className="text-xl font-bold mb-1 border-b border-slate-200 dark:border-slate-700 pb-2 flex items-center gap-2">
🔍 SEO & Metadata Ayarları
</h2>
<p className="text-sm text-slate-500 dark:text-slate-400 mb-6">
Arama motorları ve sosyal medya paylaşımları için meta bilgileri.
</p>
{/* Site URL */}
<div className="mb-6">
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Site URL <span className="text-slate-400 font-normal">(canonical & sitemap için)</span>
</label>
<input type="url" name="siteUrl" defaultValue={settings?.siteUrl || ""} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="https://moybeach.com" />
</div>
{/* Meta Titles */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Meta Başlık Türkçe
</label>
<input type="text" name="metaTitleTr" defaultValue={settings?.metaTitleTr || ""} maxLength={60} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="Moy Beach Akyaka | Lüks & Huzur" />
<p className="text-xs text-slate-400 mt-1">Max 60 karakter önerilir</p>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Meta Başlık İngilizce
</label>
<input type="text" name="metaTitleEn" defaultValue={settings?.metaTitleEn || ""} maxLength={60} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="Moy Beach Akyaka | Luxury & Serenity" />
<p className="text-xs text-slate-400 mt-1">Max 60 characters recommended</p>
</div>
</div>
{/* Meta Descriptions */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Meta Açıklama Türkçe
</label>
<textarea name="metaDescTr" rows={3} defaultValue={settings?.metaDescTr || ""} maxLength={160} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500 resize-none" placeholder="Akyaka'nın kalbinde doğayla iç içe lüks bir beach, restoran ve otel deneyimi." />
<p className="text-xs text-slate-400 mt-1">Max 160 karakter önerilir</p>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Meta Açıklama İngilizce
</label>
<textarea name="metaDescEn" rows={3} defaultValue={settings?.metaDescEn || ""} maxLength={160} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500 resize-none" placeholder="A luxury beach, restaurant and hotel experience in the heart of Akyaka." />
<p className="text-xs text-slate-400 mt-1">Max 160 characters recommended</p>
</div>
</div>
{/* Keywords */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Anahtar Kelimeler Türkçe
</label>
<input type="text" name="metaKeywordsTr" defaultValue={settings?.metaKeywordsTr || ""} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="moy beach, akyaka, beach, restoran, otel, muğla" />
<p className="text-xs text-slate-400 mt-1">Virgülle ayırın</p>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Keywords English
</label>
<input type="text" name="metaKeywordsEn" defaultValue={settings?.metaKeywordsEn || ""} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="moy beach, akyaka, beach, restaurant, hotel, mugla" />
<p className="text-xs text-slate-400 mt-1">Separate with commas</p>
</div>
</div>
{/* OG Image */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
OG / Paylaşım Görseli
<span className="text-slate-400 font-normal ml-1">(1200×630px önerilir)</span>
</label>
<input type="file" name="ogImage" accept="image/*" className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100" />
</div>
{settings?.ogImage && (
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Mevcut OG Görseli</label>
<div className="rounded-lg overflow-hidden border border-slate-200 dark:border-slate-700 w-full max-w-xs">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={settings.ogImage} alt="OG Image" className="w-full h-auto object-cover" />
</div>
</div>
)}
</div>
{/* Twitter & Google Verify */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Twitter/X Handle
<span className="text-slate-400 font-normal ml-1">(@işareti olmadan)</span>
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 font-medium">@</span>
<input type="text" name="twitterHandle" defaultValue={settings?.twitterHandle || ""} className="w-full pl-8 pr-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="moybeachakyaka" />
</div>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Google Search Console Doğrulama Kodu
</label>
<input type="text" name="googleVerify" defaultValue={settings?.googleVerify || ""} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="google-site-verification kodu" />
<p className="text-xs text-slate-400 mt-1">Google Search Console Doğrulama HTML Meta Tag'deki content değeri</p>
</div>
</div>
</section>
<div className="pt-4 flex justify-end"> <div className="pt-4 flex justify-end">
<button type="submit" className="flex items-center space-x-2 bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-lg transition-colors font-medium"> <button type="submit" className="flex items-center space-x-2 bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-lg transition-colors font-medium">
<Save className="w-5 h-5" /> <Save className="w-5 h-5" />
+87
View File
@@ -0,0 +1,87 @@
import { prisma } from '@/lib/db';
import { NextResponse } from 'next/server';
export const dynamic = 'force-dynamic';
export async function GET() {
const [settings, services] = await Promise.all([
prisma.siteSettings.findFirst(),
prisma.service.findMany({ orderBy: { createdAt: 'asc' } }),
]);
const siteUrl = settings?.siteUrl || 'https://moybeach.com';
const siteName = 'Moy Beach';
const serviceList = services.length > 0
? services.map(s => `- **${s.title}**: ${s.description}`).join('\n')
: '- Beach hizmetleri\n- Restoran\n- Otel';
const contactLines = [
settings?.phone ? `- Telefon: ${settings.phone}` : '',
settings?.email ? `- E-posta: ${settings.email}` : '',
settings?.addressText1 ? `- Adres: ${settings.addressText1}${settings.addressText2 ? ', ' + settings.addressText2 : ''}` : '',
settings?.workingHours ? `- Çalışma Saatleri: ${settings.workingHours}` : '',
].filter(Boolean).join('\n');
const socialLines = [
settings?.instagramUrl ? `- Instagram: ${settings.instagramUrl}` : '',
settings?.facebookUrl ? `- Facebook: ${settings.facebookUrl}` : '',
settings?.youtubeUrl ? `- YouTube: ${settings.youtubeUrl}` : '',
settings?.whatsappNumber ? `- WhatsApp: https://wa.me/${settings.whatsappNumber}` : '',
].filter(Boolean).join('\n');
const content = `# ${siteName}
> ${settings?.metaDescTr || "Akyaka'nın kalbinde doğayla iç içe lüks bir beach, restoran ve otel deneyimi."}
${siteName} is a luxury beach, restaurant and hotel located in Akyaka, Muğla, Turkey.
Situated at the heart of the Akyaka coast, it offers a unique blend of natural beauty,
fine dining, and premium hospitality.
## Important Links
- Homepage (TR): ${siteUrl}/tr
- Homepage (EN): ${siteUrl}/en
- Sitemap: ${siteUrl}/sitemap.xml
## Services
${serviceList}
## Contact Information
${contactLines || '- İletişim bilgileri için siteyi ziyaret edin.'}
## Social Media
${socialLines || '- Sosyal medya bilgileri için siteyi ziyaret edin.'}
## Location
- Şehir: Akyaka, Muğla, Türkiye
- Bölge: Ege Bölgesi, Türkiye
- Koordinatlar: 37.0437° N, 28.3249° E
## Language Support
This website is available in Turkish (TR) and English (EN).
## About
${siteName} offers an exclusive beachside experience in one of Turkey's most
pristine coastal towns — Akyaka. Known for its crystal-clear waters,
fresh Ula wind, and untouched nature, Akyaka is a hidden gem on the
Aegean coast. ${siteName} brings luxury and comfort to this natural paradise.
---
Generated: ${new Date().toISOString()}
`;
return new NextResponse(content, {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
},
});
}
+19
View File
@@ -0,0 +1,19 @@
import { MetadataRoute } from 'next';
import { prisma } from '@/lib/db';
export default async function robots(): Promise<MetadataRoute.Robots> {
const settings = await prisma.siteSettings.findFirst();
const siteUrl = settings?.siteUrl || 'https://moybeach.com';
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: ['/admin/', '/api/'],
},
],
sitemap: `${siteUrl}/sitemap.xml`,
host: siteUrl,
};
}
+35
View File
@@ -0,0 +1,35 @@
import { MetadataRoute } from 'next';
import { prisma } from '@/lib/db';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const settings = await prisma.siteSettings.findFirst();
const siteUrl = settings?.siteUrl || 'https://moybeach.com';
const now = new Date();
return [
{
url: `${siteUrl}/tr`,
lastModified: now,
changeFrequency: 'weekly',
priority: 1,
alternates: {
languages: {
tr: `${siteUrl}/tr`,
en: `${siteUrl}/en`,
},
},
},
{
url: `${siteUrl}/en`,
lastModified: now,
changeFrequency: 'weekly',
priority: 1,
alternates: {
languages: {
tr: `${siteUrl}/tr`,
en: `${siteUrl}/en`,
},
},
},
];
}
+12
View File
@@ -50,6 +50,7 @@ model HeroMedia {
model SiteSettings { model SiteSettings {
id String @id @default(cuid()) id String @id @default(cuid())
// Genel
logoUrl String? logoUrl String?
phone String? phone String?
email String? email String?
@@ -60,6 +61,17 @@ model SiteSettings {
facebookUrl String? facebookUrl String?
youtubeUrl String? youtubeUrl String?
whatsappNumber String? whatsappNumber String?
// SEO
siteUrl String?
metaTitleTr String?
metaTitleEn String?
metaDescTr String?
metaDescEn String?
metaKeywordsTr String?
metaKeywordsEn String?
ogImage String?
twitterHandle String?
googleVerify String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }