# Što ćete izgraditi i zašto je to važno#
Ovaj vodič prikazuje navigacijsku postavu spremnu za produkciju koristeći go_router s deep linkovima, auth guardovima, ugniježđenim rutama, ShellRoute layoutom i prvoklasnom podrškom za web. Ciljana ključna riječ, Flutter go_router deep links, više je od marketinške fraze: deep linkovi vas prisiljavaju da navigaciju tretirate kao arhitekturni problem, a ne UI detalj.
U stvarnoj aplikaciji rute nisu samo ekrani. One kodiraju dozvole, onboarding, granice značajki, analitiku i stanje vođeno URL-om poput tabova, filtera i odabranih entiteta.
Ako planirate universal linkove i app linkove end-to-end, pročitajte naš poseban pregled deep linkinga: Flutter deep linking: universal links, app links, routing.
# Preduvjeti#
| Zahtjev | Verzija | Napomene |
|---|---|---|
| Flutter | 3.22+ | Podržani web i mobilne platforme |
| Dart | 3.4+ | Odgovara trenutnom Flutter stable |
| go_router | 14+ | API je usklađen sa ShellRoute i imenovanim rutama |
| Rješenje za stanje | Bilo koje | Riverpod, Bloc, Provider, itd. |
| Postavke deep linkova | iOS i Android | Associated domains i intent filteri |
Dizajn routinga snažno je povezan sa strukturom projekta. Ako vaš codebase raste, razmotrite feature-first raspored i eksplicitne granice: Flutter app architecture: clean architecture, feature-first.
# Koncepti routinga koji stvarno znače u produkciji#
Deklarativne rute s determinističkim URL-ovima#
Deep linking zahtijeva da se svaki bitan ekran može prikazati kao URL lokacija. Ako se do UI-ja može doći samo navigacijom u memoriji, nije moguće linkati, dijeliti i teže ga je testirati.
Dobro pravilo je: ako korisnik može doći na nešto iz notifikacije, e-maila, QR koda ili preglednika, treba stabilnu rutu i predvidljivu priču o restoreu.
Redirecti nisu samo provjere autentikacije#
Logika preusmjeravanja obično evoluira od jednostavne „login rampe” u policy engine:
- onboarding dovršen naspram nedovršen
- ekrani za prisilnu nadogradnju
- maintenance mode
- pristup temeljen na rolama
- feature flagovi i A B eksperimenti
- rukovanje isteklih sesija pri cold startu kroz deep link
go_router može sve to odraditi, ali samo ako redirecte dizajnirate kao deterministička pravila s minimalnim nuspojavama.
Ugniježđena navigacija je UX zahtjev, a ne stil koda#
Donja navigacija na mobilnim aplikacijama obično zahtijeva neovisne navigacijske stackove po tabu. Ako to ne napravite, korisnici gube povijest (back stack) pri prebacivanju tabova, što djeluje pokvareno.
ShellRoute to rješava tako da pruža zajednički okvir i ugniježđene navigatore.
ℹ️ Napomena: Flutter navigacija se može ručno složiti s Navigator 2.0, ali go_router standardizira uobičajene obrasce i smanjuje custom boilerplate. Kompromis je da morate poštovati njegov mentalni model: rute predstavljaju stanje.
# go_router postava spremna za produkciju#
Ova postava podržava:
- deep linkove na mobilnim platformama i webu
- guardove za autentikaciju i onboarding
- ShellRoute s donjom navigacijom i neovisnim stackovima
- stanje vođeno URL-om putem query parametara
- nastavak nakon logina prema originalno ciljanoj deep poveznici
Definirajte model ruta aplikacije#
Napravite jedan izvor istine za nazive ruta i putanje. Time smanjujete tipfelere i refaktori su sigurniji.
// app_routes.dart
class AppRoute {
static const splash = 'splash';
static const login = 'login';
static const home = 'home';
static const search = 'search';
static const profile = 'profile';
static const product = 'product';
static const cart = 'cart';
}
class AppPath {
static const splash = '/';
static const login = '/login';
static const home = '/home';
static const search = '/search';
static const profile = '/profile';
static const product = '/p/:id';
static const cart = '/cart';
}Osigurajte auth i stanje aplikacije za redirecte#
Redirect treba sinkron pristup stanju. U praksi auth modelirate kao state objekt koji izlaže i trenutni status i Listenable kako bi se redirect ponovno pokretao.
Minimalan pristup je ChangeNotifier wrapper koji zrcali vaš stvarni auth state iz Riverpoda ili Bloca.
// app_session.dart
import 'package:flutter/foundation.dart';
enum AuthStatus { unknown, unauthenticated, authenticated }
class AppSession extends ChangeNotifier {
AuthStatus _status = AuthStatus.unknown;
bool _onboardingComplete = false;
AuthStatus get status => _status;
bool get onboardingComplete => _onboardingComplete;
void setAuthenticated({required bool onboardingComplete}) {
_status = AuthStatus.authenticated;
_onboardingComplete = onboardingComplete;
notifyListeners();
}
void setUnauthenticated() {
_status = AuthStatus.unauthenticated;
notifyListeners();
}
void setUnknown() {
_status = AuthStatus.unknown;
notifyListeners();
}
}Ako birate state management u 2026., uskladite routing refresh sa svojim pristupom stanju: Flutter state management 2026.
Implementirajte redirect logiku s nastavkom nakon logina#
Najčešći produkcijski zahtjev je nastavak deep linka nakon logina. Možete spremiti pending lokaciju kada preusmjeravate na login.
Neka bude jednostavno: spremite jedan string u session state ili u zaseban koordinator.
// pending_redirect.dart
class PendingRedirect {
String? _location;
String? consume() {
final value = _location;
_location = null;
return value;
}
void set(String location) {
_location = location;
}
}Sada povežite go_router s:
initialLocationza deterministički startuprefreshListenablekako bi se redirect ponovno evaluirao kad se auth promijeniredirectkoji provodi pravila i sprema pending lokacije
// app_router.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'app_routes.dart';
import 'app_session.dart';
import 'pending_redirect.dart';
GoRouter createRouter({
required AppSession session,
required PendingRedirect pending,
}) {
return GoRouter(
initialLocation: AppPath.splash,
refreshListenable: session,
redirect: (context, state) {
final location = state.uri.toString();
final isLoggingIn = state.matchedLocation == AppPath.login;
final isSplash = state.matchedLocation == AppPath.splash;
// Wait for auth bootstrap, token refresh, etc.
if (session.status == AuthStatus.unknown) {
return isSplash ? null : AppPath.splash;
}
// Unauthenticated: send to login, remember intent.
if (session.status == AuthStatus.unauthenticated) {
if (!isLoggingIn) {
pending.set(location);
return AppPath.login;
}
return null;
}
// Authenticated: prevent going back to login.
if (session.status == AuthStatus.authenticated && isLoggingIn) {
final next = pending.consume();
return next ?? AppPath.home;
}
// Add onboarding gates here if needed.
// Example: if onboarding required, redirect to /onboarding.
// Splash should not remain visible when authenticated.
if (isSplash) return AppPath.home;
return null;
},
routes: _routes(session: session),
);
}⚠️ Upozorenje: Redirect mora biti brz i bez nuspojava, osim trivialnog in-memory bookkeepinga. Izbjegavajte pozivanje API-ja u redirectu. Ako trebate refresh tokena, odradite to tijekom bootstrapanja aplikacije i držite
AuthStatus.unknowndok se ne razriješi.
# ShellRoute za donju navigaciju s neovisnim stackovima#
Standardan obrazac je 3 do 5 tabova, svaki sa svojim ugniježđenim stackom. ShellRoute daje zajednički layout, dok svaka grana zadržava vlastitu povijest.
Primjer stabla ruta#
| UX element | Strategija ruta | Zašto je bitno |
|---|---|---|
| Donja navigacija | ShellRoute | Trajni UI okvir |
| Stackovi tabova | Ugniježđeni Navigator po grani | Čuva povijest po tabu |
| Detalji proizvoda | Top-level ruta ili ugniježđeno pod tab | Stabilnost deep linka i ponašanje back gumba |
| Modalni flowovi | parentNavigatorKey | Osigurava ispravan overlay stack |
Implementirajte ShellRoute s navigator keyevima#
Tipično imate:
- root navigator za full-screen rute
- po jedan navigator za svaku tab granu
// shell_keys.dart
import 'package:flutter/material.dart';
final rootNavigatorKey = GlobalKey<NavigatorState>();
final homeNavigatorKey = GlobalKey<NavigatorState>();
final searchNavigatorKey = GlobalKey<NavigatorState>();
final profileNavigatorKey = GlobalKey<NavigatorState>();Sada definirajte rute. Shell omata tri glavne grane, a i dalje možete gurati full-screen rute iznad shella.
// routes.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'app_routes.dart';
import 'shell_keys.dart';
List<RouteBase> _routes({required dynamic session}) {
return [
GoRoute(
name: AppRoute.splash,
path: AppPath.splash,
parentNavigatorKey: rootNavigatorKey,
builder: (context, state) => const SplashScreen(),
),
GoRoute(
name: AppRoute.login,
path: AppPath.login,
parentNavigatorKey: rootNavigatorKey,
builder: (context, state) => const LoginScreen(),
),
ShellRoute(
navigatorKey: rootNavigatorKey,
builder: (context, state, child) {
return AppShell(child: child);
},
routes: [
GoRoute(
name: AppRoute.home,
path: AppPath.home,
parentNavigatorKey: homeNavigatorKey,
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
name: AppRoute.product,
path: 'p/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return ProductScreen(productId: id);
},
),
],
),
GoRoute(
name: AppRoute.search,
path: AppPath.search,
parentNavigatorKey: searchNavigatorKey,
builder: (context, state) => const SearchScreen(),
),
GoRoute(
name: AppRoute.profile,
path: AppPath.profile,
parentNavigatorKey: profileNavigatorKey,
builder: (context, state) => const ProfileScreen(),
),
],
),
GoRoute(
name: AppRoute.cart,
path: AppPath.cart,
parentNavigatorKey: rootNavigatorKey,
builder: (context, state) => const CartScreen(),
),
];
}Ovo vam daje:
/homes ugniježđenim/home/p/123/searchi/profilekao odvojene top-level tabove/cartkao full-screen stranicu iznad tabova
💡 Savjet: Cross-cutting full-screen flowove poput checkouta, paywalla i košarice stavite iznad shella koristeći
parentNavigatorKey: rootNavigatorKey. Time izbjegavate nezgrapno ponašanje back gumba kad se modal treba zatvoriti natrag na trenutni tab.
# Stanje vođeno URL-om kroz query parametre#
Deep linkovi nisu samo o tome koji se ekran otvara. Radi se i o tome u kojem je stanju taj ekran.
Primjeri koji čine linkove korisnima:
/search?q=ipad&sort=price_desc/home?tab=offers/profile?section=billing
Sigurno čitanje query parametara#
Query parametre tretirajte kao nepouzdan input. Validirajte ih i postavite zadane vrijednosti.
// search_screen.dart (snippet)
class SearchArgs {
final String query;
final String sort;
SearchArgs({required this.query, required this.sort});
factory SearchArgs.fromUri(Uri uri) {
final q = uri.queryParameters['q'] ?? '';
final sort = uri.queryParameters['sort'] ?? 'relevance';
return SearchArgs(query: q, sort: sort);
}
}U route builderu:
builder: (context, state) {
final args = SearchArgs.fromUri(state.uri);
return SearchScreen(initialQuery: args.query, initialSort: args.sort);
},Ažuriranje URL-a kad se stanje promijeni#
Kad korisnik promijeni filtere, ažurirajte lokaciju rute. To čini back gumb smislenim i omogućuje dijeljive URL-ove na webu.
// in SearchScreen
void updateSearch(BuildContext context, {required String q, required String sort}) {
final uri = Uri(path: AppPath.search, queryParameters: {
'q': q,
'sort': sort,
});
context.go(uri.toString());
}🎯 Ključna poruka: Ako je stanje korisniku važno, obično treba živjeti u URL-u. Time smanjujete skriveno stanje, poboljšavate ponašanje back gumba i činite deep linkove stabilnima na mobilnim platformama i webu.
# Deep linkovi na iOS-u, Androidu i webu#
go_router rješava routing nakon što aplikacija primi link. Postavke platforme i dalje su važne.
Odgovornosti platforme naspram odgovornosti go_routera#
| Sloj | Odgovornost | Tipične zamke |
|---|---|---|
| iOS universal links | Associated domains, apple-app-site-association | Pogrešan content-type, cacheiranje, nedostajuće putanje |
| Android app links | intent filteri, assetlinks.json, SHA-256 fingerprinti | Debug keystore nije dodan, više build varijanti |
| Flutter go_router | Parsiranje lokacije i render stabla ruta | Redirect petlje, nedostajuća validacija parametara |
| Web | URL strategija i server rewrites | 404 na refresh bez rewrite pravila |
Za kompletne detalje postavki platforme, koristite end-to-end vodič: Flutter deep linking: universal links, app links, routing.
Podrška za web: server rewrites nisu opcionalni#
Na Flutter webu korisnici mogu osvježiti /home/p/123. Ako hosting ne radi rewrite nepoznatih putanja na index.html, dobit ćete 404 iako aplikacija može obraditi rutu.
Uobičajena rewrite pravila:
- Firebase Hosting: rewrite svega na
/index.html - Vercel: rewrite na
/index.htmlza SPA output - Nginx:
try_files $uri $uri/ /index.html;
Također provjerite ne miješate li hash i path URL strategije nenamjerno. Path-based URL-ovi su bolji za dijeljivost nalik SEO-u, ali zahtijevaju rewrites.
# Testiranje deep linkova: cold start, warm start i rubni slučajevi#
Deep linkovi u produkciji najčešće padaju zbog netestiranih lifecycle uvjeta. Trebate testirati:
- cold start bez sesije
- cold start s isteklom sesijom
- warm start dok je aplikacija u pozadini
- aplikacija već otvorena na drugom tabu
- link s nedostajućim ili nevažećim parametrima
- linkove koji trebaju otvoriti modalni flow iznad shella
Praktična matrica testova#
| Scenarij | Očekivano ponašanje | Što se često pokvari |
|---|---|---|
Cold start, odjavljen, otvori /home/p/123 | Preusmjeri na login, zatim nastavi na proizvod | Pending lokacija se izgubi |
Warm start, prijavljen, otvori /search?q=x | Navigira na search s queryjem | Staro stanje ostane |
Nevažeći id /home/p/abc kad id mora biti numerički | Prikaži not found ili grešku | Crash zbog parsiranja |
Web refresh na /profile | Obnovi se ista ruta | Server vrati 404 |
Deep link na /cart iz notifikacije | Otvori košaricu iznad shella | Završava unutar tab stacka |
Naredbe za okidanje deep linkova#
Android:
adb shell am start -a android.intent.action.VIEW \
-d "https://example.com/home/p/123" \
com.example.appiOS Simulator:
xcrun simctl openurl booted "https://example.com/home/p/123"Flutter web lokalno:
flutter run -d chrome --web-port 5173
# then open http://localhost:5173/home/p/123Redirect petlje i kako ih spriječiti#
Redirect petlje obično se događaju kada:
- login ruta također okida redirect natrag na login
- splash nastavlja preusmjeravati dok je auth unknown
- obavezni parametar nedostaje, ali redirect šalje na rutu koja također ne prolazi
Praktičan obrazac je:
- dopustiti
/loginkad je korisnik unauthenticated - dopustiti
/splash samo kad je status unknown - validirati parametre ruta i vratiti se na zasebnu not-found rutu
Dodajte error ekran:
GoRouter(
errorBuilder: (context, state) => NotFoundScreen(message: state.error.toString()),
// ...
);I validirajte parametre:
final raw = state.pathParameters['id'];
if (raw == null) return const NotFoundScreen(message: 'Missing product id');⚠️ Upozorenje: Nikad ne pretpostavljajte da su query parametri prisutni ili valjani u deep linkovima. Sve u
state.uritretirajte kao korisnički input koji može biti krivo formatiran, skraćen ili namjerno zlonamjeran.
# Kako routing utječe na arhitekturu aplikacije#
Odluke o routingu određuju granice između značajki. Ako su rute razbacane po UI widgetima, mučit ćete se s:
- dodavanjem novih guardanih flowova
- dosljednom implementacijom analitike
- podrškom za web URL-ove i dijeljive linkove
- skaliranjem timova po značajkama bez merge konflikata
Preporučena struktura za skalabilan router#
Pragmatičan feature-first pristup:
| Folder | Sadrži | Zašto |
|---|---|---|
lib/core/routing | kreiranje routera, redirect pravila, imena ruta | Centralna politika |
lib/features/home | home ekrani, ugniježđene rute | Vlasništvo featurea |
lib/features/auth | login flow, session state | Auth granica |
lib/features/search | filteri vođeni URL-om | Dijeljivo stanje |
lib/core/analytics | navigation observeri | Dosljednost |
Ako želite puni breakdown feature-first pristupa i čistih granica: Flutter app architecture: clean architecture, feature-first.
Navigacija kao state machine#
U produkciji navigaciju promatrajte kao state machine pokretan:
- auth stanjem
- onboarding stanjem
- subscription stanjem
- stanjem URL lokacije
Redirect funkcija je logika prijelaza. Držite je determinističnom i testabilnom tako da je izvučete u čistu (pure) funkciju.
Primjer ideje za signature:
- ulaz: session state, trenutna lokacija
- izlaz: sljedeća lokacija ili null
To olakšava unit testiranje bez widget testova.
Analitika i privatnost#
Deep linkovi i query parametri često sadrže osjetljive podatke. Izbjegavajte stavljati e-mailove, tokene ili PII u URL-ove jer:
- URL-ovi se logiraju u analitičkim alatima
- URL-ove mogu uhvatiti proxyji i server logovi na webu
- korisnici dijele URL-ove
Umjesto ?email=a@b.com, koristite neprozirni id ili one-time token s kratkim TTL-om i validirajte na serveru.
# Ključne poruke#
- Modelirajte ekrane koji se mogu deep linkati kao determinističke URL-ove, uključujući smisleno stanje kroz query parametre.
- Koristite
refreshListenablei brzu redirect funkciju za auth guardove i onboarding pravila bez API poziva unutar redirecta. - Koristite ShellRoute za donju navigaciju s neovisnim stackovima, a modalne ili checkout flowove gurajte iznad shella preko root navigatora.
- Implementirajte nastavak nakon logina spremanjem pending lokacije, pa njenim „konzumiranjem” nakon uspješne autentikacije.
- Testirajte deep linkove kroz cold start, warm start, nevažeće parametre i web refresh uz ispravna server rewrite pravila.
# Zaključak#
Robusna postava Flutter go_router deep links je razlika između navigacijskog demo projekta i produkcijske aplikacije koja preživi stvarne korisnike, stvarne URL-ove i stvarne rubne slučajeve. Ako implementirate determinističke URL-ove, redirecte vođene politikama, ugniježđenu navigaciju preko ShellRoutea i stanje vođeno URL-om, dobivate dijeljive linkove, ispravno back ponašanje i routing sloj koji se skalira s vašom arhitekturom.
Ako želite da vam Samioda pomogne dizajnirati produkcijsku strategiju navigacije i deep linkinga, uključujući konfiguraciju platforme, redirect politike i feature-first strukturu routinga, kontaktirajte nas i pregledat ćemo vaš trenutni router te predložiti „otvrdnuti” plan implementacije.
FAQ
Osnivač i senior developer u Samiodi. 8+ godina iskustva u izradi React, Next.js, Flutter i n8n rješenja za klijente diljem Europe.
Više iz kategorije Mobilni razvoj
Sve →Ojačavanje sigurnosti Flutter aplikacije: SSL pinning, detekcija roota i jailbreaka te sigurna pohrana (Vodič za 2026.)
Praktičan vodič za ojačavanje sigurnosti Flutter aplikacije uz checklistu temeljenu na threat modelu, obrasce sigurne pohrane, kompromise SSL pinninga, provjere integriteta u runtimeu i sigurno rukovanje auth tokenima.
Flutter zadaci u pozadini: raspoređivanje, pouzdanost i ograničenja platformi (iOS + Android) u 2026.
Praktičan vodič za raspoređivanje Flutter zadataka u pozadini: ograničenja platformi na iOS-u i Androidu, kompromisi pouzdanosti te kada koristiti workmanager, background_fetch ili izvorni kod za sinkronizaciju, obavijesti i raspoređivanje koje čuva bateriju.
Flutter dizajnerski sustav i tematizacija u 2026.: Material 3, dinamičke boje, tipografija i tamni način rada
Praktičan vodič za tematizaciju Flutter dizajnerskog sustava uz Material 3: skalabilna arhitektura teme, dizajnerski tokeni, dinamičke boje, tipografija, razmaci, tematizacija komponenti i konzistentan tamni način rada kroz sve funkcionalnosti.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
Vodič za Flutter deep linking za 2026.: Universal Links, Android App Links i pouzdano rutiranje unutar aplikacije
Praktičan, produkcijski spreman vodič za Flutter deep linking: Universal Links, Android App Links, go_router obrada ruta, deferred deep linkovi, osnove atribucije u analitici i kontrolna lista za rješavanje problema.
Flutter zadaci u pozadini: raspoređivanje, pouzdanost i ograničenja platformi (iOS + Android) u 2026.
Praktičan vodič za raspoređivanje Flutter zadataka u pozadini: ograničenja platformi na iOS-u i Androidu, kompromisi pouzdanosti te kada koristiti workmanager, background_fetch ili izvorni kod za sinkronizaciju, obavijesti i raspoređivanje koje čuva bateriju.
Flutter dizajnerski sustav i tematizacija u 2026.: Material 3, dinamičke boje, tipografija i tamni način rada
Praktičan vodič za tematizaciju Flutter dizajnerskog sustava uz Material 3: skalabilna arhitektura teme, dizajnerski tokeni, dinamičke boje, tipografija, razmaci, tematizacija komponenti i konzistentan tamni način rada kroz sve funkcionalnosti.