feat: AI SEO optimization - JSON-LD schemas, per-page metadata, dynamic lesson metadata
This commit is contained in:
@@ -0,0 +1,50 @@
|
|||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
const BASE_URL = 'https://ayrisai.xyz';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Developer Cheatsheets & Quick Reference | ayris.tech',
|
||||||
|
description:
|
||||||
|
'Free developer cheatsheets for Git, Next.js App Router, Docker, TypeScript, Python, SQL, and more. Quick-copy command references used in ayris.tech YouTube tutorials.',
|
||||||
|
keywords: [
|
||||||
|
'developer cheatsheet', 'Git commands cheatsheet', 'Next.js cheatsheet',
|
||||||
|
'Docker commands', 'TypeScript quick reference', 'SQL cheatsheet',
|
||||||
|
'programming reference card', 'ayris.tech',
|
||||||
|
],
|
||||||
|
alternates: {
|
||||||
|
canonical: `${BASE_URL}/en/cheatsheets`,
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title: 'Developer Cheatsheets & Quick Reference | ayris.tech',
|
||||||
|
description: 'Free copy-paste developer cheatsheets for Git, Next.js, Docker, TypeScript, and more.',
|
||||||
|
url: `${BASE_URL}/en/cheatsheets`,
|
||||||
|
type: 'website',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CheatsheetsLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<script
|
||||||
|
type="application/ld+json"
|
||||||
|
suppressHydrationWarning
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify({
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'CollectionPage',
|
||||||
|
name: 'Developer Cheatsheets & Quick Reference — ayris.tech',
|
||||||
|
description:
|
||||||
|
'Free developer cheatsheets for Git, Next.js, Docker, TypeScript, Python, SQL, and more.',
|
||||||
|
url: `${BASE_URL}/en/cheatsheets`,
|
||||||
|
publisher: {
|
||||||
|
'@type': 'Person',
|
||||||
|
name: 'ayris.tech',
|
||||||
|
url: BASE_URL,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -89,6 +89,43 @@ export default async function RootLayout({
|
|||||||
className={`${jakartaSans.variable} ${jetbrainsMono.variable} dark h-full antialiased`}
|
className={`${jakartaSans.variable} ${jetbrainsMono.variable} dark h-full antialiased`}
|
||||||
>
|
>
|
||||||
<body className="min-h-full flex flex-col bg-slate-950 text-slate-100 font-sans selection:bg-red-500 selection:text-white tracking-normal leading-relaxed" suppressHydrationWarning>
|
<body className="min-h-full flex flex-col bg-slate-950 text-slate-100 font-sans selection:bg-red-500 selection:text-white tracking-normal leading-relaxed" suppressHydrationWarning>
|
||||||
|
{/* Organization + WebSite JSON-LD — global entity recognition */}
|
||||||
|
<script
|
||||||
|
type="application/ld+json"
|
||||||
|
suppressHydrationWarning
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify([
|
||||||
|
{
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: 'ayris.tech',
|
||||||
|
url: BASE_URL,
|
||||||
|
logo: `${BASE_URL}/logo.jpeg`,
|
||||||
|
sameAs: [
|
||||||
|
'https://www.youtube.com/@ayristech',
|
||||||
|
'https://github.com/ayrisdev',
|
||||||
|
'https://git.ayris.tech/ayrisdev',
|
||||||
|
],
|
||||||
|
description:
|
||||||
|
'ayris.tech is a YouTube channel and resource portal publishing tutorials on Next.js, React, TypeScript, Python, AI agents, Docker, and modern full-stack development.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'WebSite',
|
||||||
|
name: 'ayris.tech YouTube Portal',
|
||||||
|
url: BASE_URL,
|
||||||
|
description:
|
||||||
|
'Source code, lesson notes, cheatsheets, AI prompts, and project downloads for every ayris.tech YouTube tutorial.',
|
||||||
|
publisher: { '@type': 'Organization', name: 'ayris.tech', url: BASE_URL },
|
||||||
|
potentialAction: {
|
||||||
|
'@type': 'SearchAction',
|
||||||
|
target: { '@type': 'EntryPoint', urlTemplate: `${BASE_URL}/en/lessons?q={search_term_string}` },
|
||||||
|
'query-input': 'required name=search_term_string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<NextIntlClientProvider messages={messages}>
|
<NextIntlClientProvider messages={messages}>
|
||||||
<Navbar locale={locale} />
|
<Navbar locale={locale} />
|
||||||
<main className="flex-1 flex flex-col">
|
<main className="flex-1 flex flex-col">
|
||||||
|
|||||||
@@ -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}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
const BASE_URL = 'https://ayrisai.xyz';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'YouTube Lessons & Source Code | ayris.tech',
|
||||||
|
description:
|
||||||
|
'Browse all ayris.tech YouTube tutorial source code, lesson notes, starter packages, and project downloads. Covering Next.js, React, Python, TypeScript, Tailwind CSS, AI, and more.',
|
||||||
|
keywords: [
|
||||||
|
'Next.js tutorial source code', 'React tutorial download',
|
||||||
|
'YouTube code resources', 'TypeScript tutorial',
|
||||||
|
'Tailwind CSS examples', 'full-stack web development',
|
||||||
|
'ayris.tech YouTube', 'Python AI tutorial',
|
||||||
|
],
|
||||||
|
alternates: {
|
||||||
|
canonical: `${BASE_URL}/en/lessons`,
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title: 'YouTube Lessons & Source Code | ayris.tech',
|
||||||
|
description: 'Download source code and browse lesson notes for every ayris.tech YouTube tutorial.',
|
||||||
|
url: `${BASE_URL}/en/lessons`,
|
||||||
|
type: 'website',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function LessonsLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* WebSite + ItemList Schema for lesson listing */}
|
||||||
|
<script
|
||||||
|
type="application/ld+json"
|
||||||
|
suppressHydrationWarning
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify({
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'CollectionPage',
|
||||||
|
name: 'YouTube Lessons & Source Code — ayris.tech',
|
||||||
|
description:
|
||||||
|
'Source code, lesson notes, and project downloads for every ayris.tech YouTube tutorial.',
|
||||||
|
url: `${BASE_URL}/en/lessons`,
|
||||||
|
publisher: {
|
||||||
|
'@type': 'Person',
|
||||||
|
name: 'ayris.tech',
|
||||||
|
url: BASE_URL,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
const BASE_URL = 'https://ayrisai.xyz';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'AI System Prompts & LLM Templates | ayris.tech',
|
||||||
|
description:
|
||||||
|
'Curated AI system prompts and LLM agent templates for ChatGPT, Claude, and Gemini. Covering Next.js architecture, UI/UX design, Python automation, and full-stack development roles.',
|
||||||
|
keywords: [
|
||||||
|
'AI system prompts', 'ChatGPT system prompt', 'Claude system prompt',
|
||||||
|
'LLM agent template', 'AI coding assistant prompt',
|
||||||
|
'Next.js AI prompt', 'Python AI agent prompt', 'Gemini prompt engineering',
|
||||||
|
],
|
||||||
|
alternates: {
|
||||||
|
canonical: `${BASE_URL}/en/prompts`,
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title: 'AI System Prompts & LLM Templates | ayris.tech',
|
||||||
|
description: 'Battle-tested AI system prompts and LLM templates for ChatGPT, Claude, and Gemini.',
|
||||||
|
url: `${BASE_URL}/en/prompts`,
|
||||||
|
type: 'website',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function PromptsLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<script
|
||||||
|
type="application/ld+json"
|
||||||
|
suppressHydrationWarning
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify({
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'CollectionPage',
|
||||||
|
name: 'AI System Prompts & LLM Templates — ayris.tech',
|
||||||
|
description:
|
||||||
|
'Curated AI system prompts for ChatGPT, Claude, and Gemini. For Next.js, Python, UI/UX, and full-stack development.',
|
||||||
|
url: `${BASE_URL}/en/prompts`,
|
||||||
|
publisher: {
|
||||||
|
'@type': 'Person',
|
||||||
|
name: 'ayris.tech',
|
||||||
|
url: BASE_URL,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user