204 lines
4.8 KiB
Markdown
204 lines
4.8 KiB
Markdown
# PRD: SEO & Metadata System - V2
|
|
|
|
## Executive Summary
|
|
|
|
This updated PRD acknowledges the existing comprehensive SEO metadata implementation on jedmund.com and focuses on the remaining gaps: dynamic sitemap generation, OG image generation for text content, and metadata testing/validation.
|
|
|
|
## Current State Assessment
|
|
|
|
### Already Implemented ✅
|
|
|
|
- **Metadata utilities** (`/src/lib/utils/metadata.ts`) providing:
|
|
- Complete OpenGraph and Twitter Card support
|
|
- JSON-LD structured data generators
|
|
- Smart title formatting and fallbacks
|
|
- Canonical URL handling
|
|
- **100% page coverage** with appropriate metadata
|
|
- **Dynamic content support** with excerpt generation
|
|
- **Error handling** with noindex for 404 pages
|
|
|
|
### Remaining Gaps ❌
|
|
|
|
1. **No dynamic sitemap.xml**
|
|
2. **No OG image generation API** for text-based content
|
|
3. **No automated metadata validation**
|
|
4. **robots.txt doesn't reference sitemap**
|
|
|
|
## Revised Goals
|
|
|
|
1. **Complete technical SEO** with dynamic sitemap generation
|
|
2. **Enhance social sharing** with generated OG images for text content
|
|
3. **Ensure quality** with metadata validation tools
|
|
4. **Improve discoverability** with complete robots.txt
|
|
|
|
## Proposed Implementation
|
|
|
|
### Phase 1: Dynamic Sitemap (Week 1)
|
|
|
|
#### Create `/src/routes/sitemap.xml/+server.ts`
|
|
|
|
```typescript
|
|
export async function GET() {
|
|
const pages = await getAllPublicPages()
|
|
const xml = generateSitemapXML(pages)
|
|
|
|
return new Response(xml, {
|
|
headers: {
|
|
'Content-Type': 'application/xml',
|
|
'Cache-Control': 'max-age=3600'
|
|
}
|
|
})
|
|
}
|
|
```
|
|
|
|
Features:
|
|
|
|
- Auto-discover all public routes
|
|
- Include lastmod dates from content
|
|
- Set appropriate priorities
|
|
- Exclude admin routes
|
|
|
|
### Phase 2: OG Image Generation (Week 1-2)
|
|
|
|
#### Create `/src/routes/api/og-image/+server.ts`
|
|
|
|
```typescript
|
|
export async function GET({ url }) {
|
|
const { title, subtitle, type } = Object.fromEntries(url.searchParams)
|
|
|
|
const svg = generateOGImageSVG({
|
|
title,
|
|
subtitle,
|
|
type, // 'post', 'project', 'default'
|
|
brandColor: '#your-brand-color'
|
|
})
|
|
|
|
const png = await convertSVGtoPNG(svg)
|
|
|
|
return new Response(png, {
|
|
headers: {
|
|
'Content-Type': 'image/png',
|
|
'Cache-Control': 'public, max-age=31536000'
|
|
}
|
|
})
|
|
}
|
|
```
|
|
|
|
Templates:
|
|
|
|
- **Posts**: Title + excerpt on branded background
|
|
- **Projects**: Logo placeholder + title
|
|
- **Default**: Site logo + tagline
|
|
|
|
### Phase 3: Metadata Validation (Week 2)
|
|
|
|
#### Create `/src/lib/utils/metadata-validator.ts`
|
|
|
|
```typescript
|
|
export function validateMetaTags(page: string) {
|
|
return {
|
|
hasTitle: checkTitle(),
|
|
titleLength: getTitleLength(),
|
|
hasDescription: checkDescription(),
|
|
descriptionLength: getDescriptionLength(),
|
|
hasOGImage: checkOGImage(),
|
|
hasCanonical: checkCanonical(),
|
|
structuredDataValid: validateJSONLD()
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Add development-only validation component
|
|
|
|
- Console warnings for missing/invalid metadata
|
|
- Visual indicators in dev mode
|
|
- Automated tests for all routes
|
|
|
|
### Phase 4: Final Touches (Week 2)
|
|
|
|
1. **Update robots.txt**
|
|
|
|
```
|
|
Sitemap: https://jedmund.com/sitemap.xml
|
|
|
|
# Existing rules...
|
|
```
|
|
|
|
2. **Add metadata debugging route** (dev only)
|
|
|
|
- `/api/meta-debug` - JSON output of all pages' metadata
|
|
- Useful for testing social media previews
|
|
|
|
## Success Metrics
|
|
|
|
- [ ] Sitemap.xml validates and includes all public pages
|
|
- [ ] OG images generate for all text-based content
|
|
- [ ] All pages pass metadata validation
|
|
- [ ] Google Search Console shows improved indexing
|
|
- [ ] Social media previews display correctly
|
|
|
|
## Technical Considerations
|
|
|
|
### Performance
|
|
|
|
- Cache generated OG images (1 year)
|
|
- Cache sitemap (1 hour)
|
|
- Lazy-load validation in development only
|
|
|
|
### Maintenance
|
|
|
|
- Sitemap auto-updates with new content
|
|
- OG image templates easy to modify
|
|
- Validation runs in CI/CD pipeline
|
|
|
|
## Implementation Timeline
|
|
|
|
**Week 1:**
|
|
|
|
- Day 1-2: Implement dynamic sitemap
|
|
- Day 3-5: Create OG image generation API
|
|
|
|
**Week 2:**
|
|
|
|
- Day 1-2: Add metadata validation utilities
|
|
- Day 3-4: Testing and refinement
|
|
- Day 5: Documentation and deployment
|
|
|
|
Total: **2 weeks** (vs. original 5 weeks)
|
|
|
|
## Future Enhancements
|
|
|
|
1. **A/B testing** different OG images/titles
|
|
2. **Multi-language support** with hreflang tags
|
|
3. **Advanced schemas** (FAQ, HowTo) for specific content
|
|
4. **Analytics integration** to track metadata performance
|
|
|
|
## Appendix: Current Implementation Reference
|
|
|
|
### Existing Files
|
|
|
|
- `/src/lib/utils/metadata.ts` - Core utilities
|
|
- `/src/lib/utils/content.ts` - Content extraction
|
|
- `/src/routes/+layout.svelte` - Default metadata
|
|
- All page routes - Individual implementations
|
|
|
|
### Usage Pattern
|
|
|
|
```svelte
|
|
<script>
|
|
import { generateMetaTags, generateArticleJsonLd } from '$lib/utils/metadata'
|
|
|
|
$: metaTags = generateMetaTags({
|
|
title: pageTitle,
|
|
description: pageDescription,
|
|
url: $page.url.href,
|
|
type: 'article',
|
|
image: contentImage
|
|
})
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>{metaTags.title}</title>
|
|
<!-- ... rest of meta tags ... -->
|
|
</svelte:head>
|
|
```
|