Статья A.S Groups

WordPress media offload в Cloudflare R2: как вынести uploads и не сломать сайт

Медиа WordPress переносятся из uploads в Cloudflare R2 и CDN

Навигация по статье

Услуги A.S Groups

Нужен сайт, магазин или автоматизация?

Помогаю бизнесу запускать и дорабатывать WordPress-проекты: от посадочной страницы до WooCommerce, CRM и Telegram-уведомлений.

Обсудить проект Telegram
WordPress под ключ Лендинги, корпоративные сайты и структура под заявки. WooCommerce Интернет-магазины, каталог, оплата, доставка и интеграции. Доработка сайта Правки, скорость, формы, баги и развитие текущего проекта. CRM / Telegram / AI Автоматизация заявок, уведомлений и ручных процессов.

Когда медиатека WordPress разрастается до десятков гигабайт, хранить все изображения, документы и производные размеры на том же диске, где работает PHP и база данных, становится неудобно. Один из практичных вариантов — вынести файлы в объектное хранилище Cloudflare R2 и отдавать их через отдельный домен или CDN.

Но media offload — это не простая замена /wp-content/uploads/ на другой URL. WordPress хранит вложения как отдельные записи, сохраняет метаданные изображений, создаёт несколько размеров и строит responsive-разметку через srcset. Если перенести только оригиналы или переписать URL без проверки, часть изображений останется на локальном диске, а часть страниц получит 404.

Что именно хранит WordPress для одного изображения

После загрузки изображения WordPress создаёт attachment и записывает информацию о файле. Функция wp_generate_attachment_metadata() формирует метаданные и производные размеры, а wp_get_attachment_metadata() возвращает сохранённую структуру.

Поэтому у одного визуально «одного» изображения на диске может находиться несколько файлов: оригинал, thumbnail, medium, large и дополнительные размеры темы или плагинов. При переносе в R2 нужно копировать весь фактический набор, а не только файл, который виден в карточке медиатеки.

Почему нельзя просто удалить локальный uploads

Самая опасная последовательность выглядит так: загрузили несколько оригиналов в R2, поменяли базовый URL и сразу очистили локальный каталог. После этого обнаруживается, что часть старых записей использует уменьшенные размеры, WebP-варианты или прямые ссылки, которых в object storage нет.

Безопасная схема обратная: сначала инвентаризация, затем копирование всех байтов, проверка количества и контрольных сумм, переключение чтения на R2, тестирование и только потом решение о локальной очистке.

Какая архитектура обычно работает

Для большинства проектов достаточно разделить задачу на четыре слоя:

  • WordPress остаётся источником attachment ID и metadata;
  • новые файлы после загрузки копируются в R2;
  • публичные URL вложений указывают на custom domain перед R2;
  • старые uploads мигрируются отдельным контролируемым проходом.

Cloudflare указывает, что R2 bucket по умолчанию приватный. Для публичной раздачи файлов в production рекомендуется подключить custom domain: такой вариант позволяет использовать Cloudflare Cache и другие edge-возможности. Публичный r2.dev удобен для разработки, но не должен быть основой production-схемы.

Custom domain лучше зашить в архитектуру заранее

Допустим, файлы лежат в R2 с ключом 2026/09/photo.webp, а наружу должны открываться как https://img.example.com/2026/09/photo.webp. Если сразу использовать собственный домен, WordPress и контент не привязываются к служебному hostname провайдера.

Это упрощает будущую миграцию: при необходимости можно поменять origin или правила маршрутизации, сохранив публичные URL. Для SEO и кеша это обычно устойчивее, чем многократная массовая замена адресов внутри контента.

Как WordPress получает URL attachment

Стандартный URL вложения возвращает wp_get_attachment_url(). WordPress также предоставляет фильтр wp_get_attachment_url, через который можно контролируемо менять публичный адрес.

Для production-проекта важно не делать слепую строковую замену любого URL. Сначала нужно убедиться, что файл действительно находится в R2, и только после этого возвращать offload-адрес. Иначе одна пропущенная миграция превращается в битое изображение на сайте.

Не забываем про srcset

Responsive images — частая причина, почему «в браузере вроде всё открывается», а на другом устройстве возникает 404. WordPress формирует набор кандидатов через механизм, связанный с wp_calculate_image_srcset(). В srcset могут попасть несколько размеров одного attachment.

После offload нужно проверить HTML карточек, записей, WooCommerce-галерей и Elementor-блоков на разных ширинах экрана. Все кандидаты в srcset должны существовать по новому адресу и иметь корректный Content-Type.

Новые загрузки и историческая миграция — две разные задачи

Для новых файлов можно построить предсказуемый pipeline: WordPress принимает upload, генерирует производные размеры, после чего готовый набор копируется в R2. Историческая медиатека сложнее: там могут быть файлы из старых тем, ручные директории, уже удалённые attachment-записи и URL, вставленные прямо в HTML.

Поэтому перенос старого сайта лучше выполнять пакетами. Для каждого объекта полезно фиксировать source path, target key, размер и SHA-256. Если процесс прервётся, такой manifest позволяет продолжить с нужного места, не перезаписывая всё заново.

Что делать с прямыми URL внутри контента

