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.