import { z } from "zod"; // Structured data schemas const howtoStepSchema = z.object({ name: z.string(), text: z.string(), image: z.string().optional(), url: z.string().optional(), }); const howtoSchema = z.object({ totalTime: z.string().optional(), // ISO 8601 duration format (e.g., "PT2H") steps: z.array(howtoStepSchema), }); const faqItemSchema = z.object({ question: z.string(), answer: z.string(), }); // Per-page SEO/social overrides. Each falls back to its top-level twin // (seo.title ?? title, seo.description ?? description); the rest are opt-in. const seoSchema = z .object({ title: z.string(), description: z.string(), ogImage: z.string(), noindex: z.boolean(), canonical: z.string(), }) .partial(); export const metadataSchema = z.object({ title: z.string(), // Visible page heading; falls back to `title`. Lets the on-page h1 differ // from the canonical `title` used for SEO, breadcrumbs, search, and nav. hero: z.string().optional(), publishedAt: z.coerce.date(), // Last meaningful content edit. Feeds og:article:modified_time and JSON-LD // dateModified; falls back to `publishedAt` when absent. updatedAt: z.coerce.date().optional(), description: z.string(), category: z.string(), author: z.string(), image: z.string().optional(), seo: seoSchema.optional(), // Structured data fields howto: howtoSchema.optional(), faq: z.array(faqItemSchema).optional(), }); export type Metadata = z.infer; export type SeoData = z.infer; export type HowToStep = z.infer; export type HowToData = z.infer; export type FAQItem = z.infer;