Даже если attachment URL меняется динамически, старые статьи могут содержать абсолютный адрес вида https://site.ru/wp-content/uploads/2023/.... Особенно это характерно для импортированного контента, HTML-блоков, CSS background-image и некоторых page builder данных.

Перед массовой заменой нужно сделать резервную копию базы и определить, где именно хранятся значения. С сериализованными данными нельзя работать обычным SQL REPLACE: длина строк входит в сериализованную структуру. Для WordPress безопаснее использовать инструменты, которые понимают сериализацию, и затем выборочно проверить страницы.

R2 для публичных и приватных файлов

Не вся медиатека обязана быть публичной. Каталожные изображения обычно можно отдавать через custom domain, а закрытые документы, счета или файлы личного кабинета требуют другой модели доступа.

Для приватных объектов R2 поддерживает presigned URLs. Такие ссылки дают временный доступ к конкретной операции S3 API. Их нельзя считать постоянным публичным CDN-URL и нельзя встраивать в кэшируемые страницы на длительный срок.

Content-Type и кеш

При копировании байтов в object storage важно сохранить правильный MIME. Изображение WebP должно возвращаться как image/webp, CSS — как text/css, PDF — как application/pdf. Неверный Content-Type может повлиять на браузер, загрузку файла, кеш и индексацию.

После подключения custom domain отдельно проверяется политика кеша. Для файлов с уникальными неизменяемыми именами можно использовать длинный TTL, а для объектов, которые перезаписываются под тем же ключом, требуется аккуратная инвалидизация.

Удалять ли локальную копию

Это бизнес-решение, а не обязательная часть offload. Возможны три режима:

  • R2 как дополнительная копия, локальные файлы сохраняются;
  • после подтверждённой загрузки локальные байты удаляются;
  • часть типов файлов остаётся локально, остальные уходят в R2.

На первом этапе я обычно предпочитаю сохранить rollback: переключение URL уже даёт понимание реальной работы схемы, а физическое удаление можно выполнить после периода наблюдения и резервной копии.

Как проверить миграцию до переключения

Минимальный чек-лист выглядит так:

  1. получить список реальных файлов в uploads;
  2. скопировать оригиналы и производные размеры в R2 с сохранением структуры ключей;
  3. сверить количество, размер и при возможности хеши;
  4. проверить HTTP 200 и Content-Type для выборки объектов;
  5. протестировать страницы через временное правило или staging;
  6. проверить src, srcset, Open Graph и изображения WooCommerce;
  7. только после этого переключить production URL.

Типичные ошибки media offload

  • Перенесён только original. На мобильном браузер выбирает отсутствующий medium/large.
  • URL переписан до загрузки. WordPress начинает ссылаться на ещё не существующие объекты.
  • Неверный Content-Type. Байты правильные, но edge отвечает как generic binary.
  • Сразу удалён uploads. Откат становится аварийной операцией.
  • Не учтены старые абсолютные ссылки. Новые attachments работают, старые статьи продолжают ходить на локальный сервер.
  • Один bucket используется и для публичного, и для приватного контента без модели доступа.

Что проверить после запуска

После переключения я проверяю не только главную страницу. Нужна выборка старых и новых записей, категории, карточки WooCommerce, поиск, sitemap, Open Graph, lazy loading и мобильный srcset. В логах полезно искать 404 по домену медиа и сравнивать их с предыдущим периодом.

Если сайт использует плагины оптимизации изображений, генерацию WebP/AVIF или CDN-трансформации, их нужно включить в общую схему. Два независимых механизма переписывания URL часто конфликтуют.

Когда R2 имеет смысл

Offload особенно полезен, когда медиатека быстро растёт, несколько приложений должны читать одни и те же файлы, серверный диск ограничен или нужна отдельная edge-раздача. На маленьком сайте с несколькими сотнями картинок усложнять архитектуру только ради модного object storage не обязательно.

Если же задача реальная, перенос лучше проектировать как миграцию данных с контролем целостности, а не как установку одного плагина. Тогда WordPress сохраняет привычную медиатеку, а R2 становится надёжным слоем хранения и доставки.

Нужна настройка media offload без риска для действующего сайта?

В A.S Groups могу провести аудит текущего uploads, определить зависимые плагины и форматы, настроить Cloudflare R2 и custom domain, перенести исторические файлы с проверкой, а затем аккуратно переключить WordPress. Для общей архитектуры можно начать со страницы разработки WordPress или технической поддержки WordPress.

Официальные источники

Следующий шаг

Нужно решить похожую задачу?

Предложить аудит медиатеки WordPress, миграцию uploads в R2, настройку custom domain/CDN и безопасное внедрение без битых изображений.

Обсудить задачу

Источники

Обсуждение

Вопросы и комментарии

Можно уточнить детали статьи или поделиться своим опытом. Первый комментарий проходит проверку.

Оставить комментарий

Email не публикуется. Ссылки и HTML в тексте удаляются.

Мы используем приватную аналитику SlimStat, чтобы понимать, какие страницы полезны посетителям, и улучшать сайт. IP-адреса анонимизируются и хэшируются. Вы можете согласиться или отказаться от аналитики.
Cookies и конфиденциальность

Используем необходимые cookies, аналитику и данные форм, чтобы сайт работал корректно и заявки доходили.