feat: AI SEO optimization - JSON-LD schemas, per-page metadata, dynamic lesson metadata

This commit is contained in:
AyrisAI
2026-07-25 11:11:44 +03:00
parent d88cf955c7
commit 84acfadf96
5 changed files with 299 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
import type { Metadata } from 'next';
import { getLessonBySlug } from '@/lib/actions/lessonActions';
const BASE_URL = 'https://ayrisai.xyz';
interface Props {
params: Promise<{ locale: string; slug: string }>;
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug, locale } = await params;
const lesson = await getLessonBySlug(slug);
if (!lesson) {
return {
title: 'Lesson Not Found | ayris.tech',
};
}
const url = `${BASE_URL}/${locale}/lessons/${slug}`;
const description =
lesson.summary ||
`Watch the ${lesson.title} tutorial on ayris.tech YouTube. Download source code, read lesson notes, and grab the full project starter pack.`;
return {
title: lesson.title,
description,
keywords: [
...(lesson.tags || []),
'YouTube tutorial', 'source code download', 'ayris.tech', lesson.category,
],
alternates: { canonical: url },
openGraph: {
title: lesson.title,
description,
url,
type: 'video.other',
images: lesson.thumbnailUrl
? [{ url: lesson.thumbnailUrl, width: 1280, height: 720, alt: lesson.title }]
: [],
},
twitter: {
card: 'summary_large_image',
title: lesson.title,
description,
images: lesson.thumbnailUrl ? [lesson.thumbnailUrl] : [],
},
};
}
export default async function LessonDetailLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string; slug: string }>;
}) {
const { slug, locale } = await params;
const lesson = await getLessonBySlug(slug);
const jsonLd = lesson
? {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'VideoObject',
name: lesson.title,
description: lesson.summary || lesson.title,
thumbnailUrl: lesson.thumbnailUrl || '',
uploadDate: lesson.createdAt || new Date().toISOString(),
duration: lesson.duration
? `PT${lesson.duration.replace(':', 'M')}S`
: undefined,
contentUrl: lesson.youtubeUrl || '',
embedUrl: lesson.youtubeId
? `https://www.youtube.com/embed/${lesson.youtubeId}`
: undefined,
author: { '@type': 'Person', name: 'ayris.tech', url: BASE_URL },
publisher: {
'@type': 'Organization',
name: 'ayris.tech',
url: BASE_URL,
logo: { '@type': 'ImageObject', url: `${BASE_URL}/logo.jpeg` },
},
},
{
'@type': 'BreadcrumbList',
itemListElement: [
{ '@type': 'ListItem', position: 1, name: 'Home', item: `${BASE_URL}/${locale}` },
{ '@type': 'ListItem', position: 2, name: 'Lessons', item: `${BASE_URL}/${locale}/lessons` },
{ '@type': 'ListItem', position: 3, name: lesson.title, item: `${BASE_URL}/${locale}/lessons/${slug}` },
],
},
],
}
: null;
return (
<>
{jsonLd && (
<script
type="application/ld+json"
suppressHydrationWarning
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
)}
{children}
</>
);
}