feat(seo): add metadata, sitemap, robots, JSON-LD, dynamic OG image

- Centralize brand/keyword config in lib/site.ts (slogan, 22 keywords).
- Root layout: metadataBase, title template, OG/Twitter defaults, robots rules.
- Per-page metadata: home, trips list (filter-aware), trip detail, profile (noindex).
- Layout wrappers add metadata to client-component pages: login, register, create-trip.
- Trip detail: generateMetadata + JSON-LD Event + BreadcrumbList (price, availability, rating).
- Home page: JSON-LD Organization + WebSite + SearchAction (sitelink search).
- app/sitemap.ts: dynamic sitemap pulling OPEN/FULL trips from Prisma.
- app/robots.ts: disallow /api/, /profile, /create-trip; references sitemap.
- app/trips/[id]/opengraph-image.tsx: dynamic 1200x630 OG image per trip with
  cover photo, title, mountain, date, price, brand badge.
- Seeder: switch trip images from local SVG placeholders to real Unsplash CDN URLs.
- Drop 18 obsolete seed SVGs from public/images/seed/.

New env: NEXT_PUBLIC_SITE_URL (defaults to localhost:3000) — set to prod domain on deploy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
arifal
2026-04-27 02:14:08 +07:00
parent a4508dc828
commit d91e16b6ef
31 changed files with 657 additions and 255 deletions
+43 -18
View File
@@ -107,6 +107,31 @@ async function main() {
const utc = (y: number, m0: number, d: number, h = 12, min = 0) =>
new Date(Date.UTC(y, m0, d, h, min, 0, 0));
// Unsplash mountain photos (URL CDN publik, gratis, stabil).
// Slug ID di komentar = id di unsplash.com/photos/{slug} buat ditelusuri ulang.
const img = (id: string) =>
`https://images.unsplash.com/photo-${id}?w=1200&q=80&auto=format&fit=crop`;
const MOUNTAIN_PHOTOS = {
papandayan1: img("1554629947-334ff61d85dc"), // xfngap_DToE
papandayan2: img("1464822759023-fed622ff2c3b"), // Bkci_8qcdvQ
papandayan3: img("1454496522488-7a8e488e8606"), // 9wg5jCEPBsw
ciremai1: img("1480497490787-505ec076689f"), // 6bKxagnIDtk
ciremai2: img("1483728642387-6c3bdd6c93e5"), // YFFGkE3y4F8
ciremai3: img("1502085671122-2d218cd434e6"), // NNmiv6zcFvk
gede1: img("1478059299873-f047d8c5fe1a"), // DXQB5D1njMY
gede2: img("1519681393784-d120267933ba"), // z8ct_Q3oCqM
gede3: img("1501785888041-af3ef285b470"), // T7K4aEPoGGk
gede4: img("1540979388789-6cee28a1cdc9"), // eUFfY6cwjSU
tangkuban1: img("1506905925346-21bda4d32df4"), // 1527pjeb6jg
tangkuban2: img("1490682143684-14369e18dce8"), // 8c6eS43iq1o
malabar1: img("1494548162494-384bba4ab999"), // xP_AGmeEa6s
malabar2: img("1500964757637-c85e8a162699"), // twukN12EN7c
malabar3: img("1549880181-56a44cf4a9a5"), // ePpaQC2c1xA
guntur1: img("1558883493-8b86ff880fec"), // vaG8rOJLDHo
guntur2: img("1554629947-334ff61d85dc"), // reuse — xfngap_DToE
guntur3: img("1464822759023-fed622ff2c3b"), // reuse — Bkci_8qcdvQ
} as const;
// --- Trip 1: Papandayan (by Dede Inoen) — 2 hari ---
const trip1 = await prisma.trip.create({
data: {
@@ -144,9 +169,9 @@ Minggu
organizerId: dede.id,
images: {
create: [
{ url: "/images/seed/papandayan-1.svg", caption: "Kawah Papandayan", order: 0 },
{ url: "/images/seed/papandayan-2.svg", caption: "Track menuju puncak", order: 1 },
{ url: "/images/seed/papandayan-3.svg", caption: "Camping ground Pondok Salada", order: 2 },
{ url: MOUNTAIN_PHOTOS.papandayan1, caption: "Kawah Papandayan", order: 0 },
{ url: MOUNTAIN_PHOTOS.papandayan2, caption: "Track menuju puncak", order: 1 },
{ url: MOUNTAIN_PHOTOS.papandayan3, caption: "Camping ground Pondok Salada", order: 2 },
],
},
},
@@ -175,9 +200,9 @@ Itinerary:
organizerId: panji.id,
images: {
create: [
{ url: "/images/seed/ciremai-1.svg", caption: "Puncak Ciremai 3.078 mdpl", order: 0 },
{ url: "/images/seed/ciremai-2.svg", caption: "Jalur pendakian via Apuy", order: 1 },
{ url: "/images/seed/ciremai-3.svg", caption: "Sunrise dari puncak", order: 2 },
{ url: MOUNTAIN_PHOTOS.ciremai1, caption: "Puncak Ciremai 3.078 mdpl", order: 0 },
{ url: MOUNTAIN_PHOTOS.ciremai2, caption: "Jalur pendakian via Apuy", order: 1 },
{ url: MOUNTAIN_PHOTOS.ciremai3, caption: "Sunrise dari puncak", order: 2 },
],
},
},
@@ -204,10 +229,10 @@ Start malam, summit saat sunrise. View epic dijamin!`,
organizerId: fiersa.id,
images: {
create: [
{ url: "/images/seed/gede-1.svg", caption: "Puncak Gunung Gede", order: 0 },
{ url: "/images/seed/gede-2.svg", caption: "Surya Kencana padang edelweis", order: 1 },
{ url: "/images/seed/gede-3.svg", caption: "Blue lake / Danau Biru", order: 2 },
{ url: "/images/seed/gede-4.svg", caption: "Night hike track Cibodas", order: 3 },
{ url: MOUNTAIN_PHOTOS.gede1, caption: "Puncak Gunung Gede", order: 0 },
{ url: MOUNTAIN_PHOTOS.gede2, caption: "Surya Kencana padang edelweis", order: 1 },
{ url: MOUNTAIN_PHOTOS.gede3, caption: "Blue lake / Danau Biru", order: 2 },
{ url: MOUNTAIN_PHOTOS.gede4, caption: "Night hike track Cibodas", order: 3 },
],
},
},
@@ -234,8 +259,8 @@ Explore Kawah Ratu, Kawah Domas, foto-foto, terus makan sate maranggi!`,
organizerId: dede.id,
images: {
create: [
{ url: "/images/seed/tangkuban-1.svg", caption: "Kawah Ratu", order: 0 },
{ url: "/images/seed/tangkuban-2.svg", caption: "Kawah Domas", order: 1 },
{ url: MOUNTAIN_PHOTOS.tangkuban1, caption: "Kawah Ratu", order: 0 },
{ url: MOUNTAIN_PHOTOS.tangkuban2, caption: "Kawah Domas", order: 1 },
],
},
},
@@ -262,9 +287,9 @@ Trip ringan, 3-4 jam naik. Cocok buat yang mau healing malam-malam.`,
organizerId: fiersa.id,
images: {
create: [
{ url: "/images/seed/malabar-1.svg", caption: "Puncak Malabar malam hari", order: 0 },
{ url: "/images/seed/malabar-2.svg", caption: "View Bandung dari atas", order: 1 },
{ url: "/images/seed/malabar-3.svg", caption: "Track pendakian", order: 2 },
{ url: MOUNTAIN_PHOTOS.malabar1, caption: "Puncak Malabar malam hari", order: 0 },
{ url: MOUNTAIN_PHOTOS.malabar2, caption: "View Bandung dari atas", order: 1 },
{ url: MOUNTAIN_PHOTOS.malabar3, caption: "Track pendakian", order: 2 },
],
},
},
@@ -291,9 +316,9 @@ Buat yang suka challenge. Pemandangan kawah aktif dari dekat!`,
organizerId: panji.id,
images: {
create: [
{ url: "/images/seed/guntur-1.svg", caption: "Kawah aktif Gunung Guntur", order: 0 },
{ url: "/images/seed/guntur-2.svg", caption: "Jalur berbatu menuju puncak", order: 1 },
{ url: "/images/seed/guntur-3.svg", caption: "View dari puncak", order: 2 },
{ url: MOUNTAIN_PHOTOS.guntur1, caption: "Kawah aktif Gunung Guntur", order: 0 },
{ url: MOUNTAIN_PHOTOS.guntur2, caption: "Jalur berbatu menuju puncak", order: 1 },
{ url: MOUNTAIN_PHOTOS.guntur3, caption: "View dari puncak", order: 2 },
],
},
},