feat: add sitemap.xml, robots.txt, full SEO metadata
This commit is contained in:
+40
-2
@@ -20,9 +20,47 @@ const jetbrainsMono = JetBrains_Mono({
|
|||||||
weight: ["400", "500", "600", "700"],
|
weight: ["400", "500", "600", "700"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const BASE_URL = 'https://ayrisai.xyz';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "DevHub YouTube | Educational Code & Resource Portal",
|
metadataBase: new URL(BASE_URL),
|
||||||
description: "Official resource portal featuring tabbed code snippets, lesson notes, starter packs, and project downloads for my YouTube tutorials.",
|
title: {
|
||||||
|
default: 'ayris.tech YouTube | Code & Resource Portal',
|
||||||
|
template: '%s | ayris.tech YouTube',
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
'Official resource portal for the ayris.tech YouTube channel. Browse source code, lesson notes, cheatsheets, AI prompts, and project downloads for every tutorial.',
|
||||||
|
keywords: [
|
||||||
|
'Next.js', 'React', 'TypeScript', 'Tailwind CSS', 'Python',
|
||||||
|
'YouTube tutorial', 'source code', 'ayris.tech', 'web development',
|
||||||
|
'AI agents', 'Prisma', 'Docker', 'full-stack',
|
||||||
|
],
|
||||||
|
authors: [{ name: 'ayris.tech', url: BASE_URL }],
|
||||||
|
creator: 'ayris.tech',
|
||||||
|
openGraph: {
|
||||||
|
type: 'website',
|
||||||
|
locale: 'en_US',
|
||||||
|
url: BASE_URL,
|
||||||
|
siteName: 'ayris.tech YouTube Portal',
|
||||||
|
title: 'ayris.tech YouTube | Code & Resource Portal',
|
||||||
|
description:
|
||||||
|
'Source code, lesson notes, cheatsheets, and project downloads for every ayris.tech YouTube tutorial.',
|
||||||
|
images: [{ url: `${BASE_URL}/logo.jpeg`, width: 400, height: 400, alt: 'ayris.tech Logo' }],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: 'summary_large_image',
|
||||||
|
title: 'ayris.tech YouTube | Code & Resource Portal',
|
||||||
|
description: 'Source code, cheatsheets, AI prompts and downloads for every ayris.tech tutorial.',
|
||||||
|
images: [`${BASE_URL}/logo.jpeg`],
|
||||||
|
},
|
||||||
|
robots: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
googleBot: { index: true, follow: true },
|
||||||
|
},
|
||||||
|
alternates: {
|
||||||
|
canonical: BASE_URL,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { MetadataRoute } from 'next';
|
||||||
|
|
||||||
|
const BASE_URL = 'https://ayrisai.xyz';
|
||||||
|
const PROTECTED = ['/admin/', '/api/'];
|
||||||
|
|
||||||
|
export default function robots(): MetadataRoute.Robots {
|
||||||
|
return {
|
||||||
|
rules: [
|
||||||
|
// Genel arama botları — tüm public içerik açık
|
||||||
|
{
|
||||||
|
userAgent: '*',
|
||||||
|
allow: '/',
|
||||||
|
disallow: PROTECTED,
|
||||||
|
},
|
||||||
|
// AI arama / citation botları — izin ver
|
||||||
|
{ userAgent: 'OAI-SearchBot', allow: '/', disallow: PROTECTED },
|
||||||
|
{ userAgent: 'ChatGPT-User', allow: '/', disallow: PROTECTED },
|
||||||
|
{ userAgent: 'PerplexityBot', allow: '/', disallow: PROTECTED },
|
||||||
|
{ userAgent: 'Perplexity-User', allow: '/', disallow: PROTECTED },
|
||||||
|
{ userAgent: 'Claude-SearchBot', allow: '/', disallow: PROTECTED },
|
||||||
|
{ userAgent: 'Claude-User', allow: '/', disallow: PROTECTED },
|
||||||
|
// Training crawler'ları engelle
|
||||||
|
{ userAgent: 'GPTBot', disallow: '/' },
|
||||||
|
{ userAgent: 'ClaudeBot', disallow: '/' },
|
||||||
|
{ userAgent: 'Google-Extended', disallow: '/' },
|
||||||
|
{ userAgent: 'CCBot', disallow: '/' },
|
||||||
|
{ userAgent: 'Bytespider', disallow: '/' },
|
||||||
|
{ userAgent: 'Meta-ExternalAgent', disallow: '/' },
|
||||||
|
],
|
||||||
|
sitemap: `${BASE_URL}/sitemap.xml`,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import type { MetadataRoute } from 'next';
|
||||||
|
import { getLessons } from '@/lib/actions/lessonActions';
|
||||||
|
import { getCheatsheets } from '@/lib/actions/cheatsheetActions';
|
||||||
|
import { getPrompts } from '@/lib/actions/promptActions';
|
||||||
|
|
||||||
|
const BASE_URL = 'https://ayrisai.xyz';
|
||||||
|
const LOCALES = ['en'];
|
||||||
|
|
||||||
|
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
// Static pages
|
||||||
|
const staticRoutes = [
|
||||||
|
{ path: '', priority: 1.0, changeFrequency: 'daily' as const },
|
||||||
|
{ path: '/lessons', priority: 0.9, changeFrequency: 'daily' as const },
|
||||||
|
{ path: '/cheatsheets', priority: 0.8, changeFrequency: 'weekly' as const },
|
||||||
|
{ path: '/prompts', priority: 0.8, changeFrequency: 'weekly' as const },
|
||||||
|
{ path: '/contact', priority: 0.5, changeFrequency: 'monthly' as const },
|
||||||
|
];
|
||||||
|
|
||||||
|
const staticEntries: MetadataRoute.Sitemap = LOCALES.flatMap((locale) =>
|
||||||
|
staticRoutes.map(({ path, priority, changeFrequency }) => ({
|
||||||
|
url: `${BASE_URL}/${locale}${path}`,
|
||||||
|
lastModified: now,
|
||||||
|
changeFrequency,
|
||||||
|
priority,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dynamic: Lessons
|
||||||
|
let lessonEntries: MetadataRoute.Sitemap = [];
|
||||||
|
try {
|
||||||
|
const lessons = await getLessons();
|
||||||
|
lessonEntries = LOCALES.flatMap((locale) =>
|
||||||
|
lessons.map((lesson: any) => ({
|
||||||
|
url: `${BASE_URL}/${locale}/lessons/${lesson.slug}`,
|
||||||
|
lastModified: lesson.updatedAt || now,
|
||||||
|
changeFrequency: 'weekly' as const,
|
||||||
|
priority: 0.85,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
// Dynamic: Cheatsheets
|
||||||
|
let cheatEntries: MetadataRoute.Sitemap = [];
|
||||||
|
try {
|
||||||
|
const sheets = await getCheatsheets();
|
||||||
|
cheatEntries = LOCALES.flatMap((locale) =>
|
||||||
|
sheets.map((sheet: any) => ({
|
||||||
|
url: `${BASE_URL}/${locale}/cheatsheets/${sheet.slug}`,
|
||||||
|
lastModified: sheet.updatedAt || now,
|
||||||
|
changeFrequency: 'monthly' as const,
|
||||||
|
priority: 0.7,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
// Dynamic: Prompts
|
||||||
|
let promptEntries: MetadataRoute.Sitemap = [];
|
||||||
|
try {
|
||||||
|
const prompts = await getPrompts();
|
||||||
|
promptEntries = LOCALES.flatMap((locale) =>
|
||||||
|
prompts.map((prompt: any) => ({
|
||||||
|
url: `${BASE_URL}/${locale}/prompts/${prompt.slug}`,
|
||||||
|
lastModified: prompt.updatedAt || now,
|
||||||
|
changeFrequency: 'monthly' as const,
|
||||||
|
priority: 0.7,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
return [...staticEntries, ...lessonEntries, ...cheatEntries, ...promptEntries];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user