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 { 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>