{"slug":"flutter-pre-caching","title":"flutter-pre-caching","summary":"Use when preloading fonts, asset/network images, Lottie/Rive animations, local JSON/config, warming initial API data, or optimizing Flutter Web startup.","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-08-30T09:57:19.419728Z","repo":{"url":"https://github.com/evanca/flutter-ai-rules","stars":644,"forks":67,"license":"MIT","updatedAt":"2026-09-14T07:51:38Z"},"bodyHtml":"<hr>\n<h2>name: flutter-pre-caching\ndescription: \"Use when preloading fonts, asset/network images, Lottie/Rive animations, local JSON/config, warming initial API data, or optimizing Flutter Web startup.\"\nlicense: MIT</h2>\n<h1>Pre-caching in Flutter and Flutter Web</h1>\n<p>Pre-caching helps avoid jank, loading flashes, font swaps, and delayed first renders.</p>\n<p>The key rule:</p>\n<blockquote>\n<p>Pre-cache only what the user will likely see in the next 1 to 2 screens.</p>\n</blockquote>\n<p>Do not pre-cache the whole app. That can slow startup and waste memory.</p>\n<hr>\n<h2>1. Google Fonts</h2>\n<h3>Runtime Google Fonts preloading</h3>\n<p>Use <code>GoogleFonts.pendingFonts()</code> to load the font variants before showing text.</p>\n<pre><code>import 'package:flutter/material.dart';\nimport 'package:google_fonts/google_fonts.dart';\n\nclass ExampleSimple extends StatefulWidget {\n  const ExampleSimple({super.key});\n\n  @override\n  State&lt;ExampleSimple&gt; createState() =&gt; _ExampleSimpleState();\n}\n\nclass _ExampleSimpleState extends State&lt;ExampleSimple&gt; {\n  late final Future&lt;List&lt;void&gt;&gt; googleFontsPending;\n\n  @override\n  void initState() {\n    super.initState();\n\n    googleFontsPending = GoogleFonts.pendingFonts([\n      GoogleFonts.poppins(),\n      GoogleFonts.montserrat(fontStyle: FontStyle.italic),\n    ]);\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final pushButtonTextStyle = GoogleFonts.poppins(\n      textStyle: Theme.of(context).textTheme.headlineMedium,\n    );\n\n    final counterTextStyle = GoogleFonts.montserrat(\n      fontStyle: FontStyle.italic,\n      textStyle: Theme.of(context).textTheme.displayLarge,\n    );\n\n    return FutureBuilder&lt;List&lt;void&gt;&gt;(\n      future: googleFontsPending,\n      builder: (context, snapshot) {\n        if (snapshot.connectionState != ConnectionState.done) {\n          return const SizedBox();\n        }\n\n        return Column(\n          mainAxisAlignment: MainAxisAlignment.center,\n          children: [\n            Text(\n              'You have pushed the button this many times:',\n              style: pushButtonTextStyle,\n            ),\n            Text(\n              '0',\n              style: counterTextStyle,\n            ),\n          ],\n        );\n      },\n    );\n  }\n}\n</code></pre>\n<h3>Production recommendation</h3>\n<p>For production and offline support, prefer bundling critical fonts as assets.</p>\n<pre><code>flutter:\n  fonts:\n    - family: AppFont\n      fonts:\n        - asset: assets/fonts/AppFont-Regular.ttf\n        - asset: assets/fonts/AppFont-Bold.ttf\n          weight: 700\n</code></pre>\n<p>Use it in the app theme:</p>\n<pre><code>MaterialApp(\n  theme: ThemeData(\n    fontFamily: 'AppFont',\n  ),\n)\n</code></pre>\n<hr>\n<h2>2. Asset images</h2>\n<p>Use <code>precacheImage</code> for images that appear soon.</p>\n<pre><code>@override\nvoid didChangeDependencies() {\n  super.didChangeDependencies();\n\n  precacheImage(\n    const AssetImage('assets/images/header.png'),\n    context,\n  );\n}\n</code></pre>\n<p>Then use the image normally:</p>\n<pre><code>Image.asset('assets/images/header.png')\n</code></pre>\n<hr>\n<h2>3. Network images</h2>\n<p>Use <code>precacheImage</code> with <code>NetworkImage</code>.</p>\n<pre><code>@override\nvoid didChangeDependencies() {\n  super.didChangeDependencies();\n\n  precacheImage(\n    const NetworkImage('https://example.com/image.png'),\n    context,\n  );\n}\n</code></pre>\n<p>Then use it normally:</p>\n<pre><code>Image.network('https://example.com/image.png')\n</code></pre>\n<p><strong>Important:</strong>\n<code>precacheImage</code> warms Flutter’s in-memory image cache. It does not provide long-term offline caching.</p>\n<p>For disk caching, use a package like <code>cached_network_image</code>:</p>\n<pre><code>dependencies:\n  cached_network_image: ^latest\n</code></pre>\n<p>Example:</p>\n<pre><code>CachedNetworkImage(\n  imageUrl: 'https://example.com/image.png',\n)\n</code></pre>\n<hr>\n<h2>4. Multiple image preloading</h2>\n<pre><code>@override\nvoid didChangeDependencies() {\n  super.didChangeDependencies();\n\n  final images = &lt;ImageProvider&gt;[\n    const AssetImage('assets/images/header.png'),\n    const AssetImage('assets/images/avatar.png'),\n    const NetworkImage('https://example.com/banner.png'),\n  ];\n\n  for (final image in images) {\n    precacheImage(image, context);\n  }\n}\n</code></pre>\n<hr>\n<h2>5. Waiting until images are ready</h2>\n<p><code>precacheImage</code> returns a <code>Future&lt;void&gt;</code>, so you can wait before rendering the real UI.</p>\n<pre><code>late Future&lt;void&gt; _preloadImagesFuture;\n\n@override\nvoid didChangeDependencies() {\n  super.didChangeDependencies();\n\n  _preloadImagesFuture = Future.wait([\n    precacheImage(\n      const AssetImage('assets/images/header.png'),\n      context,\n    ),\n    precacheImage(\n      const NetworkImage('https://example.com/banner.png'),\n      context,\n    ),\n  ]);\n}\n\n@override\nWidget build(BuildContext context) {\n  return FutureBuilder&lt;void&gt;(\n    future: _preloadImagesFuture,\n    builder: (context, snapshot) {\n      if (snapshot.connectionState != ConnectionState.done) {\n        return const CircularProgressIndicator();\n      }\n\n      return Column(\n        children: [\n          Image.asset('assets/images/header.png'),\n          Image.network('https://example.com/banner.png'),\n        ],\n      );\n    },\n  );\n}\n</code></pre>\n<div>\n<p>Note</p>\n<p>Use <code>didChangeDependencies</code>, not <code>initState</code>, when you need <code>context</code> for <code>precacheImage</code>.</p>\n</div>\n<hr>\n<h2>6. What should normally be pre-cached on app start?</h2>\n<p><strong>Good candidates:</strong></p>\n<ul>\n<li>App logo</li>\n<li>First screen hero image</li>\n<li>First visible background image</li>\n<li>Current user avatar</li>\n<li>First visible card/list images</li>\n<li>Main font variants</li>\n<li>Small local config files</li>\n<li>Translations needed for first paint</li>\n<li>First API request</li>\n<li>Lottie/Rive animation shown immediately</li>\n</ul>\n<p><strong>Bad candidates:</strong></p>\n<ul>\n<li>All product images</li>\n<li>All gallery images</li>\n<li>All remote feed images</li>\n<li>All icons in the app</li>\n<li>All route images</li>\n<li>Every image from every screen</li>\n<li>Large animations not shown immediately</li>\n</ul>\n<hr>\n<h2>7. Practical app-start preloader</h2>\n<pre><code>class AppPreloader {\n  const AppPreloader();\n\n  Future&lt;void&gt; preload(BuildContext context) async {\n    await Future.wait([\n      _precacheImages(context),\n      _loadCriticalAssets(),\n      _warmUpInitialData(),\n    ]);\n  }\n\n  Future&lt;void&gt; _precacheImages(BuildContext context) {\n    return Future.wait([\n      precacheImage(\n        const AssetImage('assets/images/home_hero.png'),\n        context,\n      ),\n      precacheImage(\n        const AssetImage('assets/images/logo.png'),\n        context,\n      ),\n    ]);\n  }\n\n  Future&lt;void&gt; _loadCriticalAssets() async {\n    await rootBundle.loadString('assets/config/app_config.json');\n  }\n\n  Future&lt;void&gt; _warmUpInitialData() async {\n    // Example:\n    // await userRepository.getCurrentUser();\n  }\n}\n</code></pre>\n<p>Usage:</p>\n<pre><code>late Future&lt;void&gt; _preloadFuture;\n\n@override\nvoid didChangeDependencies() {\n  super.didChangeDependencies();\n\n  _preloadFuture = const AppPreloader().preload(context);\n}\n</code></pre>\n<p>With <code>FutureBuilder</code>:</p>\n<pre><code>@override\nWidget build(BuildContext context) {\n  return FutureBuilder&lt;void&gt;(\n    future: _preloadFuture,\n    builder: (context, snapshot) {\n      if (snapshot.connectionState != ConnectionState.done) {\n        return const SplashScreen();\n      }\n\n      return const HomeScreen();\n    },\n  );\n}\n</code></pre>\n<hr>\n<h2>8. Local JSON / config preloading</h2>\n<p>Useful for feature flags, local app config, mock data, translations, or onboarding content.</p>\n<pre><code>import 'package:flutter/services.dart';\n\nfinal configJson = await rootBundle.loadString(\n  'assets/config/app_config.json',\n);\n</code></pre>\n<p>Example:</p>\n<pre><code>class AppConfigLoader {\n  Future&lt;String&gt; load() {\n    return rootBundle.loadString('assets/config/app_config.json');\n  }\n}\n</code></pre>\n<hr>\n<h2>9. Initial API request warm-up</h2>\n<p>This is not pre-caching in the Flutter image-cache sense, but it is often the most useful startup optimization.</p>\n<pre><code>final userFuture = userRepository.getCurrentUser();\nfinal dashboardFuture = dashboardRepository.getDashboard();\n</code></pre>\n<p>Example:</p>\n<pre><code>class StartupData {\n  const StartupData({\n    required this.user,\n    required this.dashboard,\n  });\n\n  final User user;\n  final Dashboard dashboard;\n}\n\nFuture&lt;StartupData&gt; loadStartupData() async {\n  final results = await Future.wait([\n    userRepository.getCurrentUser(),\n    dashboardRepository.getDashboard(),\n  ]);\n\n  return StartupData(\n    user: results[0] as User,\n    dashboard: results[1] as Dashboard,\n  );\n}\n</code></pre>\n<hr>\n<h2>10. Lottie preloading</h2>\n<p>If the animation appears immediately, preload it.</p>\n<pre><code>final composition = await AssetLottie(\n  'assets/animations/success.json',\n).load();\n</code></pre>\n<p>Use this only for animations shown early. Avoid preloading many large animations on app start.</p>\n<hr>\n<h2>11. Rive preloading</h2>\n<p>Rather than triggering the asset load during a build (which causes jank), preload the <code>RiveFile</code> byte data in advance:</p>\n<pre><code>import 'package:flutter/services.dart';\nimport 'package:rive/rive.dart';\n\n// Preload the RiveFile\nfinal data = await rootBundle.load('assets/animations/character.riv');\nfinal riveFile = RiveFile.import(data);\n\n// Display using direct constructor to avoid initialization delays:\nRiveAnimation.direct(riveFile);\n</code></pre>\n<p>This ensures that the animation renders immediately upon widget mount.</p>\n<hr>\n<h2>12. Shader warm-up</h2>\n<p>Most apps do not need this. It can help if you have:</p>\n<ul>\n<li>Heavy custom painting</li>\n<li>Complex transitions</li>\n<li>Known animation jank</li>\n<li>Older rendering paths</li>\n</ul>\n<p>Basic shape:</p>\n<pre><code>class CustomShaderWarmUp extends ShaderWarmUp {\n  @override\n  void warmUpOnCanvas(Canvas canvas) {\n    final paint = Paint();\n\n    canvas.drawRect(\n      const Rect.fromLTWH(0, 0, 100, 100),\n      paint,\n    );\n  }\n}\n</code></pre>\n<p>Then set it before <code>runApp</code>:</p>\n<pre><code>void main() {\n  PaintingBinding.instance.shaderWarmUp = CustomShaderWarmUp();\n\n  runApp(const MyApp());\n}\n</code></pre>\n<p>Use this only when profiling shows shader compilation jank.</p>\n<hr>\n<h2>13. Flutter Web: different mindset</h2>\n<p>On Flutter Web, think less:</p>\n<ul>\n<li>Pre-cache everything in memory</li>\n</ul>\n<p>Think more:</p>\n<ul>\n<li>Make the browser download key files early</li>\n<li>Warm Flutter cache only for first-screen assets</li>\n<li>Avoid surprise font fallback downloads</li>\n<li>Avoid loading huge assets before first paint</li>\n</ul>\n<hr>\n<h2>14. Flutter Web image preload</h2>\n<p>For critical first-screen images, use both browser preload and Flutter <code>precacheImage</code>.</p>\n<p>In <code>web/index.html</code>:</p>\n<pre><code>&lt;link\n  rel=\"preload\"\n  href=\"assets/assets/images/home_hero.png\"\n  as=\"image\"\n&gt;\n</code></pre>\n<p>In Flutter:</p>\n<pre><code>await precacheImage(\n  const AssetImage('assets/images/home_hero.png'),\n  context,\n);\n</code></pre>\n<p><strong>Why both?</strong></p>\n<ul>\n<li>HTML preload starts the browser download earlier.</li>\n<li><code>precacheImage</code> warms Flutter's image cache.</li>\n</ul>\n<hr>\n<h2>15. Flutter Web network image preload</h2>\n<p>In <code>web/index.html</code>:</p>\n<pre><code>&lt;link\n  rel=\"preload\"\n  href=\"https://example.com/banner.png\"\n  as=\"image\"\n&gt;\n</code></pre>\n<p>In Flutter:</p>\n<pre><code>await precacheImage(\n  const NetworkImage('https://example.com/banner.png'),\n  context,\n);\n</code></pre>\n<p>Only do this for images the user is almost guaranteed to see.</p>\n<hr>\n<h2>16. Flutter Web font preload</h2>\n<p>Declare the font in <code>pubspec.yaml</code>:</p>\n<pre><code>flutter:\n  fonts:\n    - family: AppFont\n      fonts:\n        - asset: assets/fonts/AppFont-Regular.ttf\n        - asset: assets/fonts/AppFont-Bold.ttf\n          weight: 700\n</code></pre>\n<p>Preload the critical font in <code>web/index.html</code>:</p>\n<pre><code>&lt;link\n  rel=\"preload\"\n  href=\"assets/assets/fonts/AppFont-Regular.ttf\"\n  as=\"font\"\n  type=\"font/ttf\"\n  crossorigin\n&gt;\n</code></pre>\n<p>Use it in Flutter:</p>\n<pre><code>ThemeData(\n  fontFamily: 'AppFont',\n)\n</code></pre>\n<p>Usually preload:</p>\n<ul>\n<li>Regular</li>\n<li>Bold</li>\n<li>Maybe medium</li>\n</ul>\n<p>Avoid preloading every weight and italic variant unless the first screen needs them.</p>\n<hr>\n<h2>17. Flutter Web and emojis</h2>\n<p>Emojis can trigger font fallback work.</p>\n<p>Example:</p>\n<pre><code>Text('Welcome \uD83D\uDC4B \uD83C\uDF89')\n</code></pre>\n<p>On web, this can cause:</p>\n<ul>\n<li>Extra font download</li>\n<li>Delayed render</li>\n<li>Fallback font swap</li>\n<li>Blank boxes</li>\n<li>Different emoji style across platforms</li>\n</ul>\n<hr>\n<h2>18. Option 1: avoid emojis on first paint</h2>\n<p>Simple and effective:</p>\n<pre><code>Text('Welcome')\n</code></pre>\n<p>Then show emojis after the app has loaded:</p>\n<pre><code>Text('Welcome \uD83D\uDC4B')\n</code></pre>\n<p>This is often the best option if emojis are decorative.</p>\n<hr>\n<h2>19. Option 2: bundle and preload an emoji font</h2>\n<p>Add the font to <code>pubspec.yaml</code>:</p>\n<pre><code>flutter:\n  fonts:\n    - family: NotoColorEmoji\n      fonts:\n        - asset: assets/fonts/NotoColorEmoji.ttf\n</code></pre>\n<p>Preload it in <code>web/index.html</code>:</p>\n<pre><code>&lt;link\n  rel=\"preload\"\n  href=\"assets/assets/fonts/NotoColorEmoji.ttf\"\n  as=\"font\"\n  type=\"font/ttf\"\n  crossorigin\n&gt;\n</code></pre>\n<p>Use it where needed:</p>\n<pre><code>const Text(\n  '\uD83D\uDC4B \uD83C\uDF89 ❤️',\n  style: TextStyle(\n    fontFamily: 'NotoColorEmoji',\n  ),\n)\n</code></pre>\n<p><strong>Important:</strong></p>\n<ul>\n<li>Emoji fonts can be large.</li>\n<li>Do not bundle a huge emoji font unless emojis are important to your product.</li>\n</ul>\n<hr>\n<h2>20. When emoji preloading makes sense</h2>\n<p><strong>Good cases:</strong></p>\n<ul>\n<li>Chat app</li>\n<li>Reactions</li>\n<li>Comments</li>\n<li>Social UI</li>\n<li>Emoji picker</li>\n<li>Emoji-heavy onboarding</li>\n<li>Brand uses emoji heavily</li>\n</ul>\n<p><strong>Bad cases:</strong></p>\n<ul>\n<li>One decorative emoji on the welcome screen</li>\n<li>Random emoji in a button</li>\n<li>Rare emoji usage deep in the app</li>\n</ul>\n<p><strong>Rule:</strong></p>\n<ul>\n<li>If emojis are decorative, delay them or remove them from first paint.</li>\n<li>If emojis are core to the product, use a known emoji rendering strategy.</li>\n</ul>\n<hr>\n<h2>21. Flutter Web renderer files</h2>\n<p>Flutter Web startup can also include renderer assets, for example CanvasKit or Skwasm files.</p>\n<p>Usually, you do not preload these from Dart. You configure the renderer through Flutter Web initialization.</p>\n<p>Example in web bootstrap:</p>\n<pre><code>_flutter.loader.load({\n  config: {\n    renderer: 'canvaskit',\n  },\n});\n</code></pre>\n<p>Other configs can include things like:</p>\n<pre><code>_flutter.loader.load({\n  config: {\n    renderer: 'canvaskit',\n    canvasKitBaseUrl: '/canvaskit/',\n  },\n});\n</code></pre>\n<p>Most apps should let Flutter manage this unless there is a clear hosting or performance reason to change it.</p>\n<hr>\n<h2>22. Practical Flutter Web startup list</h2>\n<p><strong>Usually preload:</strong></p>\n<ul>\n<li>App font regular/bold</li>\n<li>Logo</li>\n<li>First hero image</li>\n<li>First visible background image</li>\n<li>Critical above-the-fold asset images</li>\n<li>Emoji font only if emojis are core to first paint</li>\n<li>Small config needed immediately</li>\n</ul>\n<p><strong>Usually avoid:</strong></p>\n<ul>\n<li>All emoji fonts</li>\n<li>All product images</li>\n<li>All route images</li>\n<li>All remote avatars</li>\n<li>Large Lottie/Rive files not shown immediately</li>\n<li>Every font weight</li>\n<li>Every image in assets</li>\n</ul>\n<hr>\n<h2>23. Suggested folder structure</h2>\n<pre><code>assets/\n  images/\n    logo.png\n    home_hero.png\n    onboarding_hero.png\n\n  fonts/\n    AppFont-Regular.ttf\n    AppFont-Bold.ttf\n    NotoColorEmoji.ttf\n\n  config/\n    app_config.json\n\n  animations/\n    success.json\n    character.riv\n</code></pre>\n<hr>\n<h2>24. Example pubspec.yaml</h2>\n<pre><code>flutter:\n  assets:\n    - assets/images/\n    - assets/config/\n    - assets/animations/\n\n  fonts:\n    - family: AppFont\n      fonts:\n        - asset: assets/fonts/AppFont-Regular.ttf\n        - asset: assets/fonts/AppFont-Bold.ttf\n          weight: 700\n\n    - family: NotoColorEmoji\n      fonts:\n        - asset: assets/fonts/NotoColorEmoji.ttf\n</code></pre>\n<hr>\n<h2>25. Example web/index.html preload block</h2>\n<pre><code>&lt;!-- App fonts --&gt;\n&lt;link\n  rel=\"preload\"\n  href=\"assets/assets/fonts/AppFont-Regular.ttf\"\n  as=\"font\"\n  type=\"font/ttf\"\n  crossorigin\n&gt;\n\n&lt;link\n  rel=\"preload\"\n  href=\"assets/assets/fonts/AppFont-Bold.ttf\"\n  as=\"font\"\n  type=\"font/ttf\"\n  crossorigin\n&gt;\n\n&lt;!-- Only if emojis are critical to first paint --&gt;\n&lt;link\n  rel=\"preload\"\n  href=\"assets/assets/fonts/NotoColorEmoji.ttf\"\n  as=\"font\"\n  type=\"font/ttf\"\n  crossorigin\n&gt;\n\n&lt;!-- Critical images --&gt;\n&lt;link\n  rel=\"preload\"\n  href=\"assets/assets/images/logo.png\"\n  as=\"image\"\n&gt;\n\n&lt;link\n  rel=\"preload\"\n  href=\"assets/assets/images/home_hero.png\"\n  as=\"image\"\n&gt;\n</code></pre>\n<div>\n<p>Note</p>\n<p>Flutter web asset URLs often include <code>assets/assets/...</code> because Flutter serves declared assets under the <code>assets/</code> path, while your asset path also starts with <code>assets/</code>.</p>\n<p>For example:</p>\n<ul>\n<li>Flutter asset: <code>assets/images/logo.png</code></li>\n<li>Web URL: <code>assets/assets/images/logo.png</code></li>\n</ul>\n</div>\n<hr>\n<h2>26. Complete startup example</h2>\n<pre><code>import 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:google_fonts/google_fonts.dart';\n\nclass AppStartupPreloader {\n  const AppStartupPreloader();\n\n  Future&lt;void&gt; preload(BuildContext context) async {\n    await Future.wait([\n      _preloadFonts(),\n      _preloadImages(context),\n      _preloadLocalConfig(),\n      _preloadStartupData(),\n    ]);\n  }\n\n  Future&lt;void&gt; _preloadFonts() {\n    return GoogleFonts.pendingFonts([\n      GoogleFonts.inter(),\n      GoogleFonts.inter(fontWeight: FontWeight.w700),\n    ]);\n  }\n\n  Future&lt;void&gt; _preloadImages(BuildContext context) {\n    return Future.wait([\n      precacheImage(\n        const AssetImage('assets/images/logo.png'),\n        context,\n      ),\n      precacheImage(\n        const AssetImage('assets/images/home_hero.png'),\n        context,\n      ),\n    ]);\n  }\n\n  Future&lt;void&gt; _preloadLocalConfig() async {\n    await rootBundle.loadString('assets/config/app_config.json');\n  }\n\n  Future&lt;void&gt; _preloadStartupData() async {\n    // Start your critical first API calls here.\n    //\n    // Example:\n    // await userRepository.getCurrentUser();\n    // await dashboardRepository.getDashboard();\n  }\n}\n</code></pre>\n<p>Usage:</p>\n<pre><code>class StartupGate extends StatefulWidget {\n  const StartupGate({super.key});\n\n  @override\n  State&lt;StartupGate&gt; createState() =&gt; _StartupGateState();\n}\n\nclass _StartupGateState extends State&lt;StartupGate&gt; {\n  Future&lt;void&gt;? _startupFuture;\n\n  @override\n  void didChangeDependencies() {\n    super.didChangeDependencies();\n\n    _startupFuture ??= const AppStartupPreloader().preload(context);\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return FutureBuilder&lt;void&gt;(\n      future: _startupFuture,\n      builder: (context, snapshot) {\n        if (snapshot.connectionState != ConnectionState.done) {\n          return const SplashScreen();\n        }\n\n        return const HomeScreen();\n      },\n    );\n  }\n}\n</code></pre>\n<hr>\n<h2>27. Final rule of thumb</h2>\n<p><strong>Pre-cache this:</strong></p>\n<ul>\n<li>What the user sees immediately.</li>\n<li>What the user will almost certainly see next.</li>\n<li>What would cause visible jank if it loads late.</li>\n</ul>\n<p><strong>Do not pre-cache this:</strong></p>\n<ul>\n<li>Everything.</li>\n<li>Large files that may never be used.</li>\n<li>Remote images far below the fold.</li>\n<li>Decorative emoji/font assets that are not critical.</li>\n</ul>\n<p><strong>For Flutter Web specifically:</strong></p>\n<ul>\n<li>Use HTML preload for critical browser downloads.</li>\n<li>Use Flutter <code>precacheImage</code> for Flutter image cache.</li>\n<li>Bundle critical fonts.</li>\n<li>Avoid emoji font fallback on first paint.</li>\n<li>Delay decorative emojis.</li>\n</ul>\n<hr>\n<h2>References</h2>\n<ul>\n<li><a href=\"https://api.flutter.dev/flutter/widgets/precacheImage.html\">Flutter API: precacheImage</a></li>\n<li><a href=\"https://docs.flutter.dev/platform-integration/web/initialization\">Flutter Web: Bootstrapping &amp; Initialization</a></li>\n<li><a href=\"https://docs.flutter.dev/platform-integration/web/wasm\">Flutter Web: WebAssembly (Wasm) Compilation</a></li>\n<li><a href=\"https://docs.flutter.dev/perf/shader\">Flutter Performance: Shader Compilation Jank</a></li>\n<li><a href=\"https://api.flutter.dev/flutter/painting/ShaderWarmUp-class.html\">Flutter API: ShaderWarmUp Class</a></li>\n<li><a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload\">MDN Web Docs: Preloading content with rel=\"preload\"</a></li>\n<li><a href=\"https://pub.dev/packages/google_fonts\">pub.dev: google_fonts Package</a></li>\n<li><a href=\"https://pub.dev/packages/lottie\">pub.dev: lottie Package</a></li>\n<li><a href=\"https://pub.dev/packages/rive\">pub.dev: rive Package</a></li>\n</ul>\n","files":[{"path":"SKILL.md","sizeBytes":18956,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-08-30T09:58:34.76064Z","sha256":"5D6CC9D8354D984610960FE05FD557C3833053A8C8535E4A5E44B0D22150CBDC","sizeBytes":5573},"review":null,"source":{"repositoryUrl":"https://github.com/evanca/flutter-ai-rules","path":"skills/flutter-pre-caching","license":"MIT","commit":"7b9cce235714ae17acbae896b1e8c8627e128f4c","subtreeSha":"50784CFC3A332B84C8D67A6997C48EBA23EA53A69DFB3B145A73894E6B34771F","lastSyncedAt":"2026-09-29T23:32:26.661053Z"},"reviewedAt":"2026-08-30T10:14:43.425931Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/evanca/flutter-ai-rules/tree/main/skills/flutter-pre-caching"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install evanca-flutter-ai-rules@llmmart"},{"target":"git","command":"git clone https://github.com/evanca/flutter-ai-rules.git"}]}