585 lines
12 KiB
Markdown
585 lines
12 KiB
Markdown
# Astro Frontmatter Schemas Skill
|
|
|
|
Common Zod schema patterns and frontmatter examples for Astro content collections.
|
|
|
|
## When to Use This Skill
|
|
|
|
- Designing content collection schemas
|
|
- Creating frontmatter for markdown files
|
|
- Validating content structure
|
|
- Setting up type-safe content
|
|
|
|
## Common Schema Patterns
|
|
|
|
### Blog Post Schema
|
|
|
|
```typescript
|
|
import { defineCollection, z } from 'astro:content';
|
|
import { glob } from 'astro/loaders';
|
|
|
|
const blog = defineCollection({
|
|
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
|
|
schema: z.object({
|
|
// Core Fields
|
|
title: z.string().max(80),
|
|
description: z.string().max(160),
|
|
pubDate: z.coerce.date(),
|
|
updatedDate: z.coerce.date().optional(),
|
|
|
|
// Author
|
|
author: z.string(),
|
|
authorUrl: z.string().url().optional(),
|
|
|
|
// Categorization
|
|
tags: z.array(z.string()).default([]),
|
|
category: z.enum(['tutorial', 'news', 'guide', 'review']).optional(),
|
|
|
|
// Publishing
|
|
draft: z.boolean().default(false),
|
|
featured: z.boolean().default(false),
|
|
|
|
// Media
|
|
image: z.object({
|
|
url: z.string(),
|
|
alt: z.string()
|
|
}).optional(),
|
|
|
|
// SEO
|
|
canonicalURL: z.string().url().optional(),
|
|
|
|
// Metadata
|
|
readingTime: z.number().optional(),
|
|
relatedPosts: z.array(z.string()).default([])
|
|
})
|
|
});
|
|
```
|
|
|
|
**Corresponding Frontmatter:**
|
|
|
|
```yaml
|
|
---
|
|
title: 'Getting Started with Astro'
|
|
description: 'Learn the fundamentals of building fast websites with Astro'
|
|
pubDate: 2024-01-15
|
|
updatedDate: 2024-01-20
|
|
author: 'John Doe'
|
|
authorUrl: 'https://example.com/john'
|
|
tags: ['astro', 'tutorial', 'web-dev']
|
|
category: 'tutorial'
|
|
draft: false
|
|
featured: true
|
|
image:
|
|
url: './images/hero.jpg'
|
|
alt: 'Astro logo with stars'
|
|
canonicalURL: 'https://example.com/blog/getting-started'
|
|
readingTime: 8
|
|
relatedPosts: ['advanced-astro', 'astro-tips']
|
|
---
|
|
```
|
|
|
|
### Documentation Schema
|
|
|
|
```typescript
|
|
const docs = defineCollection({
|
|
loader: glob({ pattern: "**/*.mdx", base: "./src/content/docs" }),
|
|
schema: z.object({
|
|
title: z.string(),
|
|
description: z.string(),
|
|
|
|
// Sidebar
|
|
sidebar: z.object({
|
|
order: z.number(),
|
|
label: z.string().optional(),
|
|
hidden: z.boolean().default(false),
|
|
badge: z.string().optional()
|
|
}).optional(),
|
|
|
|
// Status
|
|
lastUpdated: z.coerce.date().optional(),
|
|
version: z.string().optional(),
|
|
deprecated: z.boolean().default(false),
|
|
|
|
// Contributors
|
|
contributors: z.array(z.string()).default([]),
|
|
|
|
// Navigation
|
|
prev: z.object({
|
|
text: z.string(),
|
|
link: z.string()
|
|
}).optional(),
|
|
next: z.object({
|
|
text: z.string(),
|
|
link: z.string()
|
|
}).optional(),
|
|
|
|
// Table of Contents
|
|
tableOfContents: z.boolean().default(true),
|
|
tocDepth: z.number().min(1).max(6).default(3)
|
|
})
|
|
});
|
|
```
|
|
|
|
**Corresponding Frontmatter:**
|
|
|
|
```yaml
|
|
---
|
|
title: 'API Reference'
|
|
description: 'Complete API documentation for the core library'
|
|
sidebar:
|
|
order: 2
|
|
label: 'API Docs'
|
|
hidden: false
|
|
badge: 'New'
|
|
lastUpdated: 2024-01-20
|
|
version: '2.1.0'
|
|
deprecated: false
|
|
contributors: ['john-doe', 'jane-smith']
|
|
prev:
|
|
text: 'Installation'
|
|
link: '/docs/installation'
|
|
next:
|
|
text: 'Configuration'
|
|
link: '/docs/configuration'
|
|
tableOfContents: true
|
|
tocDepth: 3
|
|
---
|
|
```
|
|
|
|
### Product Schema
|
|
|
|
```typescript
|
|
const products = defineCollection({
|
|
loader: glob({ pattern: "**/*.json", base: "./src/content/products" }),
|
|
schema: z.object({
|
|
// Basic Info
|
|
name: z.string(),
|
|
description: z.string(),
|
|
shortDescription: z.string().max(100).optional(),
|
|
|
|
// Pricing
|
|
price: z.number().positive(),
|
|
currency: z.string().default('USD'),
|
|
salePrice: z.number().positive().optional(),
|
|
|
|
// Categorization
|
|
category: z.enum(['software', 'hardware', 'service', 'digital']),
|
|
tags: z.array(z.string()).default([]),
|
|
|
|
// Inventory
|
|
sku: z.string(),
|
|
inStock: z.boolean().default(true),
|
|
quantity: z.number().int().min(0).optional(),
|
|
|
|
// Features
|
|
features: z.array(z.string()),
|
|
specifications: z.record(z.string()).optional(),
|
|
|
|
// Media
|
|
images: z.array(z.object({
|
|
url: z.string(),
|
|
alt: z.string(),
|
|
primary: z.boolean().default(false)
|
|
})),
|
|
|
|
// Rating
|
|
rating: z.number().min(0).max(5).optional(),
|
|
reviewCount: z.number().int().min(0).default(0)
|
|
})
|
|
});
|
|
```
|
|
|
|
**Corresponding JSON:**
|
|
|
|
```json
|
|
{
|
|
"name": "Premium Web Hosting",
|
|
"description": "Fast, reliable web hosting with 99.9% uptime",
|
|
"shortDescription": "Premium hosting solution",
|
|
"price": 29.99,
|
|
"currency": "USD",
|
|
"salePrice": 19.99,
|
|
"category": "service",
|
|
"tags": ["hosting", "cloud", "premium"],
|
|
"sku": "HOST-001",
|
|
"inStock": true,
|
|
"quantity": 100,
|
|
"features": [
|
|
"99.9% uptime guarantee",
|
|
"Free SSL certificate",
|
|
"24/7 support"
|
|
],
|
|
"specifications": {
|
|
"storage": "100GB SSD",
|
|
"bandwidth": "Unlimited",
|
|
"domains": "1"
|
|
},
|
|
"images": [
|
|
{
|
|
"url": "/images/hosting-main.jpg",
|
|
"alt": "Server rack",
|
|
"primary": true
|
|
}
|
|
],
|
|
"rating": 4.5,
|
|
"reviewCount": 127
|
|
}
|
|
```
|
|
|
|
### Team Member Schema
|
|
|
|
```typescript
|
|
const team = defineCollection({
|
|
loader: glob({ pattern: "**/*.yml", base: "./src/content/team" }),
|
|
schema: z.object({
|
|
name: z.string(),
|
|
role: z.string(),
|
|
department: z.enum(['engineering', 'design', 'marketing', 'sales']).optional(),
|
|
|
|
bio: z.string(),
|
|
|
|
avatar: z.string(),
|
|
|
|
social: z.object({
|
|
twitter: z.string().url().optional(),
|
|
github: z.string().url().optional(),
|
|
linkedin: z.string().url().optional(),
|
|
website: z.string().url().optional()
|
|
}).optional(),
|
|
|
|
startDate: z.coerce.date(),
|
|
location: z.string().optional(),
|
|
|
|
skills: z.array(z.string()).default([]),
|
|
featured: z.boolean().default(false)
|
|
})
|
|
});
|
|
```
|
|
|
|
**Corresponding YAML:**
|
|
|
|
```yaml
|
|
name: 'Jane Smith'
|
|
role: 'Senior Software Engineer'
|
|
department: 'engineering'
|
|
bio: 'Full-stack developer with 10 years experience building scalable web applications.'
|
|
avatar: '/images/team/jane.jpg'
|
|
social:
|
|
twitter: 'https://twitter.com/janesmith'
|
|
github: 'https://github.com/janesmith'
|
|
linkedin: 'https://linkedin.com/in/janesmith'
|
|
website: 'https://janesmith.dev'
|
|
startDate: 2020-03-15
|
|
location: 'San Francisco, CA'
|
|
skills: ['TypeScript', 'React', 'Node.js', 'AWS']
|
|
featured: true
|
|
```
|
|
|
|
### Project/Portfolio Schema
|
|
|
|
```typescript
|
|
const projects = defineCollection({
|
|
loader: glob({ pattern: "**/*.md", base: "./src/content/projects" }),
|
|
schema: z.object({
|
|
title: z.string(),
|
|
description: z.string(),
|
|
|
|
technologies: z.array(z.string()),
|
|
|
|
liveUrl: z.string().url().optional(),
|
|
githubUrl: z.string().url().optional(),
|
|
demoUrl: z.string().url().optional(),
|
|
|
|
thumbnail: z.string(),
|
|
images: z.array(z.string()).default([]),
|
|
|
|
date: z.coerce.date(),
|
|
endDate: z.coerce.date().optional(),
|
|
|
|
status: z.enum(['active', 'completed', 'archived']).default('active'),
|
|
featured: z.boolean().default(false),
|
|
|
|
client: z.string().optional(),
|
|
team: z.array(z.string()).default([]),
|
|
|
|
tags: z.array(z.string()).default([])
|
|
})
|
|
});
|
|
```
|
|
|
|
**Corresponding Frontmatter:**
|
|
|
|
```yaml
|
|
---
|
|
title: 'E-commerce Platform Redesign'
|
|
description: 'Complete redesign and modernization of legacy e-commerce platform'
|
|
technologies: ['React', 'Next.js', 'TypeScript', 'Tailwind CSS', 'PostgreSQL']
|
|
liveUrl: 'https://shop.example.com'
|
|
githubUrl: 'https://github.com/example/shop'
|
|
thumbnail: './images/shop-thumb.jpg'
|
|
images:
|
|
- './images/shop-1.jpg'
|
|
- './images/shop-2.jpg'
|
|
- './images/shop-3.jpg'
|
|
date: 2023-06-01
|
|
endDate: 2023-12-15
|
|
status: 'completed'
|
|
featured: true
|
|
client: 'Example Corp'
|
|
team: ['john-doe', 'jane-smith']
|
|
tags: ['web-dev', 'e-commerce', 'react']
|
|
---
|
|
```
|
|
|
|
## Zod Validation Patterns
|
|
|
|
### String Validation
|
|
|
|
```typescript
|
|
// Basic string
|
|
title: z.string()
|
|
|
|
// Length constraints
|
|
title: z.string().min(1).max(100)
|
|
description: z.string().max(500)
|
|
|
|
// Email validation
|
|
email: z.string().email()
|
|
|
|
// URL validation
|
|
website: z.string().url()
|
|
|
|
// Regex pattern
|
|
slug: z.string().regex(/^[a-z0-9-]+$/)
|
|
phone: z.string().regex(/^\+?[1-9]\d{1,14}$/)
|
|
|
|
// Specific values
|
|
status: z.string().refine(val => ['draft', 'published'].includes(val))
|
|
```
|
|
|
|
### Number Validation
|
|
|
|
```typescript
|
|
// Basic number
|
|
count: z.number()
|
|
|
|
// Integer only
|
|
age: z.number().int()
|
|
|
|
// Positive numbers
|
|
price: z.number().positive()
|
|
|
|
// Range
|
|
rating: z.number().min(1).max(5)
|
|
percentage: z.number().min(0).max(100)
|
|
|
|
// With coercion (converts strings)
|
|
views: z.coerce.number()
|
|
```
|
|
|
|
### Date Validation
|
|
|
|
```typescript
|
|
// With coercion (recommended)
|
|
pubDate: z.coerce.date()
|
|
|
|
// Strict date object
|
|
deadline: z.date()
|
|
|
|
// Date range
|
|
startDate: z.coerce.date()
|
|
endDate: z.coerce.date().refine(
|
|
(date) => date > startDate,
|
|
"End date must be after start date"
|
|
)
|
|
```
|
|
|
|
### Array Validation
|
|
|
|
```typescript
|
|
// Array of strings
|
|
tags: z.array(z.string())
|
|
|
|
// Minimum items
|
|
tags: z.array(z.string()).min(1)
|
|
|
|
// Maximum items
|
|
images: z.array(z.string()).max(10)
|
|
|
|
// Default value
|
|
tags: z.array(z.string()).default([])
|
|
|
|
// Array of objects
|
|
authors: z.array(z.object({
|
|
name: z.string(),
|
|
email: z.string().email()
|
|
}))
|
|
```
|
|
|
|
### Object Validation
|
|
|
|
```typescript
|
|
// Nested object
|
|
author: z.object({
|
|
name: z.string(),
|
|
email: z.string().email(),
|
|
url: z.string().url().optional()
|
|
})
|
|
|
|
// Record (dynamic keys)
|
|
metadata: z.record(z.string())
|
|
settings: z.record(z.boolean())
|
|
|
|
// Partial object (all fields optional)
|
|
options: z.object({
|
|
theme: z.string(),
|
|
color: z.string()
|
|
}).partial()
|
|
```
|
|
|
|
### Enum Validation
|
|
|
|
```typescript
|
|
// Fixed set of values
|
|
status: z.enum(['draft', 'published', 'archived'])
|
|
priority: z.enum(['low', 'medium', 'high'])
|
|
category: z.enum(['news', 'tutorial', 'guide'])
|
|
```
|
|
|
|
### Boolean Validation
|
|
|
|
```typescript
|
|
// Basic boolean
|
|
published: z.boolean()
|
|
|
|
// With default
|
|
draft: z.boolean().default(false)
|
|
featured: z.boolean().default(false)
|
|
```
|
|
|
|
### Optional and Default Values
|
|
|
|
```typescript
|
|
// Optional field
|
|
subtitle: z.string().optional()
|
|
updatedDate: z.coerce.date().optional()
|
|
|
|
// With default value
|
|
draft: z.boolean().default(false)
|
|
views: z.number().default(0)
|
|
tags: z.array(z.string()).default([])
|
|
```
|
|
|
|
### Union Types
|
|
|
|
```typescript
|
|
// Multiple possible types
|
|
content: z.union([
|
|
z.string(),
|
|
z.object({ html: z.string() })
|
|
])
|
|
|
|
// Discriminated union
|
|
media: z.discriminatedUnion('type', [
|
|
z.object({
|
|
type: z.literal('image'),
|
|
url: z.string(),
|
|
alt: z.string()
|
|
}),
|
|
z.object({
|
|
type: z.literal('video'),
|
|
url: z.string(),
|
|
duration: z.number()
|
|
})
|
|
])
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Use z.coerce.date()** for date strings in frontmatter
|
|
2. **Provide defaults** for booleans and arrays
|
|
3. **Use enums** for fixed sets of values
|
|
4. **Validate formats** with built-in validators (email, url)
|
|
5. **Add constraints** (min, max) for better validation
|
|
6. **Use optional()** for truly optional fields
|
|
7. **Document schemas** with TypeScript comments
|
|
8. **Keep schemas DRY** by extracting common patterns
|
|
|
|
## Common Frontmatter Patterns
|
|
|
|
### Minimal Blog Post
|
|
|
|
```yaml
|
|
---
|
|
title: 'Post Title'
|
|
description: 'Post description'
|
|
pubDate: 2024-01-15
|
|
author: 'Author Name'
|
|
tags: ['tag1', 'tag2']
|
|
---
|
|
```
|
|
|
|
### Full-Featured Blog Post
|
|
|
|
```yaml
|
|
---
|
|
title: 'Comprehensive Post Title'
|
|
description: 'Detailed SEO-optimized description'
|
|
pubDate: 2024-01-15
|
|
updatedDate: 2024-01-20
|
|
author: 'Author Name'
|
|
authorUrl: 'https://author.com'
|
|
tags: ['web-dev', 'tutorial']
|
|
category: 'tutorial'
|
|
draft: false
|
|
featured: true
|
|
image:
|
|
url: './hero.jpg'
|
|
alt: 'Hero image description'
|
|
canonicalURL: 'https://example.com/original'
|
|
readingTime: 10
|
|
relatedPosts: ['post-1', 'post-2']
|
|
---
|
|
```
|
|
|
|
### Documentation Page
|
|
|
|
```yaml
|
|
---
|
|
title: 'Getting Started'
|
|
description: 'Introduction to the framework'
|
|
sidebar:
|
|
order: 1
|
|
label: 'Intro'
|
|
lastUpdated: 2024-01-15
|
|
contributors: ['john', 'jane']
|
|
tableOfContents: true
|
|
---
|
|
```
|
|
|
|
### Product (JSON)
|
|
|
|
```json
|
|
{
|
|
"name": "Product Name",
|
|
"description": "Product description",
|
|
"price": 99.99,
|
|
"category": "software",
|
|
"sku": "PROD-001",
|
|
"inStock": true,
|
|
"features": ["Feature 1", "Feature 2"],
|
|
"images": [
|
|
{ "url": "/img.jpg", "alt": "Product", "primary": true }
|
|
]
|
|
}
|
|
```
|
|
|
|
## Tips
|
|
|
|
- Always match frontmatter to schema exactly
|
|
- Use ISO date format: `2024-01-15`
|
|
- Quote strings with special characters
|
|
- Use arrays for multiple values: `['tag1', 'tag2']`
|
|
- Nest objects with proper indentation
|
|
- Test with sample content first
|
|
- Check generated types in `.astro/types.d.ts`
|