diff --git a/app/[locale]/projects/[slug]/page.tsx b/app/[locale]/projects/[slug]/page.tsx new file mode 100644 index 0000000..4c525dd --- /dev/null +++ b/app/[locale]/projects/[slug]/page.tsx @@ -0,0 +1,103 @@ +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { ProjectCaseStudyContent } from "@/components/project-case-study-content"; +import { ProjectCaseStudyJsonLd } from "@/components/json-ld"; +import { + getProjectCaseStudy, + PROJECT_CASE_STUDY_SLUGS, + type ProjectCaseStudyLocale, +} from "@/data/project-case-studies"; +import { + createAlternates, + getAbsoluteUrl, + getLocalizedUrl, +} from "@/lib/seo"; + +type ProjectCaseStudyPageProps = { + params: Promise<{ locale: string; slug: string }>; +}; + +export function generateStaticParams() { + return (["tr", "en"] as ProjectCaseStudyLocale[]).flatMap((locale) => + PROJECT_CASE_STUDY_SLUGS.map((slug) => ({ locale, slug })), + ); +} + +export const dynamicParams = false; + +export async function generateMetadata({ + params, +}: ProjectCaseStudyPageProps): Promise { + const { locale, slug } = await params; + const siteLocale: ProjectCaseStudyLocale = locale === "en" ? "en" : "tr"; + const project = getProjectCaseStudy(slug, siteLocale); + + if (!project) { + return { + title: siteLocale === "en" ? "Project not found" : "Proje bulunamadı", + }; + } + + const path = `/projects/${project.slug}`; + const url = getLocalizedUrl(siteLocale, path); + const socialImageUrl = getAbsoluteUrl(project.image); + + return { + title: project.title, + description: project.summary, + alternates: createAlternates(siteLocale, { tr: path, en: path }), + openGraph: { + title: project.title, + description: project.summary, + url, + siteName: "Poyraz Avsever", + type: "website", + locale: siteLocale === "en" ? "en_US" : "tr_TR", + alternateLocale: siteLocale === "en" ? ["tr_TR"] : ["en_US"], + images: [ + { + url: socialImageUrl, + width: 1080, + height: 1080, + alt: project.screenshotAlt, + }, + ], + }, + twitter: { + card: "summary_large_image", + title: project.title, + description: project.summary, + creator: "@poyrazavsever", + images: [{ url: socialImageUrl, alt: project.screenshotAlt }], + }, + }; +} + +export default async function ProjectCaseStudyPage({ + params, +}: ProjectCaseStudyPageProps) { + const { locale, slug } = await params; + const siteLocale: ProjectCaseStudyLocale = locale === "en" ? "en" : "tr"; + const project = getProjectCaseStudy(slug, siteLocale); + + if (!project) notFound(); + + const url = getLocalizedUrl(siteLocale, `/projects/${project.slug}`); + + return ( + <> + result.description)} + /> + + + ); +} diff --git a/app/sitemap.ts b/app/sitemap.ts index c50b4c7..60f8090 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -2,6 +2,7 @@ import type { MetadataRoute } from "next"; import { listAnimationSources } from "@/data/animation-sources"; import { isNewsletterCategory } from "@/data/blog"; import { listBlogDetails } from "@/data/blog-detail"; +import { listProjectCaseStudies } from "@/data/project-case-studies"; import { getAbsoluteUrl, getLocalizedUrl, @@ -127,5 +128,26 @@ export default async function sitemap(): Promise { }, ); - return [...staticRoutes, ...blogRoutes, ...animationSourceRoutes]; + const projectCaseStudyRoutes: MetadataRoute.Sitemap = LOCALES.flatMap( + (locale) => + listProjectCaseStudies(locale).map((project) => { + const path = `/projects/${project.slug}`; + const paths = { tr: path, en: path }; + + return { + url: getLocalizedUrl(locale, path), + changeFrequency: "monthly" as const, + priority: 0.8, + alternates: { languages: getLanguageLinks(paths) }, + images: [getAbsoluteUrl(project.image)], + }; + }), + ); + + return [ + ...staticRoutes, + ...projectCaseStudyRoutes, + ...blogRoutes, + ...animationSourceRoutes, + ]; } diff --git a/components/json-ld.tsx b/components/json-ld.tsx index f56f73d..a3757f0 100644 --- a/components/json-ld.tsx +++ b/components/json-ld.tsx @@ -144,6 +144,57 @@ export function ProjectsJsonLd({ ); } +export function ProjectCaseStudyJsonLd({ + name, + description, + url, + liveUrl, + image, + locale, + applicationCategory, + technologies, + features, +}: { + name: string; + description: string; + url: string; + liveUrl: string; + image: string; + locale: "tr" | "en"; + applicationCategory: string; + technologies: string[]; + features: string[]; +}) { + const data = { + "@context": "https://schema.org", + "@type": "SoftwareApplication", + "@id": `${url}#software-application`, + name, + description, + url, + sameAs: liveUrl, + image: image.startsWith("http") ? image : `${SITE_URL}${image}`, + inLanguage: locale === "tr" ? "tr-TR" : "en-US", + applicationCategory, + operatingSystem: "Web", + keywords: technologies, + featureList: features, + author: { + "@type": "Person", + "@id": `${SITE_URL}/#person`, + name: "Poyraz Avsever", + url: SITE_URL, + }, + }; + + return ( +