Content Schema Reference
Everything you need to add products, articles, and categories to Zubd.io.
All content lives in src/content/ as MDX files.
Overview
Zubd.io is a static Astro site. Content is stored as MDX files (Markdown + JSX)
inside src/content/products/ and src/content/articles/.
Each content type has a Zod schema that validates frontmatter at
build time — if a required field is missing or wrong, the build fails with a clear error.
Every piece of content exists in two files: one in en/
and one in ar/. Both must have the same handle value
(which must also match the filename).
handle field = identical across EN and AR.
Example: cerave-moisturizing-cream.mdx in both en/ and ar/,
both with handle: "cerave-moisturizing-cream".
Product Schema
File path: src/content/products/en/{handle}.mdx
| Field | Type | Required | Description |
|---|---|---|---|
title | string | ✅ | Product display name |
handle | string | ✅ | URL slug — must match filename, identical across locales |
locale | "en" | "ar" | ✅ | Language of this file |
brand | string | ✅ | Brand name (e.g., "CeraVe") |
model | string | — | Product model/variant |
category | string | ✅ | Category slug — auto-creates the category page |
subcategory | string | — | Optional sub-category |
tags | string[] | — | Search/filter tags |
image | string (URL) | ✅ | Product image — use Unsplash URL or /images/products/ |
imageAlt | string | ✅ | Accessible image description |
gallery | string[] | — | Additional image URLs (for gallery thumbs) |
rating | 0–5 float | ✅ | Aggregate rating (e.g., 4.7) |
reviewCount | integer | — | Number of reviews contributing to rating |
affiliateUrl | URL string | ✅ | Full affiliate link — gets rel="nofollow sponsored" |
affiliateLabel | string | — | Buy button text (default: "Buy Now") |
price | string | — | Display price string (e.g., "$24.99") |
priceUpdated | ISO date string | — | When price was last verified |
metaTitle | string | — | SEO title override (falls back to title) |
metaDescription | string ≤320 chars | ✅ | SEO meta description |
pros | string[] | ✅ | List of pros |
cons | string[] | ✅ | List of cons |
userReviews | Review[] | — | See review object below |
publishedAt | ISO date string | ✅ | Publication date (e.g., "2025-04-07") |
updatedAt | ISO date string | — | Last updated date |
draft | boolean | — | true = hidden from build (default: false) |
User Review Object
| Field | Type | Description |
|---|---|---|
author | string | Reviewer name |
rating | 0–5 float | Reviewer's star rating |
date | ISO date string | Review date |
text | string | Review body text |
source | "amazon" | "google" | "manual" | Where the review came from |
Full Product Template
---
title: "Product Name"
handle: "product-name"
locale: "en"
brand: "Brand Name"
model: "Model Variant"
category: "category-slug"
tags: ["tag1", "tag2"]
image: "https://images.unsplash.com/photo-XXXXX?w=480&auto=format&fit=crop&q=80"
imageAlt: "Descriptive alt text"
rating: 4.5
reviewCount: 120
affiliateUrl: "https://amzn.to/XXXXXX"
affiliateLabel: "Buy on Amazon"
price: "$49.99"
priceUpdated: "2025-04-07"
metaDescription: "Short description under 160 chars for SEO."
pros:
- "First pro"
- "Second pro"
cons:
- "First con"
userReviews:
- author: "Jane D."
rating: 5
date: "2025-03-01"
text: "Great product!"
source: "amazon"
publishedAt: "2025-04-07"
draft: false
---
## Overview
Your review body in Markdown here... Article Schema
File path: src/content/articles/en/{handle}.mdx
| Field | Type | Required | Description |
|---|---|---|---|
title | string | ✅ | Article headline |
handle | string | ✅ | URL slug — must match filename |
locale | "en" | "ar" | ✅ | Language of this file |
category | string | ✅ | Category slug |
subcategory | string | — | Optional sub-category |
tags | string[] | — | Tags for discoverability |
heroImage | string (URL) | ✅ | Hero image URL |
heroImageAlt | string | ✅ | Hero image alt text |
excerpt | string ≤300 chars | ✅ | Article summary shown in cards |
featuredProducts | string[] | ✅ | Ordered list of product handles (rank #1 first) |
metaTitle | string | — | SEO title override |
metaDescription | string ≤320 chars | ✅ | SEO meta description |
author | string | — | Author name (default: "Zubd Editorial") |
publishedAt | ISO date string | ✅ | Publication date |
updatedAt | ISO date string | — | Last major update |
draft | boolean | — | true = hidden (default: false) |
featuredProducts is the key field that connects an article to its
product cards. List the product handle values in rank order — #1 first.
The RankedProductList component automatically fetches and renders them.
Full Article Template
---
title: "Top 10 [Products] for [Use Case] (2025)"
handle: "top-10-products-use-case"
locale: "en"
category: "category-slug"
tags: ["tag1", "tag2"]
heroImage: "https://images.unsplash.com/photo-XXXXX?w=1200&auto=format&fit=crop&q=80"
heroImageAlt: "Descriptive hero image alt text"
excerpt: "Concise summary of what the article covers (max 300 chars)."
featuredProducts:
- "product-handle-1"
- "product-handle-2"
- "product-handle-3"
metaTitle: "10 Best [Products] for [Use Case] (2025)"
metaDescription: "SEO description under 160 chars."
author: "Zubd Editorial"
publishedAt: "2025-04-07"
draft: false
---
## Why Trust This List?
Methodology paragraph...
## What to Look For
Key buying criteria... Categories
Categories are automatically derived from the category
field in your article frontmatter. There is no separate category file to create.
When you publish an article with category: "fitness", the page
/en/categories/fitness is automatically built and shows all articles in that category.
To add a new category, simply use a new category value in your article frontmatter.
To add a human-readable label for it, add an entry to src/lib/utils.ts in the
getCategoryLabel() function:
// src/lib/utils.ts → getCategoryLabel()
fitness: { en: 'Fitness', ar: 'اللياقة البدنية' },
food: { en: 'Food & Kitchen', ar: 'الطعام والمطبخ' },
// Add your new category here: Images (Unsplash)
All product and article images use Unsplash URLs directly — no image hosting required. Use this URL format:
https://images.unsplash.com/photo-{PHOTO_ID}?w=1200&auto=format&fit=crop&q=80 Common sizes:
- Article hero:
?w=1200&h=630&fit=crop - Product image:
?w=480&h=480&fit=crop - OG image:
?w=1200&h=630&fit=crop
Find images at unsplash.com — copy the photo ID from the URL.
Publishing Workflow
- Create
src/content/products/en/your-product.mdx - Create
src/content/products/ar/your-product.mdx(same filename) - Add the product
handleto an article'sfeaturedProductslist - Create
src/content/articles/en/your-article.mdx - Create
src/content/articles/ar/your-article.mdx - Run
npm run build— Zod validates all frontmatter - Push to
main→ Cloudflare Pages auto-deploys
draft: true to save work-in-progress without publishing.
URL Structure
| Page | EN URL | AR URL |
|---|---|---|
| Homepage | /en | /ar |
| Article | /en/articles/{handle} | /ar/articles/{handle} |
| Product | /en/products/{handle} | /ar/products/{handle} |
| Category | /en/categories/{category} | /ar/categories/{category} |
| Privacy | /en/privacy | /ar/privacy |
| Contact | /en/contact | /ar/contact |
| Docs | /en/docs | — |
| Sitemap | /sitemap-index.xml | |
| llms.txt | /llms.txt | |