From be89498530b7566269ee204c7138b55942ef73b1 Mon Sep 17 00:00:00 2001 From: Lamont Crook Date: Mon, 6 Jul 2026 15:46:47 -0600 Subject: [PATCH] Add per-page metadata via generateMetadata (from query-index) The catch-all now exports generateMetadata, reading each page's title, description, and og:image from the query-index feed (authored via the page's Metadata block, indexed by helix-query.yaml). Previously every Worker-rendered page used the static layout title ("next-eds spike"). Falls back to the layout defaults when a page isn't indexed. Co-Authored-By: Claude Opus 4.8 --- app/(site)/[[...slug]]/page.js | 9 +++++++++ lib/eds/metadata.js | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 lib/eds/metadata.js diff --git a/app/(site)/[[...slug]]/page.js b/app/(site)/[[...slug]]/page.js index 0a99446..a7281f8 100644 --- a/app/(site)/[[...slug]]/page.js +++ b/app/(site)/[[...slug]]/page.js @@ -2,6 +2,15 @@ import { notFound } from 'next/navigation'; import { fetchPlainHtml, EDS_ORIGIN } from '../../../lib/eds/fetch.js'; import { parseEds } from '../../../lib/eds/parse.js'; import { renderNode } from '../../../lib/eds/render.js'; +import { buildMetadata } from '../../../lib/eds/metadata.js'; + +// Per-page metadata (title, description, Open Graph) from the page's authored Metadata +// block, read via the query-index feed. Falls back to the root layout defaults when a page +// isn't indexed. +export async function generateMetadata({ params }) { + const { slug } = await params; + return buildMetadata((slug ?? []).join('/')); +} // Pre-render every published page at build time from the EDS query-index feed (SSG). Pages // not in the feed (or added later) still resolve on demand via ISR, since dynamicParams diff --git a/lib/eds/metadata.js b/lib/eds/metadata.js new file mode 100644 index 0000000..7e172a5 --- /dev/null +++ b/lib/eds/metadata.js @@ -0,0 +1,23 @@ +import { fetchQueryIndex, normalizePath } from './queryIndex.js'; + +// Build Next.js metadata for a page from the EDS query-index — title, description, and +// og:image, all authored via the page's Metadata block and indexed by helix-query.yaml. +// Returns {} when the page isn't in the feed (unpublished / excluded), so the root layout's +// default title/description apply. +export async function buildMetadata(pathSlug = '') { + const map = await fetchQueryIndex(); + const row = map.get(normalizePath(`/${pathSlug}`)); + if (!row) return {}; + + const meta = {}; + if (row.title) meta.title = row.title; + if (row.description) meta.description = row.description; + + const openGraph = {}; + if (row.title) openGraph.title = row.title; + if (row.description) openGraph.description = row.description; + if (row.image) openGraph.images = [{ url: row.image }]; + if (Object.keys(openGraph).length) meta.openGraph = openGraph; + + return meta; +}