Міграція контенту при редизайні сайту
Редизайн майже завжди супроводжується змінами структури контенту: URL-схеми, ієрархія сторінок, формати полів. Міграція контенту при редизайні відрізняється від CMS-міграції тим, що платформа може залишитися тією ж, але змінюється семантика даних.
Аудит контенту перед редизайном
# Сканування всіх URL сайту
npx screaming-frog-seo-spider --crawl https://mysite.com --headless \
--export-tabs "Crawl Overview,Internal,Response Codes" \
--output-folder ./crawl-results
# Або через sitemap
curl https://mysite.com/sitemap.xml | grep '<loc>' | sed 's/<[^>]*>//g' > urls.txt
wc -l urls.txt
Для кожної сторінки записуємо:
- URL (старий та новий)
- Тип контенту
- Унікальні елементи (відео, галереї, форми)
- Важливість для SEO (трафік з Google Analytics)
Маппінг URL
// scripts/generate-redirects.ts
// Генеруємо файл редиректів на основі маппінгу
const urlMapping: Record<string, string> = {
'/blog/category/web-development': '/web-development',
'/services/web-design': '/services/design',
'/about-us/team': '/team',
'/portfolio': '/work',
};
// Для Next.js
const redirects = Object.entries(urlMapping).map(([source, destination]) => ({
source,
destination,
permanent: true,
}));
// Для Nginx
const nginxRules = Object.entries(urlMapping)
.map(([from, to]) => `rewrite ^${from}$ ${to} permanent;`)
.join('\n');
Трансформація структури контенту
Приклад: реструктуризація блогу з додаванням нових полів:
// Було: простий пост з body
// Стало: пост з intro + body (StreamField) + callout + related_posts
async function transformPost(oldPost: OldPost): Promise<NewPost> {
return {
title: oldPost.title,
slug: oldPost.slug,
intro: extractIntro(oldPost.body), // перший параграф
body: convertToStreamField(oldPost.body),
publishedAt: oldPost.date,
author: await findOrCreateAuthor(oldPost.authorName),
tags: oldPost.tags,
seoTitle: oldPost.seoTitle || oldPost.title,
seoDescription: oldPost.seoDescription || extractIntro(oldPost.body, 160),
};
}
function extractIntro(html: string, maxChars = 250): string {
const firstParagraph = html.match(/<p[^>]*>(.*?)<\/p>/s)?.[1] ?? '';
const text = firstParagraph.replace(/<[^>]*>/g, '');
return text.slice(0, maxChars).trim();
}
Обробка медіафайлів
При переходу на інше сховище потрібно оновити всі URL у контенті:
async function updateMediaUrls(content: string, urlMap: Map<string, string>): string {
return content.replace(
/https:\/\/old-domain\.com\/wp-content\/uploads\/([^\s"']+)/g,
(match, path) => urlMap.get(path) || `https://cdn.newdomain.com/${path}`
);
}
// Завантажуємо медіафайли і створюємо маппінг
async function migrateMedia(oldUrls: string[]) {
const urlMap = new Map<string, string>();
for (const url of oldUrls) {
const buffer = await downloadFile(url);
const key = url.split('/uploads/')[1];
const newUrl = await uploadToS3(buffer, key);
urlMap.set(key, newUrl);
}
return urlMap;
}
Паралельний запуск при редизайні
- Заморозка контенту — за 2 тижні до старту остаточної міграції
- Migrate-and-verify — мігруємо дані, верифікуємо ключові сторінки
- Parallel run — staging з новим дизайном та реальним контентом
- DNS cutover — перемикання у період низького трафіку
- Post-launch crawl — перевірка редиректів та 404
Валідація результату
# Перевіряємо, що всі старі URL або повертають 301, або 200
while IFS= read -r url; do
status=$(curl -s -o /dev/null -w "%{http_code}" "$url")
echo "$status $url"
done < old-urls.txt | grep -v "^301\|^200" > broken.txt
Міграція контенту при редизайні середнього сайту (100–500 сторінок) — 2–4 тижні.







