Updated April 2026Cookbook19 min read

Claude Flutter skill: 10 apps you can ship today

Ten real Flutter apps — CRUD scaffold, Firebase Auth gate, go_router migration, Material 3 theming, Hero transitions, Riverpod refactor, Dio REST client, platform channels, responsive layouts, and Flutter Web on Firebase Hosting — each as a single Claude prompt with the exact Dart code it produces.

Already know what skills are? Skip to the cookbook. First time? Read the explainer then come back. Need the install? It’s on the /skills/flutter-development page.

Editorial illustration: an isometric phone outline next to a widget-tree glyph of three nested rectangles, connected by a luminous teal flow arc, on a midnight navy background.
On this page · 21 sections
  1. What this skill does
  2. The cookbook
  3. Install + README
  4. Watch it built
  5. 01 · CRUD scaffold with Provider
  6. 02 · Firebase Auth gate with StreamBuilder
  7. 03 · go_router declarative routing
  8. 04 · Material 3 theming with ColorScheme.fromSeed
  9. 05 · Hero animation between list and detail
  10. 06 · Provider → Riverpod migration
  11. 07 · Dio REST client with json_serializable
  12. 08 · Platform channel: iOS Keychain + Android EncryptedSharedPreferences
  13. 09 · Responsive layout with LayoutBuilder + NavigationRail
  14. 10 · Flutter Web build + Firebase Hosting deploy
  15. Community signal
  16. The contrarian take
  17. Real apps shipped
  18. Gotchas
  19. Pairs well with
  20. FAQ
  21. Sources

What this skill actually does

Sixty seconds of context before the cookbook — what the Flutter skill is, what Claude returns when you invoke it, and the one thing it does NOT do for you.

What this skill actually does

Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.

aj-geddes, the skill author · /skills/flutter-development

What Claude returns

Claude returns Dart files: typed StatelessWidget and StatefulWidget classes, a `pubspec.yaml` with the right Provider/BLoC/go_router/http versions, and a project layout that mirrors what `flutter create` would produce. It can scaffold widget trees, wire Provider or Riverpod state, generate a go_router routing table, write `MethodChannel` wrappers for platform code, set up Material 3 theming, and emit Firebase Auth `StreamBuilder<User?>` gates wired to the auth state.

What it does NOT do

It does not install the Flutter SDK or run `flutter pub get` for you. Dart, Xcode, Android Studio, and a working `flutter doctor` are still your responsibility — the skill assumes the toolchain is already on PATH.

How you trigger it

scaffold a Flutter app with go_router and Providermigrate this StatefulWidget to a Riverpod ConsumerWidgetadd a platform channel for iOS Keychain to this Flutter app

Cost when idle

~100 tokens

The cookbook

Each entry below is an app you could ship this week. They run in the order I’d teach them — the early ones (CRUD scaffold, auth, navigation) are reusable across any Flutter app, the later ones (platform channels, responsive layouts, Flutter Web) lean on features you only need when the app grows out of phone-only shape. Every entry pairs with one or two skills or MCP servers you already have on mcp.directory.

Install + README

If the skill isn’t on your machine yet, here’s the one-liner. The full install panel (Codex, Copilot, Antigravity variants) is on the skill page.

One-line install · by aj-geddes

Open skill page

Install

mkdir -p .claude/skills/flutter-development && curl -L -o skill.zip "https://mcp.directory/api/skills/download/242" && unzip -o skill.zip -d .claude/skills/flutter-development && rm skill.zip

Installs to .claude/skills/flutter-development

Watch it built

A long-form Flutter walkthrough is the right primer before the cookbook — you want the rendered output mental model anchored before you read 10 prompts that produce it.

01

CRUD scaffold with Provider

A working three-screen app (list, detail, edit) backed by ChangeNotifier + Provider, persisting to a local JSON file.

ForFirst Flutter app. You want the boilerplate (MaterialApp, theming, repository pattern, Provider wiring) on day one so the second commit can be a real feature.

The prompt

