Flutter Web과 데스크톱 — 멀티플랫폼 빌드 전략

Flutter의 가장 큰 장점은 하나의 코드베이스로 모바일, 웹, 데스크톱을 모두 지원한다는 것입니다. 각 플랫폼의 특성과 빌드 전략을 정리해보겠습니다.


지원 플랫폼

플랫폼안정성렌더러
AndroidStableSkia/Impeller
iOSStableImpeller
WebStableCanvasKit/HTML
WindowsStableSkia
macOSStableSkia
LinuxStableSkia

Flutter Web

빌드

BASH
# 웹 빌드
flutter build web

# 렌더러 지정
flutter build web --web-renderer canvaskit  # 고품질 (기본)
flutter build web --web-renderer html       # 가벼움, 텍스트 SEO
flutter build web --web-renderer auto       # 모바일은 html, 데스크톱은 canvaskit

렌더러 비교

항목CanvasKitHTML
렌더링 품질높음 (Skia)브라우저 의존
초기 로딩 크기~2MB~400KB
텍스트 선택제한적네이티브
SEO제한적더 나음
성능일관적가변적

웹 전용 코드

DART
import 'package:flutter/foundation.dart' show kIsWeb;

Widget build(BuildContext context) {
  if (kIsWeb) {
    return const WebSpecificWidget();
  }
  return const MobileWidget();
}

URL 전략

DART
// main.dart
import 'package:flutter_web_plugins/url_strategy.dart';

void main() {
  // URL에서 # 제거 (example.com/page 대신 example.com/#/page)
  usePathUrlStrategy();
  runApp(const MyApp());
}

Flutter 데스크톱

설정

BASH
# 플랫폼 지원 활성화
flutter config --enable-windows-desktop
flutter config --enable-macos-desktop
flutter config --enable-linux-desktop

# 기존 프로젝트에 데스크톱 지원 추가
flutter create --platforms=windows,macos,linux .

빌드

BASH
# Windows 빌드
flutter build windows

# macOS 빌드
flutter build macos

# Linux 빌드
flutter build linux

데스크톱 전용 고려사항

DART
import 'dart:io' show Platform;

// 플랫폼별 분기
if (Platform.isWindows) {
  // Windows 전용
} else if (Platform.isMacOS) {
  // macOS 전용
} else if (Platform.isLinux) {
  // Linux 전용
}

창 크기 관리 (데스크톱)

YAML
dependencies:
  window_manager: ^0.4.0
DART
import 'package:window_manager/window_manager.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await windowManager.ensureInitialized();

  WindowOptions windowOptions = const WindowOptions(
    size: Size(1200, 800),
    minimumSize: Size(800, 600),
    center: true,
    backgroundColor: Colors.transparent,
    title: 'My Desktop App',
    titleBarStyle: TitleBarStyle.hidden,
  );

  windowManager.waitUntilReadyToShow(windowOptions, () async {
    await windowManager.show();
    await windowManager.focus();
  });

  runApp(const MyApp());
}

멀티플랫폼 UI 전략

반응형 레이아웃

DART
class AdaptiveLayout extends StatelessWidget {
  const AdaptiveLayout({super.key});

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth > 1200) {
          return const DesktopLayout();
        } else if (constraints.maxWidth > 600) {
          return const TabletLayout();
        } else {
          return const MobileLayout();
        }
      },
    );
  }
}

플랫폼별 입력 처리

DART
// 데스크톱: 키보드 단축키
Shortcuts(
  shortcuts: {
    LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyS):
        const SaveIntent(),
    LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyZ):
        const UndoIntent(),
  },
  child: Actions(
    actions: {
      SaveIntent: CallbackAction<SaveIntent>(
        onInvoke: (_) => _save(),
      ),
    },
    child: const MyApp(),
  ),
)

// 데스크톱: 마우스 호버
MouseRegion(
  onEnter: (_) => setState(() => _isHovered = true),
  onExit: (_) => setState(() => _isHovered = false),
  cursor: SystemMouseCursors.click,
  child: Container(
    color: _isHovered ? Colors.blue.shade100 : Colors.transparent,
    child: const Text('호버 효과'),
  ),
)

// 데스크톱: 우클릭 컨텍스트 메뉴
GestureDetector(
  onSecondaryTapUp: (details) {
    _showContextMenu(context, details.globalPosition);
  },
  child: const ListTile(title: Text('우클릭하세요')),
)

플랫폼별 패키지 사용

DART
// 조건부 import
import 'stub_impl.dart'
    if (dart.library.io) 'io_impl.dart'
    if (dart.library.html) 'web_impl.dart';

// 또는 패키지 수준에서 분기
// 일부 패키지는 웹에서 동작하지 않음
// sqflite → 웹 불가 (대안: drift/web)
// path_provider → 웹 제한적
// shared_preferences → 웹 가능 (localStorage 사용)

웹 SEO와 성능

DART
// 메타 태그 설정 (web/index.html)
// <meta name="description" content="앱 설명">

// 초기 로딩 최적화
// web/index.html에서 스플래시 표시
HTML
<!-- web/index.html 로딩 표시 -->
<style>
  .loading {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
  }
</style>
<body>
  <div class="loading" id="loading">
    <p>로딩 중...</p>
  </div>
  <script>
    window.addEventListener('flutter-first-frame', function() {
      document.getElementById('loading').style.display = 'none';
    });
  </script>
</body>

빌드 전략

전략설명적합한 경우
단일 코드베이스모든 플랫폼에서 동일 코드UI가 단순한 앱
적응형 UI플랫폼별 레이아웃 분기대부분의 앱 (권장)
별도 프로젝트플랫폼별 완전 분리플랫폼별 경험이 매우 다른 경우

면접 포인트: "Flutter Web의 한계는?"이라는 질문에 SEO 제한(CanvasKit), 초기 로딩 크기, 브라우저 API 접근 제한을 답할 수 있어야 합니다. 콘텐츠 중심 웹사이트보다는 웹 앱(SPA) 에 적합합니다.


정리

  • Flutter는 모바일, 웹, 데스크톱을 하나의 코드베이스로 지원합니다
  • 웹 렌더러는 CanvasKit(고품질)과 HTML(가벼움) 중 선택할 수 있습니다
  • 데스크톱에서는 창 크기 관리, 키보드 단축키, 마우스 호버 등을 고려해야 합니다
  • LayoutBuilder로 화면 크기에 따른 적응형 레이아웃을 구현하세요
  • Flutter Web은 콘텐츠 사이트보다 웹 앱에 더 적합합니다
  • 플랫폼별 패키지 호환성을 확인하고, 조건부 import로 분기하세요
댓글 로딩 중...