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`}
|
||||
>
|
||||
<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}>
|
||||
<Navbar locale={locale} />
|
||||
<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