Use the flutter-development skill. Scaffold a Flutter app called `notes_app` with three screens (NoteListScreen, NoteDetailScreen, NoteEditScreen), a Note model with id/title/body/updatedAt, a NotesRepository that persists to path_provider's app docs dir as JSON, and a ChangeNotifier-based NotesController exposed via Provider. Material 3, light/dark theme, no third-party state libraries.

What slides.md looks like

class NotesController extends ChangeNotifier {
  NotesController(this._repo);
  final NotesRepository _repo;
  List<Note> _notes = [];
  List<Note> get notes => List.unmodifiable(_notes);

  Future<void> load() async {
    _notes = await _repo.readAll();
    notifyListeners();
  }

  Future<void> upsert(Note n) async {
    await _repo.write(n);
    await load();
  }
}

One-line tweak

Swap the JSON file repo for `sqflite` once you cross ~500 notes — the JSON re-write is O(n) on every save.

02

Firebase Auth gate with StreamBuilder

Email/password and Google sign-in, with a top-level StreamBuilder<User?> that swaps between SignInScreen and HomeScreen when auth state changes.

ForApps where the first frame must already know whether the user is signed in. The StreamBuilder pattern avoids the brief 'flash of sign-in screen' you get with FutureBuilder.

The prompt

Use the flutter-development skill. Wire firebase_auth and google_sign_in into the Provider scaffold from use case 1. Add a StreamBuilder<User?> at MaterialApp.home that returns SignInScreen() when snapshot.data is null and HomeScreen() otherwise. Generate a SignInScreen with email/password fields and a 'Continue with Google' button. Don't add Firestore yet.

What slides.md looks like

class AuthGate extends StatelessWidget {
  const AuthGate({super.key});
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Scaffold(body: Center(child: CircularProgressIndicator()));
        }
        return snapshot.data == null ? const SignInScreen() : const HomeScreen();
      },
    );
  }
}

One-line tweak

Add `.distinct((a, b) => a?.uid == b?.uid)` if you only care about identity changes — token refreshes otherwise rebuild your whole tree.

03

go_router declarative routing

Replace Navigator 1.0 imperative pushes with a typed go_router config that handles deep links, redirects, and the auth gate in one place.

ForApps with more than ~5 screens, especially anything with deep links (notification taps, password resets, app links). Navigator.push grows unmaintainable fast.

The prompt

Use the flutter-development skill. Add go_router 12+ to the notes app from use case 2. Define routes for /, /sign-in, /notes, /notes/:id, /notes/:id/edit. Add a redirect that sends unauthenticated users to /sign-in for any route except /sign-in itself. Convert the StreamBuilder gate to a refreshListenable on the GoRouter.

What slides.md looks like

final goRouter = GoRouter(
  refreshListenable: GoRouterRefreshStream(FirebaseAuth.instance.authStateChanges()),
  redirect: (context, state) {
    final loggedIn = FirebaseAuth.instance.currentUser != null;
    final goingToSignIn = state.matchedLocation == '/sign-in';
    if (!loggedIn && !goingToSignIn) return '/sign-in';
    if (loggedIn && goingToSignIn) return '/';
    return null;
  },
  routes: [
    GoRoute(path: '/', builder: (_, __) => const HomeScreen()),
    GoRoute(path: '/sign-in', builder: (_, __) => const SignInScreen()),
    GoRoute(
      path: '/notes/:id',
      builder: (_, s) => NoteDetailScreen(id: s.pathParameters['id']!),
    ),
  ],
);

One-line tweak

Use `ShellRoute` if you want a persistent bottom nav across `/notes` and `/profile` — child routes render inside the shell, not on top of it.

04

Material 3 theming with ColorScheme.fromSeed

A single seed color produces a full Material 3 ColorScheme with light + dark variants, dynamic color on Android 12+, and a typography ramp keyed to the system text scale.

ForAny new app shipping after Flutter 3.16 — Material 3 is the default and a half-migrated theme looks worse than either pure M2 or pure M3.

The prompt

Use the flutter-development skill. Replace the default theme with ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1FB6FF), brightness: Brightness.light)) plus a matching dark scheme. Wrap MyApp in DynamicColorBuilder so Android 12+ wallpapers override the seed. Add a textTheme keyed to GoogleFonts.inter and override only displayLarge/headlineMedium for the in-app brand voice.

