Comment rendre une application Flutter Web optimisée SEO

Les applications Flutter Web fonctionnent comme des SPA (Single-Page Applications) :
au lieu de renvoyer une page HTML complète à chaque navigation, elles chargent une seule fois un index.html minimal, puis mettent à jour l’interface côté navigateur avec JavaScript.

Ce index.html initial contient très peu d’informations SEO.
Mais bonne nouvelle : Google est capable d’exécuter le JavaScript, d’attendre que l’application charge, puis d’indexer le DOM final modifié.

Autrement dit :
si votre application met à jour dynamiquement coté client le <title>, la meta description ou le contenu des pages, Google peut lire ces valeurs modifiées, exactement comme vous les voyez dans le panneau “Elements” du DevTools de Chrome.

Exemple concret :
Si vous changez la meta description en JavaScript :

document.querySelector('meta[name="description"]').setAttribute('content', 'Nouvelle description SEO');

Googlebot exécutera le script, verra la meta mise à jour, et pourra l’indexer.

Cependant, pour que le référencement fonctionne réellement bien dans une application Flutter Web, il est indispensable de soigner plusieurs points :

  • URLs propres (sans #/)
  • Mise à jour dynamique des balises <head> (title, description, canonical…)
  • sitemap.xml + robots.txt

Voici une mise en place éprouvée, simple à intégrer dans un projet Flutter Web en production, afin d’obtenir un comportement SEO comparable à une application web classique.

1. Avoir des URLs propres (sans #/)

Par défaut, Flutter Web utilise le hash (/#/page) pour gérer les routes.
Ce format fonctionne, mais il n’est pas optimal pour le SEO.

On active la URL Strategy en mode “path” (vraies URLs lisibles) :

import 'package:flutter/material.dart';
import 'package:flutter_web_plugins/url_strategy.dart';

void main() {
  usePathUrlStrategy(); //enlève #/ dans les URLs
  runApp(const MyApp());
}

Assurez-vous aussi que dans web/index.html la balise <base> est présente :

<base href="/">

2. Mettre à jour title et meta en Flutter

Comme Flutter gère le DOM après chargement, on doit modifier les metas depuis Dart

MetaManager (version Web)

lib/seo/meta_manager_web.dart

import 'dart:html' as html;

class MetaManager {
  static void setTitle(String title) => html.document.title = title;

  static void setDescription(String description) {
    _upsertMeta('description', description);
  }

  static void setCanonical(String url) {
    _upsertLink(rel: 'canonical', href: url);
  }

  static void _upsertMeta(String name, String content) {
    final el = html.document.head!.querySelector('meta[name="$name"]');
    if (el != null) {
      el.setAttribute('content', content);
      return;
    }
    html.document.head!.append(html.MetaElement()
      ..name = name
      ..content = content);
  }

  static void _upsertLink({required String rel, required String href}) {
    final el = html.document.head!.querySelector('link[rel="$rel"]');
    if (el != null) {
      el.setAttribute('href', href);
      return;
    }
    html.document.head!.append(html.LinkElement()
      ..rel = rel
      ..href = href);
  }
}

Version mobile (ne rien faire)

lib/seo/meta_manager_stub.dart

class MetaManager {
  static void setTitle(String _) {}
  static void setDescription(String _) {}
  static void setCanonical(String _) {}
}

Import conditionnel

lib/seo/meta_manager.dart

export 'meta_manager_stub.dart'
    if (dart.library.html) 'meta_manager_web.dart';

Résultat :

  • Sur Web → les balises <head> sont modifiées
  • Sur iOS / Android → aucune erreur

3. Appliquer le SEO automatiquement à chaque navigation

On utilise un NavigatorObserver pour mettre à jour les balises dès que l’utilisateur change de page.

lib/seo/seo_observer.dart

import 'package:flutter/material.dart';
import 'meta_manager.dart';

class SeoData {
  final String? title;
  final String? description;
  final String? canonicalUrl;

  const SeoData({this.title, this.description, this.canonicalUrl});
}

class SeoObserver extends NavigatorObserver {
  @override
  void didPush(Route route, Route<dynamic>? previousRoute) {
    _apply(route);
  }

  void _apply(Route route) {
    final seo = route.settings.arguments;
    if (seo is SeoData) {
      if (seo.title != null) MetaManager.setTitle(seo.title!);
      if (seo.description != null) MetaManager.setDescription(seo.description!);
      if (seo.canonicalUrl != null) MetaManager.setCanonical(seo.canonicalUrl!);
    }
  }
}

4. Centraliser le SEO dans les routes (GoRouter)

Chaque route renvoie un MaterialPage avec son SEO associé :

import 'package:go_router/go_router.dart';
import 'seo/seo_observer.dart';
import 'ui/home.dart';
import 'ui/product_list.dart';

const baseUrl = 'https://monsite.com';

final router = GoRouter(
  observers: [SeoObserver()],
  routes: [
    GoRoute(
      path: '/',
      pageBuilder: (context, state) => MaterialPage(
        key: state.pageKey,
        arguments: const SeoData(
          title: 'Accueil | mon site name',
          description: 'Découvrez notre catalogue, nos offres et nouveautés.',
          canonicalUrl: '$baseUrl/',
        ),
        child: const HomeScreen(),
      ),
    ),
    GoRoute(
      path: 'product-list/:categorie/:type',
      pageBuilder: (context, state) {
        final categorie = state.pathParameters['categorie']!;
        final type = state.pathParameters['type']!;
        return MaterialPage(
          key: state.pageKey,
          arguments: SeoData(
            title: '$type - $categorie | Bloopa',
            description: 'Tous les $type dans la catégorie $categorie.',
            canonicalUrl: '$baseUrl/product-list/$categorie/$type',
          ),
          child: ProductListScreen(
            categorie: categorie,
            typeproduit: type,
          ),
        );
      },
    ),
  ],
);

Avantage : pas besoin d’appeler MetaManager dans chaque page, c’est automatique.

6. Ajouter un Sitemap et un robots.txt

web/robots.txt

User-agent: *
Allow: /
Sitemap: https://monsite.com/sitemap.xml

La génération du sitemap.xml peut être :

  • manuelle
  • générée à la compilation (article à venir)
  • servie par une Cloud Function si catalogue dynamique (article à venir)

Conclusion

Avec ces quelques étapes :

  • URLs propres
  • Title / Description / Canonical mis à jour par page
  • Google indexe le contenu réel affiché au client
  • Pas de changement côté mobile

Comme Google est désormais capable d’exécuter le JavaScript et de lire le contenu final du DOM, une application Flutter Web peut être réellement SEO-friendly, à condition que les balises <head> soient mises à jour proprement.

Note importante : Les bots de Facebook, Twitter, LinkedIn, WhatsApp, Slack, etc. ne rendent PAS le JavaScript : ils ne verront donc pas les balises Open Graph ou Twitter Card modifiées côté client.
Pour que le partage social affiche correctement l’image, le titre et la description, il faut une stratégie différente, basée sur du pré-rendu (prerender) ou une page HTML statique par URL.

Un article séparé sera dédié à cette partie “Social Share” pour expliquer comment générer des pages pré-rendues compatibles avec Facebook/Twitter/LinkedIn.

2 thoughts on “Comment rendre une application Flutter Web optimisée SEO

  1. I really like what you guys are usuaally up too. Thiss sort of clever work and coverage!
    Keep up the fantastic worfks guys I’ve added you guy to mmy own blogroll.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *