Documentation Index
Fetch the complete documentation index at: https://buttercms.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
Validation overview
Content validation checklist
Completeness check
Quality scan
Link validation
Possible link outcomes:
- ✅ Link works correctly
- ⚠️ Link to unmigrated content (fix before go-live)
- ⚠️ Link to source system (update to new URL)
- ❌ Broken link (must fix)
Image checklist
SEO validation
SEO checklist
SEO monitoring
After migration, monitor these metrics:
| Metric | Tool | What to Watch |
|---|
| Rankings | Google Search Console | Any significant drops |
| Indexing | Google Search Console | Pages being indexed |
| 404 Errors | Google Search Console | Broken URLs |
| Organic Traffic | Google Analytics | Traffic changes |
| Crawl Errors | Google Search Console | Crawl issues |
Redirect validation
async function validateRedirects(redirectMap) {
const issues = [];
for (const [oldUrl, newUrl] of Object.entries(redirectMap)) {
try {
const response = await fetch(oldUrl, { redirect: 'manual' });
if (response.status !== 301 && response.status !== 302) {
issues.push({
url: oldUrl,
expected: 'redirect',
actual: response.status
});
} else {
const location = response.headers.get('location');
if (!location.includes(newUrl)) {
issues.push({
url: oldUrl,
expected: newUrl,
actual: location
});
}
}
} catch (error) {
issues.push({
url: oldUrl,
error: error.message
});
}
}
return issues;
}
| Metric | Pre-Migration | Post-Migration | Target |
|---|
| Page Load | ___ ms | ___ ms | < 3000ms |
| TTFB | ___ ms | ___ ms | < 600ms |
| LCP | ___ s | ___ s | < 2.5s |
| FID | ___ ms | ___ ms | < 100ms |
| CLS | ___ | ___ | < 0.1 |
Functional testing
User acceptance testing
Conduct UAT with real users:
- Content team review - Have editors verify content accuracy
- Developer review - Verify API responses match expectations
- Stakeholder review - Business owners approve key pages
- QA review - Systematic testing of all functionality
Comparison report template
# Migration Validation Report
## Summary
- **Migration Date:** YYYY-MM-DD
- **Source System:** [WordPress/Contentful/etc]
- **Validation Date:** YYYY-MM-DD
- **Overall Status:** ✅ PASSED / ❌ ISSUES FOUND
## Content Counts
| Content Type | Expected | Migrated | Difference |
|--------------|----------|----------|------------|
| Blog Posts | 150 | 150 | 0 |
| Pages | 25 | 25 | 0 |
| Authors | 5 | 5 | 0 |
| Categories | 12 | 12 | 0 |
## Quality Checks
- [ ] ✅ All content visually inspected
- [ ] ✅ No broken formatting found
- [ ] ✅ No encoding issues
- [ ] ✅ All media loading correctly
## SEO Validation
- [ ] ✅ Meta titles preserved
- [ ] ✅ Meta descriptions preserved
- [ ] ✅ Redirects working
- [ ] ✅ Sitemap updated
## Performance
- [ ] ✅ Page load within acceptable range
- [ ] ✅ API response times acceptable
## Issues Found
| # | Severity | Description | Resolution |
|---|----------|-------------|------------|
| 1 | Low | Minor formatting in 3 posts | Fixed |
## Sign-off
- [ ] Content Team: _________________ Date: _______
- [ ] Development: _________________ Date: _______
- [ ] QA: __________________________ Date: _______
Monitoring after go-live
First 24-48 hours
- Monitor error logs
- Check for 404 errors
- Verify CDN is serving content
- Watch for user-reported issues
First week
- Compare traffic to pre-migration baseline
- Monitor search rankings
- Check indexing status
- Review user feedback
First month
- Full performance comparison
- SEO impact assessment
- User adoption metrics
- Identify any remaining issues
Rollback procedures
If critical issues are found:
Quick rollback (DNS-based)
- Point DNS back to old system
- Document all issues found
- Plan fixes before re-attempting
Partial rollback
- Identify problematic content
- Restore from backup or re-import
- Verify fixes before full cutover
Data recovery
- Use ButterCMS revision history if available
- Re-import from source backup
- Contact ButterCMS support for assistance
Automated validation
Automated validation script
// validation.js
const Butter = require('buttercms');
const BUTTERCMS_READ_TOKEN = process.env.BUTTERCMS_READ_TOKEN;
const butter = Butter(BUTTERCMS_READ_TOKEN);
class MigrationValidator {
constructor() {
this.issues = [];
this.stats = {
pagesChecked: 0,
collectionsChecked: 0,
mediaChecked: 0,
issuesFound: 0
};
}
async validatePages(pageType, expectedCount) {
console.log(`Validating ${pageType} pages...`);
const response = await butter.page.list(pageType, { page_size: 100 });
const pages = response.data.data;
// Check count
if (pages.length !== expectedCount) {
this.addIssue('count', `Expected ${expectedCount} ${pageType} pages, found ${pages.length}`);
}
// Validate each page
for (const page of pages) {
await this.validatePage(page);
this.stats.pagesChecked++;
}
}
async validatePage(page) {
const issues = [];
// Check required fields
if (!page.fields.title) {
issues.push('Missing title');
}
// Check for empty body
if (page.fields.body && page.fields.body.trim() === '') {
issues.push('Empty body content');
}
// Check for broken images
if (page.fields.featured_image) {
const imageValid = await this.checkImageUrl(page.fields.featured_image);
if (!imageValid) {
issues.push(`Broken image: ${page.fields.featured_image}`);
}
}
// Check references
if (page.fields.author && !page.fields.author.slug) {
issues.push('Invalid author reference');
}
// Check for encoding issues
const content = JSON.stringify(page.fields);
if (content.includes('�') || content.includes('&amp;')) {
issues.push('Possible encoding issues');
}
if (issues.length > 0) {
this.addIssue('page', {
slug: page.slug,
title: page.fields.title,
issues
});
}
}
async checkImageUrl(url) {
try {
const response = await fetch(url, { method: 'HEAD' });
return response.ok;
} catch {
return false;
}
}
async validateCollection(collectionKey, expectedCount) {
console.log(`Validating ${collectionKey} collection...`);
const response = await butter.content.retrieve([collectionKey]);
const items = response.data.data[collectionKey] || [];
if (items.length !== expectedCount) {
this.addIssue('count', `Expected ${expectedCount} ${collectionKey} items, found ${items.length}`);
}
this.stats.collectionsChecked++;
}
addIssue(type, details) {
this.issues.push({ type, details, timestamp: new Date().toISOString() });
this.stats.issuesFound++;
}
getReport() {
return {
stats: this.stats,
issues: this.issues,
passed: this.issues.length === 0
};
}
}
// Usage
async function runValidation() {
const validator = new MigrationValidator();
// Validate pages
await validator.validatePages('blog_post', 150); // Expected 150 posts
await validator.validatePages('landing_page', 10); // Expected 10 landing pages
// Validate collections
await validator.validateCollection('blog_authors', 5);
await validator.validateCollection('blog_categories', 12);
// Get report
const report = validator.getReport();
console.log('\n=== Validation Report ===');
console.log(`Pages checked: ${report.stats.pagesChecked}`);
console.log(`Collections checked: ${report.stats.collectionsChecked}`);
console.log(`Issues found: ${report.stats.issuesFound}`);
if (!report.passed) {
console.log('\nIssues:');
for (const issue of report.issues) {
console.log(`- ${issue.type}: ${JSON.stringify(issue.details)}`);
}
} else {
console.log('\n✅ All validations passed!');
}
return report;
}
runValidation().catch(console.error);
async function validateMedia(pages) {
const brokenMedia = [];
for (const page of pages) {
const mediaUrls = extractMediaUrls(page.fields);
for (const url of mediaUrls) {
const isValid = await checkUrl(url);
if (!isValid) {
brokenMedia.push({
page: page.slug,
url: url
});
}
}
}
return brokenMedia;
}
function extractMediaUrls(fields, urls = []) {
for (const [key, value] of Object.entries(fields)) {
// Check if images use ButterCMS CDN (recommended) or external URLs
if (typeof value === 'string' && value.includes('cdn.buttercms.com')) {
urls.push(value);
} else if (typeof value === 'object' && value !== null) {
extractMediaUrls(value, urls);
}
}
return urls;
}
async function checkUrl(url) {
try {
const response = await fetch(url, { method: 'HEAD' });
return response.ok;
} catch {
return false;
}
}
API integration tests
describe('ButterCMS API Integration', () => {
test('can fetch blog posts', async () => {
const response = await butter.post.list();
expect(response.data.data.length).toBeGreaterThan(0);
});
test('can fetch single page', async () => {
const response = await butter.page.retrieve('*', 'homepage');
expect(response.data.data).toBeDefined();
expect(response.data.data.fields.title).toBeDefined();
});
test('can fetch collection', async () => {
const response = await butter.content.retrieve(['blog_categories']);
expect(response.data.data.blog_categories.length).toBeGreaterThan(0);
});
test('localized content available', async () => {
const response = await butter.page.retrieve('*', 'homepage', { locale: 'de' });
expect(response.data.data.fields.title).toBeDefined();
});
});