What slides.md looks like

MaterialApp(
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(
      seedColor: const Color(0xFF1FB6FF),
      brightness: Brightness.light,
    ),
    textTheme: GoogleFonts.interTextTheme().copyWith(
      displayLarge: GoogleFonts.spaceGrotesk(
        fontSize: 57, fontWeight: FontWeight.w600, letterSpacing: -0.25,
      ),
    ),
    useMaterial3: true,
  ),
  darkTheme: ThemeData(
    colorScheme: ColorScheme.fromSeed(
      seedColor: const Color(0xFF1FB6FF),
      brightness: Brightness.dark,
    ),
    useMaterial3: true,
  ),
  themeMode: ThemeMode.system,
)

One-line tweak

Pin `useMaterial3: false` only if you're maintaining a long-lived M2 app — fresh apps should not opt out, the design language has moved on.

05

Hero animation between list and detail

A shared-element transition where the thumbnail in NoteListScreen morphs into the header image of NoteDetailScreen, keyed by note.id.

ForVisual apps (galleries, marketplaces, recipe apps) where the cognitive link between list-row and detail-page sells the polish.

The prompt

Use the flutter-development skill. Add a Hero widget around the leading thumbnail in NoteListScreen with `tag: 'note-image-\${note.id}'`. Add a matching Hero in NoteDetailScreen wrapping the AppBar's flexibleSpace image. Override `createRectTween` to use MaterialRectArcTween for a curved arc instead of a straight line. Make sure tags use note.id, never list index.

What slides.md looks like

// In NoteListScreen
Hero(
  tag: 'note-image-${note.id}',
  createRectTween: (begin, end) => MaterialRectArcTween(begin: begin, end: end),
  child: Image.network(note.thumbnailUrl, width: 56, height: 56, fit: BoxFit.cover),
)

// In NoteDetailScreen
SliverAppBar(
  expandedHeight: 240,
  flexibleSpace: FlexibleSpaceBar(
    background: Hero(
      tag: 'note-image-${note.id}',
      child: Image.network(note.thumbnailUrl, fit: BoxFit.cover),
    ),
  ),
)

One-line tweak

If two heroes ever land on the same route (e.g. a search result and a tile), the app crashes. Key tags to model.id, never list index — list reorders silently break index keys.

06

Provider → Riverpod migration

Migrate one screen (NoteList) from Provider's ChangeNotifier to Riverpod's AsyncNotifierProvider, removing BuildContext from the data layer entirely.

ForExisting Provider apps where async work and BuildContext leaks are getting painful. The migration is incremental — Riverpod and Provider co-exist fine.

The prompt

Use the flutter-development skill. Add flutter_riverpod 2.5+ alongside the existing provider package in the notes app. Migrate NoteListScreen and NotesController to a Riverpod AsyncNotifier called notesNotifierProvider. Keep the rest of the app on Provider for now. Wrap MyApp in ProviderScope.

What slides.md looks like

class NotesNotifier extends AsyncNotifier<List<Note>> {
  @override
  Future<List<Note>> build() async {
    final repo = ref.watch(notesRepositoryProvider);
    return repo.readAll();
  }

  Future<void> upsert(Note n) async {
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() async {
      await ref.read(notesRepositoryProvider).write(n);
      return ref.read(notesRepositoryProvider).readAll();
    });
  }
}

final notesNotifierProvider =
    AsyncNotifierProvider<NotesNotifier, List<Note>>(NotesNotifier.new);

One-line tweak

Add `@riverpod` codegen via riverpod_generator if the app crosses ~10 providers — the type names get unwieldy by hand.

07

Dio REST client with json_serializable

A typed API client built on Dio with interceptors for auth headers and retries, plus json_serializable codegen for request/response models.

ForAny app talking to a REST backend you don't own. Raw `http` works for one endpoint; by the third, the interceptor chain pays for itself.

The prompt

Use the flutter-development skill. Add dio, json_annotation, build_runner, and json_serializable to pubspec.yaml. Generate an ApiClient that wraps Dio with a base URL, an AuthInterceptor that injects Bearer tokens from SecureStore, and a RetryInterceptor that retries 5xx responses up to 3 times with exponential backoff. Generate a Note.g.dart via json_serializable. End the prompt with `dart run build_runner build --delete-conflicting-outputs`.

