feat(seo): implement SEO audit remediations

- Add dynamic sitemap.ts, robots.ts, and llms.txt route
- Add metadataBase and JSON-LD Organization schema to layout
- Set canonical alternates for all routes
- Migrate raw img tags to next/image for LCP optimization
- Add SEO dictionary strings to tr.json and en.json
This commit is contained in:
mstfyldz
2026-06-04 12:29:19 +03:00
parent 4996ec38e6
commit e1d8bae5a4
17 changed files with 233 additions and 28 deletions
+5 -3
View File
@@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import Image from "next/image";
import { motion, type Variants } from "framer-motion";
import Link from "next/link";
import { Utensils, Umbrella, Home, Wind, Hotel, Star, MapPin, ArrowRight } from "lucide-react";
@@ -289,11 +290,12 @@ export default function HomeClient({ lang, dict }: { lang: string; dict: any })
className={`flex flex-col ${i % 2 === 1 ? "md:flex-row-reverse" : "md:flex-row"} gap-10 items-center`}
>
{/* Image */}
<div className="w-full md:w-[55%] group relative overflow-hidden shadow-[var(--shadow-xl)]">
<img
<div className="w-full md:w-[55%] h-[420px] group relative overflow-hidden shadow-[var(--shadow-xl)]">
<Image
src={venue.img}
alt={venue.title}
className="w-full h-[420px] object-cover transition-transform duration-700 group-hover:scale-105"
fill
className="object-cover transition-transform duration-700 group-hover:scale-105"
/>
<div className={`absolute inset-0 bg-gradient-to-t ${venue.color} to-transparent opacity-50`} />
<Badge variant="light" className="absolute bottom-6 left-6 shadow-md">
+8 -4
View File
@@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import Image from "next/image";
import { motion, AnimatePresence } from "framer-motion";
import { X, ZoomIn } from "lucide-react";
import { Badge } from "@/app/components/ui/badge";
@@ -75,10 +76,11 @@ export default function GaleriClient({ lang, dict }: { lang: string; dict: any }
className="group relative aspect-square cursor-pointer overflow-hidden"
onClick={() => setLightbox(img)}
>
<img
<Image
src={img.src}
alt={img.title}
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
fill
className="object-cover transition-transform duration-700 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-[var(--color-moy-dark)]/0 group-hover:bg-[var(--color-moy-dark)]/50 transition-all duration-300 flex flex-col items-center justify-center gap-3">
<ZoomIn
@@ -125,10 +127,12 @@ export default function GaleriClient({ lang, dict }: { lang: string; dict: any }
className="max-w-4xl w-full"
onClick={(e) => e.stopPropagation()}
>
<img
<Image
src={lightbox.src}
alt={lightbox.title}
className="w-full max-h-[80vh] object-contain"
width={1200}
height={800}
className="w-full h-auto max-h-[80vh] object-contain"
/>
<p className="text-center text-white font-serif text-xl mt-4">{lightbox.title}</p>
</motion.div>
+11 -3
View File
@@ -1,9 +1,17 @@
import { getDictionary, Locale } from "../../dictionaries";
import GaleriClient from "./GaleriClient";
export const metadata = {
title: "Galeri | Moy Group",
};
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) {
const { lang } = await params;
const dict = await getDictionary(lang as Locale);
return {
title: dict.seo.gallery.title,
description: dict.seo.gallery.description,
alternates: {
canonical: `/${lang}/galeri`,
},
};
}
export default async function GaleriPage({
params,
+12
View File
@@ -32,6 +32,18 @@ const LOCATIONS = [
},
];
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) {
const { lang } = await params;
const dict = await getDictionary(lang as Locale);
return {
title: dict.seo.contact.title,
description: dict.seo.contact.description,
alternates: {
canonical: `/${lang}/iletisim`,
},
};
}
export default async function IletisimPage({
params,
}: {
+11 -3
View File
@@ -1,8 +1,16 @@
import { getDictionary, Locale } from "../../dictionaries";
export const metadata = {
title: "Kurumsal | Moy Group",
};
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) {
const { lang } = await params;
const dict = await getDictionary(lang as Locale);
return {
title: dict.seo.corporate.title,
description: dict.seo.corporate.description,
alternates: {
canonical: `/${lang}/kurumsal`,
},
};
}
export default async function KurumsalPage({
params,
+31 -4
View File
@@ -17,10 +17,18 @@ const montserrat = Montserrat({
weight: ["300", "400", "500", "600", "700"],
});
export const metadata: Metadata = {
title: "Moy Group | Ege'nin Ruhunu Hisset",
description: "Akyaka, Akçapınar ve Datça'da restoran, beach, bungalov ve kitesurf hizmetleriyle doğa ile iç içe premium tatil deneyimi.",
};
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise<Metadata> {
const { lang } = await params;
const dict = await getDictionary(lang as Locale);
return {
metadataBase: new URL("https://moygroup.com.tr"),
title: {
template: "%s | Moy Group",
default: dict.seo.home.title,
},
description: dict.seo.home.description,
};
}
export default async function RootLayout({
children,
@@ -33,11 +41,30 @@ export default async function RootLayout({
const locale = lang as Locale;
const dict = await getDictionary(locale);
const jsonLd = {
"@context": "https://schema.org",
"@type": "Organization",
"name": "Moy Group",
"url": "https://moygroup.com.tr",
"logo": "https://moygroup.com.tr/logo.png",
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+90-534-465-62-35",
"contactType": "customer service"
}
};
return (
<html
lang={locale}
className={`${cormorant.variable} ${montserrat.variable} h-full antialiased`}
>
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
</head>
<body className="min-h-full flex flex-col">
<Navbar lang={locale} dict={dict.navigation} />
<main className="flex-1">{children}</main>
+11 -3
View File
@@ -2,9 +2,17 @@ import { SectionHeader } from "@/app/components/ui/section-header";
import { Badge } from "@/app/components/ui/badge";
import { getDictionary, Locale } from "../../dictionaries";
export const metadata = {
title: "Dijital Menü | Moy Group",
};
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) {
const { lang } = await params;
const dict = await getDictionary(lang as Locale);
return {
title: dict.seo.menu.title,
description: dict.seo.menu.description,
alternates: {
canonical: `/${lang}/menu`,
},
};
}
export default async function MenuPage({
params,
+12 -1
View File
@@ -1,6 +1,18 @@
import { getDictionary, Locale } from "../dictionaries";
import HomeClient from "./HomeClient";
export async function generateMetadata({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return {
title: dict.seo.home.title,
description: dict.seo.home.description,
alternates: {
canonical: `/${lang}`,
},
};
}
export default async function Page({
params,
}: {
@@ -8,6 +20,5 @@ export default async function Page({
}) {
const { lang } = await params;
const dict = await getDictionary(lang);
return <HomeClient lang={lang} dict={dict.home} />;
}
+11 -3
View File
@@ -1,9 +1,17 @@
import { getDictionary, Locale } from "../../dictionaries";
import SSSClient from "./SSSClient";
export const metadata = {
title: "SSS | Moy Group",
};
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) {
const { lang } = await params;
const dict = await getDictionary(lang as Locale);
return {
title: dict.seo.faq.title,
description: dict.seo.faq.description,
alternates: {
canonical: `/${lang}/sss`,
},
};
}
export default async function SSSPage({
params,