seo: sitemap, robots, og tags

This commit is contained in:
2025-08-06 22:38:22 +01:00
parent 1e0cca1ef3
commit 8544efb5bf
5 changed files with 172 additions and 10 deletions

View File

@@ -7,10 +7,40 @@ import "@fontsource/inter/700.css";
import { Providers } from "./providers";
import "./globals.css";
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://codetutor.example.com";
export const metadata: Metadata = {
title: {
default: "CodeTutor - Coding Interview Preparation",
template: "%s | CodeTutor",
},
description:
"Master coding interviews with curated questions, detailed explanations, and optimal solutions. Practice 400+ problems with interactive code editor.",
keywords: [
"coding interview",
"leetcode",
"algorithm",
"data structures",
"programming practice",
"software engineering",
],
authors: [{ name: "Kai Chappell" }],
openGraph: {
type: "website",
locale: "en_US",
url: siteUrl,
siteName: "CodeTutor",
title: "CodeTutor - Coding Interview Preparation",
description:
"Master coding interviews with curated questions, detailed explanations, and optimal solutions.",
},
twitter: {
card: "summary_large_image",
title: "CodeTutor - Coding Interview Preparation",
description:
"Master coding interviews with curated questions, detailed explanations, and optimal solutions.",
},
metadataBase: new URL(siteUrl),
};
export default function RootLayout({

View File

@@ -1,13 +1,39 @@
import type { Metadata } from "next";
import { getPattern, getQuestions } from "@/lib/api";
import { QuestionCard } from "@/components/questions/question-card";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { notFound } from "next/navigation";
export default async function PatternDetailPage({
params,
}: {
interface PageProps {
params: Promise<{ slug: string }>;
}) {
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { slug } = await params;
try {
const pattern = await getPattern(slug);
const description =
pattern.description ||
`Learn the ${pattern.name} pattern with ${pattern.question_count} practice problems.`;
return {
title: `${pattern.name} Pattern`,
description,
openGraph: {
title: `${pattern.name} Pattern | CodeTutor`,
description,
type: "article",
},
};
} catch {
return {
title: "Pattern Not Found",
};
}
}
export default async function PatternDetailPage({ params }: PageProps) {
const { slug } = await params;
let pattern;

View File

@@ -1,13 +1,40 @@
import type { Metadata } from "next";
import { getQuestion } from "@/lib/api";
import { QuestionDetail } from "@/components/questions/question-detail";
import { ProblemWorkspace } from "@/components/editor";
import { notFound } from "next/navigation";
export default async function QuestionDetailPage({
params,
}: {
interface PageProps {
params: Promise<{ slug: string }>;
}) {
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { slug } = await params;
try {
const question = await getQuestion(slug);
const description = question.description
.replace(/[#*`]/g, "")
.substring(0, 160)
.trim();
return {
title: question.title,
description,
openGraph: {
title: `${question.title} | CodeTutor`,
description,
type: "article",
},
};
} catch {
return {
title: "Question Not Found",
};
}
}
export default async function QuestionDetailPage({ params }: PageProps) {
const { slug } = await params;
let question;

View File

@@ -0,0 +1,14 @@
import { MetadataRoute } from "next";
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://codetutor.example.com";
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: "*",
allow: "/",
disallow: ["/api/"],
},
sitemap: `${BASE_URL}/sitemap.xml`,
};
}

View File

@@ -0,0 +1,65 @@
import { MetadataRoute } from "next";
import { getQuestions, getPatterns } from "@/lib/api";
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://codetutor.example.com";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const entries: MetadataRoute.Sitemap = [
{
url: BASE_URL,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 1,
},
{
url: `${BASE_URL}/questions`,
lastModified: new Date(),
changeFrequency: "daily",
priority: 0.9,
},
{
url: `${BASE_URL}/categories`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.8,
},
{
url: `${BASE_URL}/patterns`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.8,
},
];
try {
// Fetch all questions for individual pages
const questionsResponse = await getQuestions({ limit: 1000 });
for (const question of questionsResponse.items) {
entries.push({
url: `${BASE_URL}/questions/${question.slug}`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.7,
});
}
} catch {
// Continue without question pages if API fails
}
try {
// Fetch all patterns for individual pages
const patternsResponse = await getPatterns();
for (const pattern of patternsResponse.items) {
entries.push({
url: `${BASE_URL}/patterns/${pattern.slug}`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.6,
});
}
} catch {
// Continue without pattern pages if API fails
}
return entries;
}