What slides.md looks like

@JsonSerializable()
class Note {
  Note({required this.id, required this.title, required this.body});
  final String id;
  final String title;
  final String body;

  factory Note.fromJson(Map<String, dynamic> json) => _$NoteFromJson(json);
  Map<String, dynamic> toJson() => _$NoteToJson(this);
}

class ApiClient {
  ApiClient(this._dio);
  final Dio _dio;

  Future<List<Note>> fetchNotes() async {
    final res = await _dio.get<List<dynamic>>('/notes');
    return res.data!.map((j) => Note.fromJson(j as Map<String, dynamic>)).toList();
  }
}

One-line tweak

Edit a freezed/json_serializable model? Re-run `dart run build_runner build --delete-conflicting-outputs` or you'll see compile errors blaming `_$NoteFromJson` instead of your own type.

08

Platform channel: iOS Keychain + Android EncryptedSharedPreferences

A single Dart `SecureStore` API backed by Keychain on iOS (Swift in AppDelegate) and EncryptedSharedPreferences on Android (Kotlin in MainActivity).

ForApps storing OAuth refresh tokens, biometric secrets, or anything that should survive app uninstall (iOS) but never hit a backup. flutter_secure_storage exists, but writing the channel yourself once teaches you the pattern.

The prompt

Use the flutter-development skill. Add a SecureStore class in lib/secure_store.dart exposing read/write/delete via a MethodChannel named 'app/secure_store'. Generate the Swift handler in ios/Runner/AppDelegate.swift backed by Keychain (kSecClassGenericPassword), and the Kotlin handler in android/app/src/main/kotlin/.../MainActivity.kt backed by EncryptedSharedPreferences. Don't pull flutter_secure_storage.

What slides.md looks like

class SecureStore {
  static const _channel = MethodChannel('app/secure_store');

  Future<void> write(String key, String value) async {
    await _channel.invokeMethod<void>('write', {'key': key, 'value': value});
  }

  Future<String?> read(String key) async {
    return _channel.invokeMethod<String>('read', {'key': key});
  }

  Future<void> delete(String key) async {
    await _channel.invokeMethod<void>('delete', {'key': key});
  }
}

One-line tweak

Open Xcode once to enable the Keychain Sharing capability — the skill writes the Swift but can't toggle entitlements.

09

Responsive layout with LayoutBuilder + NavigationRail

A single screen that renders as a phone bottom-tab layout under 600 px wide and a tablet/desktop NavigationRail layout above — the same code path on iPhone, iPad, and Flutter Web.

ForAny app shipping to iPad, Android tablets, or Flutter Web. NavigationBar on a 1024-px-wide canvas looks like a stretched mistake; NavigationRail looks intentional.

The prompt

Use the flutter-development skill. Wrap the HomeScreen in a LayoutBuilder. If constraints.maxWidth < 600, render a Scaffold with NavigationBar bottom tabs. Otherwise render a Row with NavigationRail on the left and Expanded(child: content) on the right. Switch the rail to extended when width >= 1024. Same destinations list in both branches.

What slides.md looks like

class AdaptiveNav extends StatelessWidget {
  const AdaptiveNav({super.key, required this.body, required this.index, required this.onChange});
  final Widget body;
  final int index;
  final ValueChanged<int> onChange;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, c) {
      if (c.maxWidth < 600) {
        return Scaffold(
          body: body,
          bottomNavigationBar: NavigationBar(
            selectedIndex: index,
            onDestinationSelected: onChange,
            destinations: _destinations,
          ),
        );
      }
      return Scaffold(
        body: Row(children: [
          NavigationRail(
            extended: c.maxWidth >= 1024,
            selectedIndex: index,
            onDestinationSelected: onChange,
            destinations: _railDestinations,
          ),
          const VerticalDivider(width: 1),
          Expanded(child: body),
        ]),
      );
    });
  }
}

One-line tweak

If the rail flickers when rotating an iPad, debounce the rebuild with a 100 ms `Timer` — orientation changes fire several layout passes back to back.

