> ## 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.

# Post-migration validation

> Verify that your migrated content is complete, accurate, and performant with checklists for content integrity, media, SEO, and automated validation scripts.

## Validation overview

```mermaid theme={null}
flowchart LR
    subgraph Phase1["Content Check"]
        C1["Completeness"]
        C2["Accuracy"]
        C3["Formatting"]
        C1 --> C2 --> C3
    end
    subgraph Phase2["Functional Test"]
        F1["Links work"]
        F2["Media loads"]
        F3["References"]
        F1 --> F2 --> F3
    end
    subgraph Phase3["Performance & SEO"]
        P1["Page speed"]
        P2["Rankings"]
        P3["Indexing"]
        P1 --> P2 --> P3
    end
    Phase1 --> Phase2 --> Phase3
```

## Content validation checklist

### Completeness check

* [ ] **All content migrated** - Compare counts with source system
* [ ] **No missing pages** - Verify every page from inventory exists
* [ ] **No missing media** - Confirm all images and files transferred
* [ ] **Metadata preserved** - Check publish dates, authors, categories
* [ ] **Relationships intact** - Verify references and links work

### Quality scan

* [ ] **Visual inspection** - Scroll through migrated content
* [ ] **Formatting correct** - Headers, paragraphs, lists render properly
* [ ] **No broken formatting** - No HTML tags showing as text
* [ ] **Character encoding** - Special characters display correctly
* [ ] **No placeholder content** - All fields have real content

### Link validation

* [ ] **Internal links work** - Links between pages resolve correctly
* [ ] **External links work** - Links to external sites are valid
* [ ] **No broken links** - Use a link checker tool
* [ ] **Correct destinations** - Links go to intended pages
* [ ] **Anchor links work** - In-page navigation functions

**Possible link outcomes:**

1. ✅ Link works correctly
2. ⚠️ Link to unmigrated content (fix before go-live)
3. ⚠️ Link to source system (update to new URL)
4. ❌ Broken link (must fix)

## Media validation

### Image checklist

* [ ] **All images load** - No broken image icons
* [ ] **Correct image shown** - Right images on right pages
* [ ] **Image quality acceptable** - No blurry or distorted images
* [ ] **Alt text preserved** - Accessibility text migrated
* [ ] **CDN URLs used** - Images served from ButterCMS CDN

## SEO validation

### SEO checklist

* [ ] **Meta titles preserved** - SEO titles migrated correctly
* [ ] **Meta descriptions preserved** - Descriptions migrated correctly
* [ ] **URL structure maintained** - Or proper redirects in place
* [ ] **Canonical URLs correct** - Pointing to right pages
* [ ] **No duplicate content** - Old URLs retired or redirected
* [ ] **Sitemap updated** - Reflects new URL structure
* [ ] **Robots.txt correct** - Allows crawling of new content

### 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

```javascript theme={null}
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;
}
```

## Performance validation

### Performance checklist

* [ ] **Page load time** - Compare to baseline
* [ ] **Time to First Byte** - API response times
* [ ] **Core Web Vitals** - LCP, FID, CLS metrics
* [ ] **Mobile performance** - Test on mobile devices
* [ ] **CDN working** - Images served from CDN

### Performance comparison

| 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:

1. **Content team review** - Have editors verify content accuracy
2. **Developer review** - Verify API responses match expectations
3. **Stakeholder review** - Business owners approve key pages
4. **QA review** - Systematic testing of all functionality

## Comparison report template

```markdown theme={null}
# 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)

1. Point DNS back to old system
2. Document all issues found
3. Plan fixes before re-attempting

### Partial rollback

1. Identify problematic content
2. Restore from backup or re-import
3. Verify fixes before full cutover

### Data recovery

1. Use ButterCMS revision history if available
2. Re-import from source backup
3. Contact ButterCMS support for assistance

## Automated validation

### Automated validation script

```javascript theme={null}
// 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;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);
```

### Media validation script

```javascript theme={null}
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

```javascript theme={null}
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();
  });
});
```
