feat : add sitemao and some SEO
CI / Verify (push) Failing after 47s
CI / Build immutable images and deploy (push) Has been skipped

This commit is contained in:
2026-09-13 15:53:08 +07:00 Unverified
parent f188251082
commit 7c8ecd0915
5 changed files with 33 additions and 1 deletions
+3
View File
@@ -1,3 +1,6 @@
# Public site origin used for SEO metadata, sitemap, and robots.txt.
BASE_URL=https://guide.sudloh.com
# Local-only demo mode serves sanitized fixtures and bypasses external services. # Local-only demo mode serves sanitized fixtures and bypasses external services.
# Keep this false in every deployed environment. # Keep this false in every deployed environment.
BUZZ_DEMO_MODE=false BUZZ_DEMO_MODE=false
+3 -1
View File
@@ -4,6 +4,7 @@ import { Anuphan, Geist_Mono } from "next/font/google";
import { DeploymentUpdateNotifier } from "@/components/deployment-update-notifier"; import { DeploymentUpdateNotifier } from "@/components/deployment-update-notifier";
import { PageProgress } from "@/components/page-progress"; import { PageProgress } from "@/components/page-progress";
import { Toaster } from "@/components/ui/sonner"; import { Toaster } from "@/components/ui/sonner";
import { siteUrl } from "@/lib/site-url";
import "./globals.css"; import "./globals.css";
@@ -19,12 +20,13 @@ const geistMono = Geist_Mono({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL("https://guide.sudloh.com"), metadataBase: new URL(siteUrl),
title: { title: {
default: "Buzz Guide", default: "Buzz Guide",
template: "%s", template: "%s",
}, },
description: "ไกด์ตัวละครจากช่องเกนชินไม่ใช่เกมมือถือ", description: "ไกด์ตัวละครจากช่องเกนชินไม่ใช่เกมมือถือ",
keywords: ["เกนชิน", "เกนชินไม่ใช่เกมมือถือ", "Buzz", "ไกด์", "สุดหล่อ"],
}; };
export default function RootLayout({ children }: LayoutProps<"/">) { export default function RootLayout({ children }: LayoutProps<"/">) {
+3
View File
@@ -1,5 +1,7 @@
import type { MetadataRoute } from "next"; import type { MetadataRoute } from "next";
import { siteUrl } from "@/lib/site-url";
export default function robots(): MetadataRoute.Robots { export default function robots(): MetadataRoute.Robots {
return { return {
rules: { rules: {
@@ -7,5 +9,6 @@ export default function robots(): MetadataRoute.Robots {
allow: "/", allow: "/",
disallow: ["/admin", "/api", "/register"], disallow: ["/admin", "/api", "/register"],
}, },
sitemap: `${siteUrl}/sitemap.xml`,
}; };
} }
+17
View File
@@ -0,0 +1,17 @@
import type { MetadataRoute } from "next";
import { connection } from "next/server";
import { getCachedPublicGuideCards } from "@/lib/cache/public";
import { siteUrl } from "@/lib/site-url";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
await connection();
const guides = await getCachedPublicGuideCards();
return [
{ url: siteUrl },
...guides.map((guide) => ({
url: `${siteUrl}/${encodeURIComponent(guide.slug)}`,
})),
];
}
+7
View File
@@ -0,0 +1,7 @@
const configuredBaseUrl = process.env.BASE_URL?.trim();
if (!configuredBaseUrl) {
throw new Error("BASE_URL is required for site metadata and sitemaps.");
}
export const siteUrl = new URL(configuredBaseUrl).origin;