seo: sitemap, robots, og tags
This commit is contained in:
@@ -7,10 +7,40 @@ import "@fontsource/inter/700.css";
|
|||||||
import { Providers } from "./providers";
|
import { Providers } from "./providers";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
|
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://codetutor.example.com";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "CodeTutor - Coding Interview Preparation",
|
title: {
|
||||||
|
default: "CodeTutor - Coding Interview Preparation",
|
||||||
|
template: "%s | CodeTutor",
|
||||||
|
},
|
||||||
description:
|
description:
|
||||||
"Master coding interviews with curated questions, detailed explanations, and optimal solutions.",
|
"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({
|
export default function RootLayout({
|
||||||
|
|||||||
@@ -1,13 +1,39 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
import { getPattern, getQuestions } from "@/lib/api";
|
import { getPattern, getQuestions } from "@/lib/api";
|
||||||
import { QuestionCard } from "@/components/questions/question-card";
|
import { QuestionCard } from "@/components/questions/question-card";
|
||||||
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
export default async function PatternDetailPage({
|
interface PageProps {
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
params: Promise<{ slug: string }>;
|
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;
|
const { slug } = await params;
|
||||||
|
|
||||||
let pattern;
|
let pattern;
|
||||||
|
|||||||
@@ -1,13 +1,40 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
import { getQuestion } from "@/lib/api";
|
import { getQuestion } from "@/lib/api";
|
||||||
import { QuestionDetail } from "@/components/questions/question-detail";
|
import { QuestionDetail } from "@/components/questions/question-detail";
|
||||||
import { ProblemWorkspace } from "@/components/editor";
|
import { ProblemWorkspace } from "@/components/editor";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
export default async function QuestionDetailPage({
|
interface PageProps {
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
params: Promise<{ slug: string }>;
|
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;
|
const { slug } = await params;
|
||||||
|
|
||||||
let question;
|
let question;
|
||||||
|
|||||||
14
frontend/src/app/robots.ts
Normal file
14
frontend/src/app/robots.ts
Normal 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`,
|
||||||
|
};
|
||||||
|
}
|
||||||
65
frontend/src/app/sitemap.ts
Normal file
65
frontend/src/app/sitemap.ts
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user