Рубрики
Flutter

Flutter для Web: Полное руководство 2025

Полное руководство по Flutter Web: рендеринг, SEO, PWA, оптимизация, deploy на Vercel и Firebase Hosting.

Flutter Web позволяет создавать веб-приложения на Dart. Разберём особенности разработки для Web.

CanvasKit vs HTML

CanvasKit

Рендер через WebGL:

  • Лучшая производительность
  • Одинаковое отображение на всех платформах
  • Больший размер бандла (~2MB)

HTML (HTML renderer)

Рендер через HTML/CSS:

  • Меньший размер бандла
  • Лучше SEO
  • Может отличаться от мобильной версии

Выбор рендерера

// HTML renderer для лучшего SEO
flutter build web --web-renderer html

// CanvasKit для лучшей производительности
flutter build web --web-renderer canvaskit

// Auto (по умолчанию)
flutter build web --web-renderer auto

SEO оптимизация

Meta теги

// web/index.html
<head>
  <meta name="description" content="Описание вашего приложения">

  <!-- Open Graph -->
  <meta property="og:title" content="Заголовок">
  <meta property="og:description" content="Описание">
  <meta property="og:image" content="https://example.com/image.jpg">

  <!-- Twitter Card -->
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:title" content="Заголовок">
  <meta name="twitter:description" content="Описание">
</head>

Динамические meta теги

dependencies:
  flutter_seo: ^1.0.0
import 'package:flutter_seo/flutter_seo.dart';

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Seo.widget(
      tree: WidgetTree(text: 'Содержимое страницы'),
      // Или вручную
      title: 'Заголовок страницы',
      description: 'Описание страницы',
      keywords: ['flutter', 'web', 'seo'],
      child: Scaffold(...),
    );
  }
}

SSR (Server-Side Rendering)

dependencies:
  flutter_web_plugins: ^0.0.0

Для SSR используйте Dart Frog или другие серверные решения.

PWA (Progressive Web App)

Manifest

// web/manifest.json
{
  "name": "My Flutter App",
  "short_name": "FlutterApp",
  "description": "My Flutter PWA",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#0175C2",
  "icons": [
    {
      "src": "icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Подключение manifest

<!-- web/index.html -->
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#0175C2">

Service Worker

// web/service_worker.js
const CACHE_NAME = 'flutter-app-cache-v1';

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll([
        '/',
        '/main.dart.js',
        '/assets/',
      ]);
    }),
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    }),
  );
});

Регистрация Service Worker

<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('service_worker.js');
  });
}
</script>

Оптимизация бандла

Deferred loading

// Отложенная загрузка тяжелых компонентов
import 'package:flutter/material.dart' deferred as ui;

Future<void> loadHeavyComponent() async {
  await ui.loadLibrary();
  // Теперь можно использовать ui
}

Анализ размера бандла

flutter build web --web-renderer canvaskit --analyze-size

Tree shaking

Убедитесь, что unused код удаляется:

// Используйте только нужные импорты
import 'package:flutter/material.dart'; // Хорошо
import 'package:flutter/material.dart' hide Router; // Если нужно исключить

URL Routing

go_router для Web

dependencies:
  go_router: ^14.0.0
final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomeScreen(),
    ),
    GoRoute(
      path: '/user/:id',
      builder: (context, state) {
        final id = state.pathParameters['id'];
        return UserScreen(id: id);
      },
    ),
  ],
);

void main() {
  runApp(MaterialApp.router(routerConfig: router));
}

Query параметры

GoRoute(
  path: '/search',
  builder: (context, state) {
    final query = state.uri.queryParameters['q'];
    return SearchScreen(query: query);
  },
)

Firebase Hosting

Установка Flutter CLI

dart pub global activate flutter_cli

Сборка для production

flutter build web --release

Firebase CLI

npm install -g firebase-tools
firebase login

Инициализация

firebase init hosting

firebase.json

{
  "hosting": {
    "public": "build/web",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ],
    "headers": [
      {
        "source": "**",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "max-age=1800"
          }
        ]
      }
    ]
  }
}

Deploy

firebase deploy

Vercel

vercel.json

{
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/index.html"
    }
  ],
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    }
  ]
}

Deploy

vercel --prod

Web специфичные API

Обработка URL

import 'dart:html' as html;

class UrlHandler {
  void openUrl(String url) {
    html.window.open(url, '_blank');
  }

  String get currentUrl => html.window.location.href;

  void updateUrl(String path) {
    html.window.history.pushState(null, '', path);
  }
}

LocalStorage

import 'dart:html' as html;

class WebStorage {
  void setString(String key, String value) {
    html.window.localStorage[key] = value;
  }

  String? getString(String key) {
    return html.window.localStorage[key];
  }

  void remove(String key) {
    html.window.localStorage.remove(key);
  }
}

Clipboard

import 'dart:html' as html;

class ClipboardService {
  Future<void> copyText(String text) async {
    await html.window.navigator.clipboard?.writeText(text);
  }
}

Hot Reload

При разработке

flutter run -d chrome

Auto refresh

void main() {
  runApp(MyApp());

  // Hot reload при изменениях файлов
  if (kDebugMode) {
    // Debug only code
  }
}

Заключение

Flutter Web в 2025 — это production-ready решение для создания веб-приложений. Используйте HTML renderer для SEO и CanvasKit для производительности.