Когда медиатека 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 уже даёт понимание реальной работы схемы, а физическое удаление можно выполнить после периода наблюдения и резервной копии.
Как проверить миграцию до переключения
Минимальный чек-лист выглядит так:
- получить список реальных файлов в uploads;
- скопировать оригиналы и производные размеры в R2 с сохранением структуры ключей;
- сверить количество, размер и при возможности хеши;
- проверить HTTP 200 и Content-Type для выборки объектов;
- протестировать страницы через временное правило или staging;
- проверить
src,srcset, Open Graph и изображения WooCommerce; - только после этого переключить 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.
Обсуждение
Вопросы и комментарии
Можно уточнить детали статьи или поделиться своим опытом. Первый комментарий проходит проверку.