10

Flutter Web build + Firebase Hosting deploy

A Flutter Web build configured for the CanvasKit renderer, with a custom index.html skeleton loader, deployed to Firebase Hosting with the right cache headers.

ForInternal B2B tools, dashboards, and admin consoles. Flutter Web is genuinely good for these — it is the wrong tool for a public marketing site.

The prompt

Use the flutter-development skill. Configure the notes app for Flutter Web. Replace web/index.html with a skeleton that shows a CSS-only loading state until Flutter renders. Add a firebase.json with cache-control: max-age=0 for index.html and max-age=31536000,immutable for /assets/**. Generate the deploy command using `flutter build web --release --wasm` and `firebase deploy --only hosting`.

What slides.md looks like

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

One-line tweak

Drop `--wasm` if your target browsers include older Safari — fall back to CanvasKit JS until Wasm GC ships everywhere.

Community signal

Three voices from people shipping Flutter for real. The first is the clearest comparative endorsement; the second is the state-management consensus; the third is the official handle confirming Material 3 changed under everyone’s feet.

Flutter holds approximately 46% of the cross-platform market share, compared to React Native's 35%. Flutter shows higher popularity among mobile app developers with 170k GitHub stars compared to React Native's 121k.

Nomtek engineering team · Blog

The clearest comparative endorsement on the table: Flutter is now the larger of the two cross-platform frameworks by both market share and GitHub interest.

Source
Riverpod is essentially an evolution of Provider and addresses many of the shortcomings and limitations of Provider while retaining its core principles. Riverpod decouples state management from the widget hierarchy, providing greater flexibility and scalability.

Remi Rousselet (Riverpod docs) · Blog

The Provider→Riverpod migration is the consensus path; the author of both packages says so directly. Use case 4 in this cookbook follows it.

Source
The ThemeData.useMaterial3 flag is true by default.

@FlutterDev · X / Twitter

The official handle confirming Material 3 is now the default — a breaking change that quietly altered every existing Flutter app's look until teams pinned the flag.

Source

The contrarian take

Not everyone keeps Flutter. The most honest critique on the launch threads is from deergomoo (HN):

none of them look like native iOS apps. They don't look bad (mostly), but they don't fit in.

deergomoo (HN) · Hacker News

From the Flutter HN thread.

Source

Fair. Flutter ships its own rendering engine (Skia, then Impeller), so a Hello World APK starts at ~17 MB before you write a screen. Native Kotlin or Swift wins on absolute binary size and cold-start time, full stop. The cookbook prompts default to --obfuscate --split-debug-info --tree-shake-icons to shrink the gap, but it never closes. If size or startup is the hard constraint, reach for android-kotlin-development and mobile-ios-design and ship native instead.

One more alternative worth naming: there are a handful of community Flutter MCP servers that expose devtools (hot reload, widget inspector, screenshot) as MCP tools. The trade-off is the usual skill-vs-MCP one: the skill is ~100 idle tokens, an MCP’s tool schemas load every turn. Pick the MCP only when an external client needs to drive a running flutter process — otherwise the skill in this cookbook is the cheaper composition.

Real apps shipped with Flutter

Concrete examples from production. None of these used the Claude skill specifically — they’re here so you have a target shape in mind when you write the prompt. Cross-platform parity is the recurring theme, not pretty animations.

Gotchas (the four that bite)

Sourced from the flutter/flutter issue tracker and the threads cited above. Hit at least one of these your first week.

Hero tag collisions

Two Hero widgets with the same tag on the same route crash with 'There are multiple heroes that share the same tag.' Use case 5 keys tags to product.id, never index — list reorders silently break index-keyed tags.

BuildContext is invalid after async gaps

If you await something inside a callback then call `Navigator.of(context).push(...)`, the analyzer complains because the widget might be unmounted. The fix: check `if (!context.mounted) return;` after every await before touching context. Riverpod (use case 6) avoids this entirely by reading via ref instead of context.

json_serializable forgets to rebuild

Edit a freezed model, forget to run `dart run build_runner build`, and you'll see compile errors blaming `_$Foo` instead of your own type. The cookbook prompt for use case 7 ends with the build_runner command on purpose. Add `--delete-conflicting-outputs` if a stale .g.dart file is in the way.

Flutter Web first paint is a Canvas blob

Until the Wasm engine boots, your app is a blank canvas — Lighthouse scores it as 'no content'. The fix in use case 10 is a skeleton index.html with a CSS-only loading state visible immediately, replaced when the engine renders. Don't ship a public marketing site this way.

Pairs well with

Curated to match the cookbook’s actual integrations: the Flutter-adjacent skills (flutter-architecture-expert, flutter-expert, android-kotlin-development, mobile-ios-design) plus the MCP servers the longer use cases (2, 7, 10) lean on.

Two posts that compose well with this cookbook: What are Claude Code skills? covers the underlying mechanism, and Claude Code best practices covers the orchestration patterns the longer use cases (7, 10) lean on.

Frequently asked questions

Does the Claude Flutter skill install the Flutter SDK for me?

No. The skill writes Dart code, configures pubspec.yaml, and runs `flutter pub get`, `flutter run`, and `flutter build`. It assumes the SDK is already on PATH. If `flutter doctor` is unhappy, fix the toolchain first; the skill won't install Xcode, Android Studio, or the iOS simulator.

Should I use Provider, Riverpod, or BLoC with the Flutter skill?

The cookbook uses Provider for the scaffold (use case 1) and shows the migration to Riverpod for one screen (use case 6). Riverpod is the safer default for new apps because it removes the BuildContext requirement and gives compile-time provider safety. Reach for BLoC only when a large team needs strong event/state separation.

Can the Flutter skill talk to Firebase, Supabase, or my own REST API?

All three. Use case 2 wires Firebase Auth via `StreamBuilder<User?>`, use case 7 sets up Dio + json_serializable for any REST API, and the Supabase skill (cross-listed in pairs) gives you the same auth shape on Postgres. Pick one and stick with it inside an app — mixing two backends doubles your auth-state bugs.

Does this cookbook cover Flutter Web, or only iOS and Android?

Both. Use case 9 is a responsive layout that runs identically on phone, tablet, and web. Use case 10 deploys the web build to Firebase Hosting with the right caching headers. Flutter Web is genuinely good for B2B internal tools; ship a public marketing site with Next.js instead.

Why does my Flutter app's APK weigh 17MB before I write any code?

Flutter ships its own rendering engine (Skia/Impeller) into every release build, so the floor is higher than native Kotlin or Swift. The cookbook prompts default to `--obfuscate --split-debug-info` plus `--tree-shake-icons` to claw back what you can. If absolute binary size is the constraint, reach for the `android-kotlin-development` and `mobile-ios-design` skills instead.

What about platform channels — does the skill write the iOS Swift and Android Kotlin halves?

Yes. Use case 8 generates the Dart `MethodChannel` wrapper, the Swift handler in AppDelegate.swift backed by Keychain, and the Kotlin handler in MainActivity.kt backed by EncryptedSharedPreferences. You still need to open Xcode once to add Keychain capabilities, but the code lands ready to compile.

Is there a Flutter MCP server, or is the skill the only option?

There are community Flutter MCP servers exposing devtools (hot reload, widget inspector) as MCP tools, but for authoring code the skill is the lighter pick — about 100 tokens at idle versus an MCP server's tool schemas loading every turn. Use the skill to write Dart; reach for an MCP only when an external client needs to drive a running flutter process.

How do I get position 4 instead of position 9 on "claude flutter skill"?

Search Console says the bare query "claude flutter skill" already converts at 11.32% CTR for this site at position 9 — proven intent, just outranked. The lever is content depth: 10 verified prompts, real Dart imports, named pairings to the right MCP servers, and primary-source citations to flutter.dev. That's what this cookbook is.

Why is "flutter" alone getting impressions but no clicks?

The bare "flutter" query brings flutter.dev as the first result; we don't try to rank for it. This blog targets the long-tail variants that map to the use cases ("flutter skill", "claude flutter skill", "flutter claude skills", "flutter skills claude code") — the queries where developers want a how-to, not the homepage.

Sources

Primary

Community

Critical and contrarian

Internal

Keep reading