← назад к разделу

Web-first подход — это React + TypeScript, упакованные в PWA или Capacitor. Такой стек закрывает большинство задач одним набором инструментов, без разделения на iOS- и Android-команды. Но у него есть честный предел: WebView — это браузер внутри приложения, и у браузера есть ограничения.

Эта статья о том, где этот предел проходит, и что делать, когда в него упёрся.

Где заканчиваются возможности WebView

WebView не хуже браузера — он им и является. Всё, что умеет браузер, WebView умеет тоже. Проблема возникает там, где у браузера заканчиваются права или мощности.

Тяжёлая графика и анимация. 3D-сцены, игровые движки, интерфейс с десятками одновременных переходов на 120 Гц — WebView упирается в потолок рендеринга раньше, чем натив. Разрыв ощутим именно при сложной анимации, а не при обычных переходах между экранами.

Сложные жесты. Многопальцевые манипуляции, инерционные взаимодействия, точная обработка касаний — браузерная модель событий немного медленнее. На большинстве экранов разницы не заметно, но в редакторах и карточных играх это начинает мешать.

Системные расширения и виджеты. Виджеты на домашнем экране, Apple Watch, CarPlay, расширения клавиатуры — это платформенные механизмы, которые вообще не запускают WebView. Сюда браузерный код добраться не может.

Фоновые задачи. Надёжная работа после закрытия приложения — геотрекинг, долгая синхронизация, загрузка больших файлов. Браузер останавливает фоновый код, когда вкладка уходит. Нативное приложение этого ограничения не имеет.

Дополненная реальность и тонкая работа с камерой. Ручное управление параметрами камеры, покадровая обработка потока, AR — браузерные API здесь либо неполны, либо недоступны совсем.

Пиковые вычисления на устройстве. Обработка изображений в реальном времени, сложные алгоритмы — там, где важна каждая миллисекунда, JavaScript проигрывает машинному коду.

Через нативные плагины Capacitor часть этого удаётся закрыть. Но если вся суть продукта — именно одна из этих задач, мост через WebView становится постоянным источником трений.

React Native — следующая ступень для React-команды

Если упёрся, но вся команда знает React, ближайший шаг — React Native. Логика и интерфейс пишутся на JavaScript/TypeScript, как и раньше. Но React Native не рисует HTML в WebView — он рендерит настоящие нативные виджеты платформы: UIView на iOS, android.view.View на Android. Отсюда нативный отклик и нативный внешний вид.

import { View, Text, Pressable } from "react-native";

export function Counter({ value, onInc }: { value: number; onInc: () => void }) {
  return (
    <View>
      <Text>{value}</Text>
      <Pressable onPress={onInc}>
        <Text>Увеличить</Text>
      </Pressable>
    </View>
  );
}

Компоненты выглядят почти как в React для веба — View вместо div, Text вместо span. Хуки, управление состоянием, загрузка данных — переиспользуются без изменений. Меняется слой представления и доступ к платформе.

Это не бесплатно: появляется отдельная сборка, нативные зависимости, свои специфические проблемы. Но порог входа для React-команды ниже, чем у любой другой нативной альтернативы.

Flutter — когда нужна своя графика

Flutter решает ту же задачу иначе. Язык — Dart (не JavaScript), а UI рисует собственный движок рендеринга через GPU (Metal на iOS, Vulkan/OpenGL на Android). Flutter не делегирует отрисовку платформенным виджетам — он рисует сам.

Отсюда два следствия: полная визуальная идентичность между iOS и Android, и стабильно высокая производительность анимации. Обратная сторона: если нужно выглядеть как родное iOS- или Android-приложение, это придётся воспроизводить вручную.

ElevatedButton(
  onPressed: onInc,
  child: Text('$value'),
)

Для фронтенд-команды Flutter — это полная смена стека: новый язык, новые инструменты, новый пакетный менеджер. Технически зрелый выбор для продуктов с тяжёлой графикой и требованием пиксель-в-пиксель единства на обеих платформах. Но переиспользования React-навыков здесь нет — это инвестиция в новый стек.

Полный натив: Swift и Kotlin

Крайняя ступень — отдельные приложения на родных языках: Swift для iOS, Kotlin для Android. Это максимум возможностей и контроля: любой системный API в день выхода, пиковая производительность, идеальная интеграция с платформой.

struct CounterView: View {
    @State private var value = 0
    var body: some View {
        Button("\(value)") { value += 1 }
    }
}

Цена — две раздельные кодовые базы, две команды, самая высокая стоимость разработки и поддержки. Полный натив оправдан, когда нативность — не деталь реализации, а суть продукта: профессиональная камера, AR-платформа, обработка требовательная к железу, глубокие системные расширения. Для большинства продуктов это избыточно, но там где он нужен — ничто другое задачу не закрывает.

Как выбирать

Выбор зависит не от моды на технологию, а от трёх вопросов:

Насколько критичен нативный отклик. Если расхождение в несколько кадров или не-родной feel убивают продукт — WebView не подойдёт. Если пользователь этого не заметит — вполне подойдёт.

Есть ли конкретные нативные требования. AR, фоновый геотрекинг, виджеты на домашнем экране, профессиональная камера — конкретная возможность, которой web-слой не даёт, перевешивает удобство единого стека.

Бюджет и состав команды. Фронтенд-команда и сжатые сроки тянут к web-first или React Native. Большой бюджет и требования, для которых нужен прямой доступ к платформе, делают полный натив реалистичным выбором.

Здравая последовательность по росту стоимости и контроля: PWA → Capacitor (с упаковкой через WKWebView на iOS и TWA на Android) → React Native → Flutter → полный натив. Двигаться вправо стоит только тогда, когда упёрся в конкретный потолок — не превентивно.

Коротко

  • WebView — это браузер внутри приложения; его ограничения наступают при тяжёлой графике, системных расширениях, фоновых задачах и тонкой работе с камерой.
  • React Native — ближайший шаг для React-команды: тот же язык, но нативные виджеты вместо HTML. Переиспользование навыков максимальное.
  • Flutter — собственный GPU-движок, полное единство между платформами, стабильная анимация. Требует перехода на Dart и смены всего стека.
  • Полный натив (Swift + Kotlin) — максимум возможностей, но две кодовые базы и две команды. Оправдан, когда нативность — суть продукта.
  • Переходить на более сложный уровень стоит только под конкретное требование, которое дешевле не закрыть.

Что почитать дальше

  • Нативные API через Capacitor — как добраться до платформы без полного перехода на натив.
  • WKWebView на iOS — как работает упаковка веб-приложения на iOS.
  • TWA на Android — аналог для Android.
  • PWA: офлайн и уведомления — что можно сделать вообще без нативной упаковки.