feat: SEO overhaul — expanded sitemap, JSON-LD, canonical URLs, search metadata
This commit is contained in:
@@ -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} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-4
@@ -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 />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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> {
|
||||||
|
const [allGames, allDevices] = await Promise.all([
|
||||||
|
db.select({ id: games.id, updatedAt: games.updatedAt }).from(games),
|
||||||
|
db.select({ slug: hardware.slug, updatedAt: hardware.updatedAt }).from(hardware),
|
||||||
|
])
|
||||||
|
|
||||||
|
const gameEntries: MetadataRoute.Sitemap = allGames.map((game) => ({
|
||||||
|
url: `${BASE_URL}/game/${game.id}`,
|
||||||
|
lastModified: game.updatedAt,
|
||||||
|
changeFrequency: "weekly",
|
||||||
|
priority: 0.8,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const deviceEntries: MetadataRoute.Sitemap = allDevices.map((device) => ({
|
||||||
|
url: `${BASE_URL}/devices/${device.slug}`,
|
||||||
|
lastModified: device.updatedAt,
|
||||||
|
changeFrequency: "monthly",
|
||||||
|
priority: 0.6,
|
||||||
|
}))
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{ url: BASE_URL, lastModified: new Date(), changeFrequency: "weekly", priority: 1 },
|
||||||
url: BASE_URL,
|
{ url: `${BASE_URL}/search`, lastModified: new Date(), changeFrequency: "weekly", priority: 0.7 },
|
||||||
lastModified: new Date(),
|
{ url: `${BASE_URL}/devices`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.6 },
|
||||||
changeFrequency: "weekly",
|
...gameEntries,
|
||||||
priority: 1,
|
...deviceEntries,
|
||||||
},
|
|
||||||
// Future dynamic routes can be added here:
|
|
||||||
// {
|
|
||||||
// url: `${BASE_URL}/games/${game.slug}`,
|
|
||||||
// lastModified: game.updatedAt,
|
|
||||||
// changeFrequency: "weekly",
|
|
||||||
// priority: 0.8,
|
|
||||||
// },
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user