Flutter Web ile Modern Web Uygulaması Geliştirme
Flutter 3.x ile web desteği production-ready olmuştur. Aynı kod tabanından Android, iOS ve Web çıktısı alabilirsiniz; ancak web'in kendine özgü davranışları (URL yönetimi, responsive tasarım, CORS) mobil geliştiricilerin dikkat etmesi gereken konulardır.
Renderer Seçimi: CanvasKit vs HTML
// Geliştirme sırasında render'ı belirtmek
// flutter run -d chrome --web-renderer canvaskit
// flutter run -d chrome --web-renderer html
// Release build
// flutter build web --web-renderer canvaskit --release
// CanvasKit: piksel-mükemmel render, Skia tabanlı
// - Artı: mobil ile birebir aynı görünüm
// - Eksi: ~1.5MB ek indirme boyutu, ilk yüklenme yavaş
// HTML: tarayıcı HTML/CSS kullanır
// - Artı: daha hızlı ilk yükleme, daha küçük boyut
// - Eksi: bazı görsel farklar mümkün
// flutter/web/index.html — ServiceWorker ile önbellekleme
// build/web/ klasörü herhangi bir web sunucusuna deploy edilebilir
URL Stratejisi: Hash vs Path
// pubspec: go_router: ^14.2.7
// lib/router/app_router.dart
final router = GoRouter(
routes: [
GoRoute(path: '/', builder: (_, __) => const HomePage()),
GoRoute(path: '/products', builder: (_, __) => const ProductsPage()),
GoRoute(
path: '/products/:id',
builder: (_, state) => ProductDetailPage(
productId: state.pathParameters['id']!,
),
),
],
);
// main.dart — web için PathURL stratejisi
void main() {
// Hash strategy (#/products) → default
// Path strategy (/products) → web sunucusu rewrite kuralı gerektirir
if (kIsWeb) {
setUrlStrategy(PathUrlStrategy()); // package:flutter_web_plugins
}
runApp(MaterialApp.router(routerConfig: router));
}
// Nginx rewrite (path strategy için)
// location / { try_files $uri $uri/ /index.html; }
// Apache: RewriteRule ^ /index.html [L]
Responsive Tasarım
class ResponsiveLayout extends StatelessWidget {
final Widget mobile;
final Widget? tablet;
final Widget desktop;
const ResponsiveLayout({
super.key,
required this.mobile,
this.tablet,
required this.desktop,
});
static bool isMobile(BuildContext ctx) =>
MediaQuery.of(ctx).size.width < 600;
static bool isTablet(BuildContext ctx) {
final w = MediaQuery.of(ctx).size.width;
return w >= 600 && w < 1200;
}
static bool isDesktop(BuildContext ctx) =>
MediaQuery.of(ctx).size.width >= 1200;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 1200) return desktop;
if (constraints.maxWidth >= 600) return tablet ?? desktop;
return mobile;
},
);
}
}
// Kullanım
class ProductsPage extends StatelessWidget {
const ProductsPage({super.key});
@override
Widget build(BuildContext context) {
return ResponsiveLayout(
mobile: const ProductListMobile(),
tablet: const ProductGridTablet(),
desktop: const ProductDashboardDesktop(),
);
}
}
// Grid için responsive sütun sayısı
GridView.builder(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 280,
childAspectRatio: 0.75,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemBuilder: (_, i) => ProductCard(product: products[i]),
itemCount: products.length,
)
Platform Kontrolü ve Web-Specific Kod
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
// Platforma göre davranış
void openUrl(String url) {
if (kIsWeb) {
// Web: tarayıcıda aç
html.window.open(url, '_blank');
} else if (Platform.isAndroid || Platform.isIOS) {
// Mobil: url_launcher
launchUrl(Uri.parse(url));
}
}
// Platform-specific import (conditional import)
// lib/utils/platform_util.dart
export 'platform_util_web.dart' if (dart.library.io) 'platform_util_mobile.dart';
// lib/utils/platform_util_web.dart
import 'dart:html' as html;
void downloadFile(String url, String filename) {
final anchor = html.AnchorElement(href: url)
..setAttribute('download', filename)
..click();
}
// lib/utils/platform_util_mobile.dart
void downloadFile(String url, String filename) async {
final savePath = '${(await getDownloadsDirectory())!.path}/$filename';
await Dio().download(url, savePath);
}
Web'de CORS Yönetimi
// Flutter Web, tarayıcının same-origin politikasına tabidir.
// API sunucunuzun CORS header'larını döndürmesi gerekir:
// Access-Control-Allow-Origin: https://yourapp.com
// Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
// Access-Control-Allow-Headers: Authorization, Content-Type
// ASP.NET Core CORS yapılandırması:
// builder.Services.AddCors(options => {
// options.AddPolicy("AllowFlutterWeb", policy =>
// policy.WithOrigins("https://yourapp.com")
// .AllowAnyHeader().AllowAnyMethod());
// });
// app.UseCors("AllowFlutterWeb");
// Geliştirme sırasında CORS bypass — flutter run ile proxy
// flutter run -d chrome --web-hostname localhost --web-port 3000
// Dio ile proxy (geliştirme ortamında)
if (kIsWeb && kDebugMode) {
dio.options.baseUrl = 'http://localhost:5000'; // doğrudan
} else if (kIsWeb) {
dio.options.baseUrl = 'https://api.yourapp.com';
}
Web Deploy: Firebase Hosting
// Build al
// flutter build web --release --web-renderer canvaskit
// Firebase Hosting ile deploy
// firebase init hosting → public: build/web, rewrite: index.html
// firebase deploy
// firebase.json
// {
// "hosting": {
// "public": "build/web",
// "rewrites": [{ "source": "**", "destination": "/index.html" }],
// "headers": [{
// "source": "**/*.@(js|css|wasm)",
// "headers": [{ "key": "Cache-Control", "value": "max-age=31536000" }]
// }]
// }
// }
// Meta tag SEO (index.html içine)
// <meta name="description" content="Ürün açıklaması" />
// NOT: Flutter Web SPA olduğu için SSR yok;
// SEO gerektiren projeler için server-side rendering çözümü gerekir
Flutter Web, prototipleme, dahili araçlar ve karma mobil/web uygulamaları için mükemmeldir. Ancak SEO bağımlı içerik siteleri için Next.js gibi SSR destekli bir çerçeve daha uygun olabilir. Renderer seçimini, sunucu CORS yapılandırmasını ve URL stratejisini baştan doğru kurun.