feat: SEO overhaul — expanded sitemap, JSON-LD, canonical URLs, search metadata

This commit is contained in:
2026-04-27 16:05:31 +08:00
parent 9a6db98d42
commit 713b309f74
5 changed files with 96 additions and 19 deletions
+15 -1
View File
@@ -17,6 +17,7 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str
return { return {
title: `${device.name} — DeckyVault`, title: `${device.name} — DeckyVault`,
description: `Benchmark data and performance stats for ${device.name} on DeckyVault`, description: `Benchmark data and performance stats for ${device.name} on DeckyVault`,
alternates: { canonical: `https://deckyvault.xyz/devices/${slug}` },
} }
} }
@@ -37,5 +38,18 @@ export default async function DevicePage({ params }: { params: Promise<{ slug: s
notFound() notFound()
} }
return <DeviceDetailClient device={device} /> const jsonLd = {
"@context": "https://schema.org",
"@type": "Product",
name: device.name,
category: device.deviceType,
url: `https://deckyvault.xyz/devices/${device.slug}`,
}
return (
<>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
<DeviceDetailClient device={device} />
</>
)
} }
+23 -2
View File
@@ -1,10 +1,31 @@
import { db } from "@/lib/db/index"
import { hardware } from "@/lib/db/schema"
import { DevicesPageClient } from "./page-client" import { DevicesPageClient } from "./page-client"
export const metadata = { export const metadata = {
title: "Devices — DeckyVault", title: "Devices — DeckyVault",
description: "Browse handheld and console devices with benchmark data on DeckyVault", description: "Browse handheld and console devices with benchmark data on DeckyVault",
alternates: { canonical: "https://deckyvault.xyz/devices" },
} }
export default function DevicesPage() { export default async function DevicesPage() {
return <DevicesPageClient /> const devices = await db.select({ slug: hardware.slug, name: hardware.name }).from(hardware)
const jsonLd = {
"@context": "https://schema.org",
"@type": "ItemList",
itemListElement: devices.map((d, i) => ({
"@type": "ListItem",
position: i + 1,
name: d.name,
url: `https://deckyvault.xyz/devices/${d.slug}`,
})),
}
return (
<>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
<DevicesPageClient />
</>
)
} }
+17
View File
@@ -116,6 +116,23 @@ export default function Landing() {
Github. Github.
</Link> </Link>
</motion.small> </motion.small>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "WebSite",
name: "DeckyVault",
url: "https://deckyvault.xyz",
description: "Steam Deck benchmarks, settings, and performance guides",
potentialAction: {
"@type": "SearchAction",
target: "https://deckyvault.xyz/search?q={search_term_string}",
"query-input": "required name=search_term_string",
},
}),
}}
/>
</section> </section>
) )
} }
+12
View File
@@ -0,0 +1,12 @@
import type { Metadata } from "next"
export const metadata: Metadata = {
title: "Search Games",
description: "Search for games and find benchmarks, settings, and performance data on DeckyVault.",
alternates: { canonical: "https://deckyvault.xyz/search" },
robots: { index: false },
}
export default function SearchLayout({ children }: { children: React.ReactNode }) {
return children
}
+27 -14
View File
@@ -1,21 +1,34 @@
import type { MetadataRoute } from "next" import type { MetadataRoute } from "next"
import { db } from "@/lib/db/index"
import { games, hardware } from "@/lib/db/schema"
const BASE_URL = "https://deckyvault.xyz" const BASE_URL = "https://deckyvault.xyz"
export default function sitemap(): MetadataRoute.Sitemap { export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
return [ const [allGames, allDevices] = await Promise.all([
{ db.select({ id: games.id, updatedAt: games.updatedAt }).from(games),
url: BASE_URL, db.select({ slug: hardware.slug, updatedAt: hardware.updatedAt }).from(hardware),
lastModified: new Date(), ])
const gameEntries: MetadataRoute.Sitemap = allGames.map((game) => ({
url: `${BASE_URL}/game/${game.id}`,
lastModified: game.updatedAt,
changeFrequency: "weekly", changeFrequency: "weekly",
priority: 1, priority: 0.8,
}, }))
// Future dynamic routes can be added here:
// { const deviceEntries: MetadataRoute.Sitemap = allDevices.map((device) => ({
// url: `${BASE_URL}/games/${game.slug}`, url: `${BASE_URL}/devices/${device.slug}`,
// lastModified: game.updatedAt, lastModified: device.updatedAt,
// changeFrequency: "weekly", changeFrequency: "monthly",
// priority: 0.8, priority: 0.6,
// }, }))
return [
{ url: BASE_URL, lastModified: new Date(), changeFrequency: "weekly", priority: 1 },
{ url: `${BASE_URL}/search`, lastModified: new Date(), changeFrequency: "weekly", priority: 0.7 },
{ url: `${BASE_URL}/devices`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.6 },
...gameEntries,
...deviceEntries,
] ]
} }