Skip to content

The marketing app keeps Next.js metadata and JSON-LD in parallel, page-keyed handler maps. Routes select a page key and pass route data when the result depends on a Sanity document.

Architecture

text
src/data/metadata.ts
  -> TPageKey and static page metadata
  -> src/utils/metadata.ts -> src/utils/metadata/handlers/*
  -> Next.js metadata export or generateMetadata()

route data + TPageKey
  -> src/utils/schema.ts -> src/utils/schema/handlers/*
  -> JsonLdSchema -> <script type="application/ld+json">

getPageMetadata() and getPageSchemaGraph() are the public app helpers. Each dispatches to the handler for its TPageKey; shared builders under src/utils/metadata/helpers.ts and src/utils/schema/helpers.ts keep canonical URLs, images, schema IDs, organization references, and other repeated values consistent. Add a page key and both handlers together when a route needs both metadata and structured data.

Route Usage

Code-driven pages can export metadata and render JSON-LD directly:

tsx
export const metadata = getPageMetadata('home')

export default function Home() {
  return (
    <>
      <HeroSection />
      <FeaturesSection />
      <EventTypesSection />
      <CTASection />
      <JsonLdSchema id="global-schema" schema={getPageSchemaGraph('home')} />
    </>
  )
}

Dynamic blog routes fetch their document and pass it to the matching page-key handler. The post route is the representative pattern:

tsx
export async function generateMetadata({ params }: TProps): Promise<Metadata> {
  const { slug } = await params
  const { data: post } = await getPostCached(slug)
  return getPageMetadata('blog/[slug]', { post: post || undefined })
}

The rendered route passes the same post to its structured-data handler:

tsx
<JsonLdSchema id="blog-page" schema={getPageSchemaGraph('blog/[slug]', { post })} />

The blog-post metadata handler derives its description from Portable Text and supplies the canonical URL, category keywords, article Open Graph fields, and Twitter image data. Author and category detail routes use the same pattern with their own route data. See Blog System for Sanity fetching and route composition.

JSON-LD Graphs

JsonLdSchema serializes a typed TSchemaGraph into an application/ld+json script and escapes < characters in the serialized value. Page-specific handlers choose the graph nodes; routes do not assemble unrelated schema objects inline.

For example, a blog-post graph contains the shared WebSite and Organization, the post WebPage, its primary ImageObject, a BreadcrumbList, and an Article. It adds a Person when the post has an author. Other handlers add only the schema relevant to their page, such as FAQPage, Service, ContactPoint, or collection nodes.

Use the ID and URL helpers in src/utils/schema/helpers.ts so references to the website, organization, page, image, breadcrumb, article, and person remain stable across graph nodes.

Robots And Sitemaps

Next.js metadata routes own crawler discovery:

  • src/app/robots.ts allows the public site, disallows /private/, and advertises the root, post, category, and author sitemaps.
  • src/app/sitemap.ts derives its page entries from src/data/metadata.ts. In launch redirect mode it limits that sitemap to the reachable early-access and blog listing routes.
  • The blog post, author, and category sitemap modules read current slugs and update timestamps through src/utils/blog/sitemap.ts. They log a failure and return an empty collection rather than failing sitemap generation.

Keep sitemap derivation connected to the same route and content sources as page metadata. Blog resource behavior and publication ownership remain in Blog System and Content Management.

Locale Conventions

The app currently uses distinct locale formats for distinct outputs:

  • Page layouts declare HTML language en.
  • Open Graph metadata uses locale en_US.
  • Schema.org nodes use IN_LANGUAGE = 'en-CA'.

Do not apply one of these values globally without changing and validating all three output contracts. Visible and metadata copy comes from src/utils/strings.ts; structured organization and product values come from src/data/schema.ts.

Built with ❤️ by the Jubiloop team