Разработка CMS для контента мобильного приложения
Мобильное приложение с хардкодным контентом — это постоянные релизы ради текстовых правок. Изменить акционный баннер, обновить цены, добавить новую категорию — каждый раз ревью в App Store и Google Play, 24-72 часа ожидания. CMS (система управления контентом) выносит редактируемый контент за пределы приложения: маркетолог меняет текст в браузере, пользователи видят изменения через минуту.
Что управляется через CMS
Не весь контент имеет смысл выносить в CMS. Правило простое: если это меняется реже раза в квартал и требует релиза — оставить в коде. Если меняется часто и нетехническими сотрудниками — CMS.
Типично выносят:
- Баннеры и промо-блоки на главном экране
- Онбординг и сплэш-контент
- Тексты пуш-уведомлений и in-app сообщений
- Каталог продуктов/услуг (если не из ERP)
- Статические страницы (FAQ, условия, контакты)
- Настройки feature flags (показывать/скрывать разделы)
- Локализованный контент (разные тексты для разных регионов)
Выбор подхода
Headless CMS — отдаёт контент через API, без фронтенда. Contentful, Strapi, Directus, Sanity. Мобильное приложение обращается к API и рендерит данные своим UI.
Backend + Admin Panel — кастомная CMS на собственном бэкенде. Laravel Nova, Rails ActiveAdmin, кастомный React SPA. Полный контроль над структурой данных и логикой.
Firebase Remote Config — для feature flags и простых конфигурационных данных. Не для структурированного контента с изображениями.
Strapi и Directus — опенсорс, self-hosted, без платы за контент API. Contentful — платный hosted-сервис, быстрый старт, есть CDN.
API-контракт для мобильного клиента
CMS должна отдавать данные в формате, который мобильный клиент может рендерить без дополнительной трансформации. Типичная ошибка — CMS отдаёт произвольную структуру, мобильный разработчик тратит дни на парсинг.
Контракт для экрана главной страницы:
{
"homeScreen": {
"version": 12,
"updatedAt": "2025-03-15T10:30:00Z",
"banners": [
{
"id": "banner-spring-sale",
"imageUrl": "https://cdn.example.com/banners/spring-sale-2x.webp",
"imageUrl2x": "https://cdn.example.com/banners/spring-sale-3x.webp",
"deepLink": "myapp://promo/spring-sale",
"title": "Весенняя распродажа",
"subtitle": "До -40% на всё",
"backgroundColor": "#FF5733",
"textColor": "#FFFFFF",
"expiresAt": "2025-04-01T00:00:00Z",
"targetAudience": "all"
}
],
"featuredCategories": [...],
"announcements": [...]
}
}
Поле version — для клиентского кэша. Если версия не изменилась с прошлого запроса, можно вернуть 304 или использовать кэш без повторной загрузки.
Клиентская сторона: кэширование и обновление
class CmsRepository(
private val api: CmsApi,
private val dao: CmsContentDao,
private val prefs: CmsPreferences
) {
fun observeHomeScreen(): Flow<HomeScreenContent> = flow {
// Сразу из кэша
val cached = dao.getHomeScreen()
if (cached != null) emit(cached.toContent())
// Проверяем обновление
try {
val response = api.getHomeScreen(
ifNoneMatch = prefs.homeScreenEtag
)
if (response.code() == 304) return@flow // кэш актуален
val fresh = response.body()!!
dao.upsertHomeScreen(fresh.toEntity())
prefs.homeScreenEtag = response.headers()["ETag"]
emit(fresh)
} catch (e: IOException) {
// Сеть недоступна — кэш уже отдан
}
}
}
ETag-кэширование: сервер генерирует хэш от контента, клиент присылает его в If-None-Match. Если контент не изменился — 304 без тела. При 100K DAU и экране, загружающемся 50 раз в день — это значительная экономия трафика и нагрузки на сервер.
Локализация контента
CMS должна поддерживать несколько локалей. Strapi v5 и Contentful имеют встроенную поддержку i18n — каждое поле может иметь разные значения для разных языков.
API принимает Accept-Language: ru-RU в заголовке и отдаёт контент на нужном языке. Мобильный клиент передаёт локаль пользователя:
// iOS
var request = URLRequest(url: cmsEndpoint)
request.setValue(Locale.current.identifier, forHTTPHeaderField: "Accept-Language")
Feature Flags через CMS
CMS — удобное место для feature flags. Не как A/B тест (для этого лучше Firebase Remote Config или Unleash), а для управления видимостью разделов:
{
"featureFlags": {
"showCryptoPayments": true,
"enableDarkMode": true,
"referralProgramEnabled": false,
"maxCartItems": 50,
"minimumAppVersion": "2.4.0"
}
}
minimumAppVersion — CMS может заблокировать старые версии приложения мягко: показать баннер «Обновите приложение», не пуская к новым функциям. Без релиза.
Admin UI для нетехнических редакторов
Кастомный admin panel должен быть максимально простым для редакторов без технического бэкграунда: WYSIWYG для текстов, drag-and-drop для порядка баннеров, предпросмотр как в приложении, история изменений с возможностью отката.
Strapi из коробки даёт хороший admin UI. Для кастомных сценариев — React с дополнительным дизайном.
Разработка headless CMS на Strapi или кастомном бэкенде с мобильным API, кэшированием и admin UI: 3–6 недель. Стоимость рассчитывается индивидуально.







