feat: add full SEO (generateMetadata, OG, Twitter Card, JSON-LD, sitemap, robots, llms.txt)
This commit is contained in:
+126
-5
@@ -1,6 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Playfair_Display, Lato } from "next/font/google";
|
||||
import "../globals.css";
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
const playfair = Playfair_Display({
|
||||
variable: "--font-playfair",
|
||||
@@ -13,15 +14,91 @@ const lato = Lato({
|
||||
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() {
|
||||
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({
|
||||
children,
|
||||
params,
|
||||
@@ -30,9 +107,53 @@ export default async function RootLayout({
|
||||
params: Promise<{ lang: string }>;
|
||||
}>) {
|
||||
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 (
|
||||
<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`}>
|
||||
{children}
|
||||
</body>
|
||||
|
||||
+136
-10
@@ -1,7 +1,7 @@
|
||||
import { prisma } from '@/lib/db';
|
||||
import { getSession } from '@/lib/auth';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { Save, Upload } from 'lucide-react';
|
||||
import { redirect } from 'next/navigation'
|
||||
import { Save } from 'lucide-react';
|
||||
import { updateSiteSettings } from '../actions';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { uploadImage } from '@/lib/cloudinary';
|
||||
@@ -32,6 +32,16 @@ export default async function SettingsAdminPage() {
|
||||
facebookUrl: formData.get('facebookUrl') as string,
|
||||
youtubeUrl: formData.get('youtubeUrl') 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;
|
||||
@@ -39,19 +49,22 @@ export default async function SettingsAdminPage() {
|
||||
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);
|
||||
revalidatePath('/', 'layout');
|
||||
redirect('/admin/settings');
|
||||
}} className="space-y-8">
|
||||
}} className="space-y-10">
|
||||
|
||||
{/* Logo 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>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
Yeni Logo Yükle
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Yeni Logo Yükle</label>
|
||||
<input
|
||||
type="file"
|
||||
name="logo"
|
||||
@@ -73,7 +86,7 @@ export default async function SettingsAdminPage() {
|
||||
|
||||
{/* Contact 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>
|
||||
<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>
|
||||
<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 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>
|
||||
@@ -104,7 +117,7 @@ export default async function SettingsAdminPage() {
|
||||
|
||||
{/* Social Media */}
|
||||
<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>
|
||||
<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>
|
||||
</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">
|
||||
<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" />
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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`,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -50,6 +50,7 @@ model HeroMedia {
|
||||
|
||||
model SiteSettings {
|
||||
id String @id @default(cuid())
|
||||
// Genel
|
||||
logoUrl String?
|
||||
phone String?
|
||||
email String?
|
||||
@@ -60,6 +61,17 @@ model SiteSettings {
|
||||
facebookUrl String?
|
||||
youtubeUrl 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())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user