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>
|
||||
|
||||
Reference in New Issue
Block a user