# Flutter Web SPA Configuration
## Problem Flutter Web SPAs have routing issues when users access URLs directly (e.g., `/sepet`, `/gizlilik`). Server returns 404 because physical files don't exist. ## Solution Create configuration files for all hosting platforms to redirect all requests to `index.html`. ## Configuration Files ### 1. web/_redirects (Netlify/Vercel) ``` /* /index.html 200 ``` ### 2. web/web.config (IIS/Windows) ```xml <?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <rewrite> <rules> <rule name="Flutter Routes" stopProcessing="true"> <match url=".*" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> </conditions> <action type="Rewrite" url="/" /> </rule> </rules> </rewrite> </system.webServer> </configuration> ``` ### 3. web/nginx.conf (Nginx) ```nginx server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; } } ``` ### 4. web/.htaccess (Apache) ```apache RewriteEngine On RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L] <FilesMatch "\.(js|css|png|jpg|jpeg|gif|ico|svg)$"> ExpiresActive On ExpiresDefault "access plus 1 year" Header set Cache-Control "public, immutable" </FilesMatch> ``` ## Clean index.html ```html <!DOCTYPE html> <html> <head> <base href="$FLUTTER_BASE_HREF"> <meta charset="UTF-8"> <meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta name="description" content="Your App Description"> <title>Your App Title</title> <link rel="manifest" href="manifest.json"> </head> <body> <script src="flutter_bootstrap.js" async></script> </body> </html> ``` ## Build Command ```bash flutter build web ``` ## URL Strategy Configuration ### 1. Add dependency to pubspec.yaml ```yaml dependencies: flutter_web_plugins: sdk: flutter ``` ### 2. Configure in main.dart ```dart import 'package:flutter/foundation.dart'; import 'package:flutter_web_plugins/url_strategy.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); // Enable path-based URLs for web if (kIsWeb) { usePathUrlStrategy(); } runApp(const MyApp()); } ``` ## Route Structure Example ### 1. Main Layout with Navigation ```dart // lib/widgets/main_layout.dart class MainLayout extends StatelessWidget { final Widget child; final String currentRoute; const MainLayout({ super.key, required this.child, required this.currentRoute, }); @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ // Navigation Bar Container( color: Colors.orange, child: Row( children: [ _buildNavItem('Ana Sayfa', '/', Icons.home), _buildNavItem('Sepet', '/sepet', Icons.shopping_cart), _buildNavItem('İletişim', '/iletisim', Icons.contact_mail), _buildNavItem('Gizlilik', '/gizlilik', Icons.privacy_tip), ], ), ), // Main Content Expanded(child: child), // Footer const FooterWidget(), ], ), ); } Widget _buildNavItem(String title, String route, IconData icon) { final isSelected = currentRoute == route; return GestureDetector( onTap: () => Navigator.pushNamed(context, route), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: isSelected ? Colors.white.withValues(alpha: 0.2) : Colors.transparent, borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: Colors.white, size: 18), const SizedBox(width: 6), Text(title, style: const TextStyle(color: Colors.white, fontSize: 14)), ], ), ), ); } } ``` ### 2. App Routes Configuration ```dart // lib/main.dart class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Your App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), useMaterial3: true, ), home: MainLayout( currentRoute: '/', child: HomeScreen(), ), routes: { '/': (context) => MainLayout( currentRoute: '/', child: HomeScreen(), ), '/sepet': (context) => MainLayout( currentRoute: '/sepet', child: CartScreen(), ), '/iletisim': (context) => MainLayout( currentRoute: '/iletisim', child: ContactScreen(), ), '/gizlilik': (context) => MainLayout( currentRoute: '/gizlilik', child: PrivacyScreen(), ), '/checkout': (context) => MainLayout( currentRoute: '/checkout', child: CheckoutScreen(), ), }, debugShowCheckedModeBanner: false, ); } } ``` ### 3. Screen Examples ```dart // lib/screens/home_screen.dart class HomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[100], body: Center( child: Text('Ana Sayfa'), ), ); } } // lib/screens/cart_screen.dart class CartScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[100], body: Center( child: Text('Sepet'), ), ); } } ``` ## Payment Logos Best Practices ### Use PNG files instead of SVG - Avoid `flutter_svg` dependency - Use `Image.asset()` for all logos - Create reusable `FooterWidget` ### FooterWidget Example ```dart // lib/widgets/footer_widget.dart import 'package:flutter/material.dart'; class FooterWidget extends StatelessWidget { const FooterWidget({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20), decoration: BoxDecoration( color: Colors.grey[100], border: Border(top: BorderSide(color: Colors.grey[300]!)), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Güvenli Ödeme'), SizedBox(width: 12), // Payment logos here ], ), ); } } ``` ### Usage in screens ```dart import '../widgets/footer_widget.dart'; // In build method: const FooterWidget() ``` ## Benefits - ✅ Direct URL access works (`/sepet`, `/gizlilik`) - ✅ Works on all hosting platforms - ✅ No CanvasKit errors (uses HTML renderer) - ✅ Clean, maintainable code - ✅ Proper caching for static assets
|