go_router против auto_route против Navigator 2.0 во Flutter
go_router и auto_route оба работают поверх Navigator 2.0, поэтому реальный выбор -- это декларативная маршрутизация по URL против типизированных маршрутов на основе кодогенерации против ручной реализации Router API. Матрица решения с конфигурацией для каждого варианта и случаи, когда чистый Navigator всё ещё выигрывает.
Краткий ответ: для большинства приложений Flutter в 2026 году используйте go_router. Он поддерживается командой Flutter, это тот вариант, на который указывает официальная документация, и он превращает URL в экраны с наименьшими церемониями. Обращайтесь к auto_route, когда вам нужны проверяемые на этапе компиляции, строго типизированные вызовы навигации и вы готовы запускать генератор кода, чтобы их получить. Опускайтесь до чистого Navigator 2.0 (самостоятельно реализуя RouterDelegate и RouteInformationParser) только тогда, когда у вас действительно необычная модель навигации, которую не выражает ни один из пакетов, потому что вы подписываетесь на большое количество шаблонного кода. А если ваше приложение небольшое и не имеет требований к глубоким ссылкам или веб-URL, обычный императивный Navigator.push всё ещё остаётся вполне хорошим ответом.
Главное, что почти все понимают неправильно в этом сравнении, — это то, что три варианта воспринимаются как параллельные. Это не так. “Navigator 2.0” — это Router API фреймворка Flutter, и оба пакета, go_router и auto_route, построены поверх него. Так что выбор не в том, “пакет A против пакета B против фреймворка”; он в том, “на каком уровне я хочу писать свою маршрутизацию”. В этом посте используются go_router 17.3.0, auto_route 11.1.0, Flutter 3.44 stable и Dart 3.x.
Слоёный пирог: что на самом деле такое “Navigator 2.0”
Во Flutter бок о бок существуют два API навигации.
Navigator 1.0 — это императивный API, который вы уже знаете: Navigator.of(context).push(MaterialPageRoute(...)) и Navigator.pop(context). Вы добавляете и убираете экраны, как в стеке. Он прост и он работает, и для небольшого приложения это всё, что вам нужно.
Navigator 2.0, более точно называемый Router API, декларативен. Вместо императивного добавления вы передаёте фреймворку список объектов Page, которые описывают весь стек, и когда состояние вашего приложения меняется, вы передаёте ему новый список. Два элемента заставляют это работать: RouteInformationParser, который превращает входящий URL (RouteInformation) в ваш собственный тип данных, специфичный для приложения, и RouterDelegate, который читает эти данные плюс состояние вашего приложения и строит Navigator с правильными pages. Официальная документация по навигации и маршрутизации описывает именно это разделение.
Загвоздка в том, что написание корректного RouterDelegate и RouteInformationParser вручную — это большой объём работы. Вы отвечаете за разбор путей, поддержание списка страниц, обработку системной кнопки “назад”, синхронизацию URL браузера и восстановление глубоких ссылок. Документация Flutter прямо говорит об этом:
Если вы предпочитаете не использовать пакет маршрутизации и хотите полного контроля над навигацией и маршрутизацией в вашем приложении, переопределите
RouteInformationParserиRouterDelegate.
Это “если вы предпочитаете полный контроль” — показательно. Для всех остальных пакет маршрутизации оборачивает этот механизм. go_router и auto_route — оба являются такой обёрткой. Они потребляют Navigator 2.0; они не конкурируют с ним. Поэтому, когда кто-то спрашивает “go_router или Navigator 2.0?”, честный ответ таков: go_router и есть Navigator 2.0 с уже написанным за вас шаблонным кодом.
На что оптимизирован go_router: URL как источник истины
go_router публикуется проверенным издателем flutter.dev и поддерживается командой Flutter. Его модель ориентирована на URL: вы объявляете таблицу маршрутов из шаблонов путей, и навигация — это вопрос перехода к пути. Глубокие ссылки, веб-URL и внутренняя навигация в приложении — все разрешаются через одну и ту же таблицу, поэтому глубокая ссылка и нажатие кнопки приводят на один и тот же экран.
Минимальная настройка:
// Flutter 3.44, go_router 17.3.0
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'orders/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return OrderScreen(orderId: id);
},
),
],
),
],
);
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) =>
MaterialApp.router(routerConfig: router);
}
Навигация — это строка:
// go_router 17.3.0 - navigate by URL
context.go('/orders/42'); // replace the stack, land on order 42
context.push('/orders/42'); // push on top of the current stack
Сила здесь — это одновременно и острый край: цели навигации представляют собой строки. context.go('/orders/42') не проверяется компилятором. Опечатайтесь в пути или переименуйте маршрут, и вы узнаете об этом во время выполнения. У go_router есть ответ, типизированные маршруты через go_router_builder, который генерирует типизированные классы GoRouteData, чтобы вы могли вызывать OrderRoute(id: 42).go(context) вместо этого. Но это опциональная кодогенерация, прикрученная к пакету, чей идиоматический подход по умолчанию — строки, и на практике многие кодовые базы go_router так её и не включают.
go_router также владеет двумя возможностями, ради которых его на самом деле и берут. ShellRoute и StatefulShellRoute.indexedStack дают вам постоянную оболочку (нижнюю панель навигации, которая переживает переключения вкладок), а колбэк redirect верхнего уровня обрабатывает контроль доступа для всего дерева. Если вам нужно полное руководство по оболочкам и настройке глубоких ссылок, смотрите настройку вложенных маршрутов и глубоких ссылок с go_router.
Одно замечание о статусе, важное для решения на несколько лет вперёд: команда Flutter указывает go_router как функционально завершённый. Их заявление о дорожной карте гласит:
Этот пакет считается функционально завершённым. Основное внимание команды Flutter будет уделяться исправлению ошибок и обеспечению стабильности.
Читайте это как стабильность, а не заброшенность. Это означает, что API, который вы изучаете сегодня, вряд ли будет меняться под вами, и исправления ошибок по-прежнему выходят. Это также означает, что крупные новые возможности с большей вероятностью придут в виде пакетов сообщества, чем как основные функции go_router.
На что оптимизирован auto_route: типизированные маршруты из кодогенерации
auto_route 11.1.0 — это пакет сообщества от Milad Akarie. Он атакует именно ту слабость, что есть в go_router по умолчанию: строково-типизированную навигацию. Вы аннотируете каждую страницу с помощью @RoutePage(), запускаете build_runner, и он генерирует строго типизированный маршрут для каждого экрана. Вызовы навигации затем проверяются компилятором, а их аргументы типизированы.
// Flutter 3.44, auto_route 11.1.0
@RoutePage()
class OrderScreen extends StatelessWidget {
const OrderScreen({super.key, required this.orderId});
final int orderId; // a real int, not a string yanked from a path
@override
Widget build(BuildContext context) => const Placeholder();
}
@AutoRouterConfig()
class AppRouter extends RootStackRouter {
@override
List<AutoRoute> get routes => [
AutoRoute(page: HomeRoute.page, initial: true),
AutoRoute(page: OrderRoute.page, path: '/orders/:id'),
];
}
После dart run build_runner build навигация типизирована из конца в конец:
// auto_route 11.1.0 - typed navigation, checked by the compiler
context.router.push(OrderRoute(orderId: 42)); // orderId is an int
Переименуйте orderId, измените его тип или удалите маршрут, и код, который переходит к нему, перестанет компилироваться. Это и есть вся идея, и для большого приложения с десятками экранов и нетривиальными аргументами это реальная, ежедневная разница в производительности. Вам никогда не приходится сериализовать объект в строку запроса и вручную разбирать её обратно.
auto_route также поставляет первоклассные эквиваленты возможностей go_router. Вложенная навигация использует виджет AutoRouter в качестве выхода для дочерних маршрутов; навигация с вкладками использует AutoTabsRouter, который сохраняет состояние вкладок за пределами экрана так же, как это делает StatefulShellRoute. Контроль доступа использует стражи: вы наследуетесь от AutoRouteGuard, реализуете onNavigation и прикрепляете страж к каждому маршруту или глобально.
// auto_route 11.1.0 - a route guard
class AuthGuard extends AutoRouteGuard {
@override
void onNavigation(NavigationResolver resolver, StackRouter router) {
if (isSignedIn) {
resolver.next(true);
} else {
router.push(const LoginRoute());
}
}
}
Цена — это генератор кода. Вы добавляете auto_route_generator и build_runner как dev-зависимости, и каждый раз, когда вы добавляете или изменяете @RoutePage, вы регенерируете. В активной разработке вы запускаете build_runner watch. Это реальный налог: более медленные холодные сборки, сгенерированный файл .gr.dart в вашем дереве и периодическая необходимость удалять сгенерированный вывод и пересобирать, когда всё рассинхронизируется. Стоит ли типизированная навигация шага кодогенерации — это центральный компромисс во всём этом сравнении.
Решение в виде матрицы
| Аспект | go_router 17.3.0 | auto_route 11.1.0 | Чистый Navigator 2.0 |
|---|---|---|---|
| Мейнтейнер | Команда Flutter (flutter.dev) | Сообщество (Milad Akarie) | Фреймворк Flutter |
| Вызовы навигации | Строки (context.go('/x')), типизация опционально через builder | Типизированы, проверяются компилятором по умолчанию | Определяете вы |
| Кодогенерация | Нет (опционально для типизированных маршрутов) | Да (build_runner) | Нет |
| Глубокие ссылки / веб-URL | Встроено | Встроено | Реализуете вы |
| Постоянная оболочка / вкладки | StatefulShellRoute | AutoTabsRouter | Реализуете вы |
| Контроль доступа | Колбэк redirect | AutoRouteGuard | Реализуете вы |
| Шаблонный код | Мало | От малого до среднего (плюс сгенерированные файлы) | Много |
| Лучше всего, когда | Большинство приложений; веб; глубокие ссылки | Большое приложение, много типизированных аргументов, вам нравится codegen | По-настоящему нестандартная модель навигации |
Строки, которые должны определять ваш выбор, — это “вызовы навигации” и “кодогенерация”. Если проверяемая на этапе компиляции навигация достаточно важна, чтобы оправдать шаг сборки, — auto_route. Если вы предпочли бы не запускать генератор и вам комфортно со строковыми путями (или вы позже подключите go_router_builder), — go_router. Всё остальное оба пакета делают компетентно.
Когда чистый Navigator 2.0 — правильный выбор (редко)
Не хватайтесь за написанный вручную RouterDelegate просто чтобы чувствовать себя в контроле. Ситуации, где он действительно окупается, узки:
- Ваше состояние навигации вообще не отображается на дерево URL. Подумайте о редакторе графа узлов, о мастере, чей стек вычисляется из конечного автомата, или о приложении, где “стек” — это проекция какой-то другой структуры данных. Таблицы маршрутов пакетов предполагают дерево путей; если ваша модель не такова, вы можете больше бороться с пакетом, чем с фреймворком.
- Вы создаёте свой собственный пакет маршрутизации или абстракцию уровня фреймворка и вам нужны примитивы напрямую.
- Вам нужно поведение на уровне
Page/Navigator.pages, которое не предоставляет ни один пакет.
Для этих случаев чистый API даёт вам полный контроль над List<Page> и циклом разбора и восстановления. Для всего остального шаблонный код, который вы бы написали, — это шаблонный код, который пакеты уже написали, протестировали и поставляют. Указания документации Flutter однозначны: приложения с продвинутыми потребностями в навигации “должны использовать пакет маршрутизации, такой как go_router”, а чистый API представлен как аварийный выход для тех, кто явно хочет полного контроля.
Не забудьте: для небольшого приложения Navigator 1.0 всё ещё подходит
Есть режим отказа, при котором приложение из пяти экранов принимает пакет маршрутизации, генератор кода и абстракцию таблицы маршрутов, которые ему никогда не были нужны. Если у вашего приложения нет веб-цели, нет глубоких ссылок и нет требования реконструировать экран из URL, императивная навигация — это не устаревшая ошибка, это инструмент подходящего размера:
// Flutter 3.44 - imperative navigation, still correct for simple apps
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => const SecondScreen(),
),
);
Официальная документация явно благословляет это для “небольших приложений без сложных глубоких ссылок”. В тот момент, когда вы добавляете веб-сборку, ссылки для обмена или нижнюю навигацию, которая должна переживать переключения вкладок, переходите на go_router или auto_route. Не раньше.
Подводные камни, которые вручает вам каждый выбор
Несколько вещей кусают людей после того, как они уже определились:
- go_router: строки не проверяются. Переименование пути — это молчаливая поломка, пока вы не наткнётесь на неё во время выполнения. Либо примите
go_router_builderдля типизированных маршрутов пораньше, либо держите каждый путь в одном файле констант и никогда не вставляйте сырую строку в коде. - go_router:
contextпосле await. Декларативная навигация не освобождает вас от линтаuse_build_context_synchronously. Навигация с устаревшимBuildContextпослеasync-разрыва — это та же ошибка, что и всегда; смотрите безопасное использование BuildContext после await. - auto_route: рассинхронизация сгенерированного файла. После грязного слияния или обновления Dart SDK сгенерированный
.gr.dartможет устареть и выдавать сбивающие с толку ошибки. Исправление почти всегда — этоdart run build_runner build --delete-conflicting-outputs, а не разглядывание диффа. - auto_route: ещё один инструмент кодогенерации в вашем конвейере. Если вы уже запускаете Freezed, json_serializable или генератор Riverpod,
auto_route— это ещё одна вещь, которая должна оставаться синхронизированной. На CI забытый шагbuild_runnerпроваливает сборку, а не вашу машину. - Оба: глубоким ссылкам всё ещё нужна нативная конфигурация. Ни один пакет не может заставить Android и iOS передать URL во Flutter самостоятельно. Вам всё ещё нужно редактировать
AndroidManifest.xmlи права доступа iOS, чтобы зарегистрировать вашу схему или ссылки приложения. Пакет разрешает URL, как только он приходит; заставить его прийти — это работа на уровне платформы. - Миграция между ними не бесплатна. Таблицы маршрутов go_router и аннотированные классы auto_route структурно различаются. Переключение в середине проекта означает переписывание слоя маршрутизации, поэтому выбирайте с прицелом на двухлетний горизонт, а не на этот спринт.
Если вы всё ещё определяетесь со своим более широким стеком, а не только с маршрутизацией, то же напряжение “официальное-и-скучное против богатого-возможностями-от-сообщества” проявляется и в управлении состоянием, которое я разбираю в Provider против Riverpod против Bloc, и в самом выборе платформы в Flutter против React Native против MAUI.
Резюме, которое переживает контакт с реальным проектом: go_router — это выбор по умолчанию, потому что он официальный, стабильный и URL-нативный. auto_route оправдывает себя на больших приложениях, где проверяемая на этапе компиляции навигация стоит генератора кода. Чистый Navigator 2.0 — для редкого приложения, чья модель навигации не укладывается в таблицу маршрутов. А обычный Navigator.push всё ещё остаётся правильным ответом для небольшого приложения, которому ничего из этого не нужно. Выбирайте самый нижний уровень, который решает вашу реальную проблему, а не самый мощный.
Related
- Как настроить вложенные маршруты и глубокие ссылки с go_router во Flutter
- Как безопасно использовать BuildContext после await во Flutter
- Provider против Riverpod против Bloc для управления состоянием Flutter в 2026 году
- Flutter против React Native против MAUI для нового мобильного проекта в 2026 году
- Миграция приложения Flutter 2 на Flutter 3.x: чек-лист null safety
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.