Retour à la Knowledge Base
Breakthrough

desktop-app-splash-screen

Mars 2026·Projet : EVQuest (Tauri v2 + React 18 + FastAPI)
TauriReactFastAPINSIS

Vécu projet : EVQuest (Tauri v2 + React 18 + FastAPI) Date : Mars 2026 Stack : Tauri v2, React 18, FastAPI, NSIS


Contexte

EVQuest : l'app s'appelait "EVQuest" mais affichait "Poker MTT Coach" dans les titres. Pas de splash screen — flash blanc puis écran de connexion basique.

Solution appliquée :

  1. Nettoyage de toutes les références "Poker MTT Coach" (4 fichiers)
  2. Splash HTML natif (400×300, fond sombre, logo + 3 dots pulse)
  3. Splash React embelli (logo, barre de progression, fade-in)
  4. Commande close_splashscreen sans plugin externe

Piège rencontré : après implémentation, l'app ne se lançait plus. Le on_window_event tuait FastAPI quand splash.close() se déclenchait. Fix : ajout d'un guard if window.label() != "main" { return; }.

Résultat : double-clic → splash instantané → transition fluide → app complète. Titre "EVQuest" partout, zéro flash blanc.


Problème

Au lancement d'une app Tauri avec backend (FastAPI, Express, etc.), l'utilisateur voit :

  1. Flash blanc/noir (~0.5-1s) — Tauri crée la fenêtre, le webview charge React
  2. Écran de connexion (~1-7s) — React attend que le backend réponde
  3. App complète — tout est prêt

Le pattern hybride élimine le flash en ajoutant un splash HTML natif (couche 1) avant React, puis transition vers un splash React embelli (couche 2) pendant la connexion.

Séquence résultante : Double-clic → splash natif instantané → React monte (invisible) → splash natif se ferme, fenêtre principale apparaît avec le splash React → backend connecté → app complète.


Points d'attention

1. on_window_event tue le backend (CRITIQUE)

C'est le piège le plus dangereux et le plus silencieux. Si le code Rust utilise on_window_event pour tuer le backend quand la fenêtre se ferme, il se déclenche aussi quand le splash se ferme.

// BUG : tue le backend quand splash.close() est appelé
.on_window_event(|window, event| {
    if let tauri::WindowEvent::CloseRequested { .. } = event {
        kill_backend(); // ← se déclenche pour TOUTE fenêtre
    }
})

// FIX : guard sur le label
.on_window_event(|window, event| {
    if let tauri::WindowEvent::CloseRequested { .. } = event {
        if window.label() != "main" { return; }
        kill_backend(); // ← seulement la fenêtre principale
    }
})

Symptôme : l'app affiche "Impossible de se connecter au backend" alors que tout compilait. Le backend a été tué par la fermeture du splash.

Règle : dès qu'on passe en multi-fenêtres, tout handler d'événement doit filtrer par window.label().

2. Ordre visible: false + show()

La fenêtre principale doit démarrer invisible ("visible": false dans tauri.conf.json), puis être rendue visible par une commande Tauri quand React est prêt. Si on oublie le visible: false, les deux fenêtres apparaissent simultanément.

Règle : la fenêtre main commence invisible, le splash est visible. La commande close_splashscreen ferme le splash ET montre le main.

3. Splash URL : où placer le HTML

Le fichier HTML du splash doit être accessible par le webview Tauri. La méthode la plus simple : le placer dans frontend/public/ (copié tel quel dans dist/ par Vite). L'URL dans la config Tauri est alors simplement "splashscreen.html".

Règle : frontend/public/splashscreen.html → URL Tauri "splashscreen.html". Pas besoin de chemin absolu ou de config spéciale.


Solution

Étape 1 — Créer le splash HTML statique

Fichier autonome, léger, pur HTML/CSS (pas de JS). Doit correspondre au branding de l'app.

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8" />
  <title>MonApp</title>
  <style>
    body {
      background: #0a0e1a;
      color: #f9fafb;
      font-family: 'Segoe UI', system-ui, sans-serif;
      display: flex;
      align-items: center;
      justify-content: center;
      height: 100vh;
      margin: 0;
      user-select: none;
    }
    .splash { text-align: center; animation: fadeIn 0.4s ease-out; }
    .logo { width: 80px; height: 80px; margin: 0 auto 16px; }
    .title { font-size: 28px; font-weight: 700; color: #08c8e6; letter-spacing: 2px; margin-bottom: 6px; }
    .tagline { font-size: 13px; color: #9ca3af; margin-bottom: 28px; }
    .dots { display: flex; gap: 8px; justify-content: center; }
    .dot { width: 8px; height: 8px; border-radius: 50%; background: #08c8e6; animation: pulse 1.2s ease-in-out infinite; }
    .dot:nth-child(2) { animation-delay: 0.2s; }
    .dot:nth-child(3) { animation-delay: 0.4s; }
    @keyframes pulse {
      0%, 80%, 100% { opacity: 0.2; transform: scale(0.8); }
      40% { opacity: 1; transform: scale(1); }
    }
    @keyframes fadeIn {
      from { opacity: 0; transform: translateY(8px); }
      to { opacity: 1; transform: translateY(0); }
    }
  </style>
</head>
<body>
  <div class="splash">
    <img class="logo" src="logo-128.png" alt="" />
    <div class="title">MonApp</div>
    <div class="tagline">Ma tagline</div>
    <div class="dots"><div class="dot"></div><div class="dot"></div><div class="dot"></div></div>
  </div>
</body>
</html>

Placer dans frontend/public/splashscreen.html. Le logo doit aussi être dans frontend/public/.

Étape 2 — Configurer les 2 fenêtres dans tauri.conf.json

{
  "app": {
    "windows": [
      {
        "label": "main",
        "title": "MonApp",
        "width": 1280,
        "height": 800,
        "visible": false,
        "maximized": true
      },
      {
        "label": "splashscreen",
        "url": "splashscreen.html",
        "title": "MonApp",
        "width": 400,
        "height": 300,
        "resizable": false,
        "decorations": false,
        "center": true,
        "alwaysOnTop": true
      }
    ]
  }
}

Points clés :

  • "label": "main" — nécessaire pour identifier la fenêtre dans le Rust
  • "visible": false — la fenêtre principale est cachée au départ
  • "decorations": false — pas de barre de titre sur le splash
  • "center": true — centré à l'écran

Étape 3 — Commande close_splashscreen côté Rust

use tauri::Manager; // nécessaire pour get_webview_window

#[tauri::command]
fn close_splashscreen(app: tauri::AppHandle) {
    if let Some(splash) = app.get_webview_window("splashscreen") {
        let _ = splash.close();
    }
    if let Some(main) = app.get_webview_window("main") {
        let _ = main.show();
    }
}

// Enregistrer dans le builder :
.invoke_handler(tauri::generate_handler![..., close_splashscreen])

Pas de dépendance supplémentaire — get_webview_window est dans l'API Tauri v2 native (trait Manager).

Étape 4 — Appel depuis React + transition

Dans le composant qui monte en premier (layout, connection screen, etc.) :

useEffect(() => {
  import("@tauri-apps/api/core")
    .then(({ invoke }) => invoke("close_splashscreen"))
    .catch(() => {});
}, []);

L'import dynamique est intentionnel : permet au frontend de fonctionner aussi en mode navigateur seul (npm run dev) sans erreur — l'import échoue silencieusement via .catch(() => {}).

Étape 5 — Branding cohérent (titres, taglines)

Vérifier que le nom de l'app est cohérent partout. Voir la checklist ci-dessous.


Pourquoi

Le pattern hybride (splash natif + splash React) élimine le flash blanc au lancement sans ajouter de dépendance externe. La commande close_splashscreen utilise l'API Tauri v2 native (get_webview_window via le trait Manager) — pas de plugin tiers. L'import dynamique de @tauri-apps/api/core permet au frontend de fonctionner aussi en mode navigateur seul.


Checklist branding desktop

Complémentaire à desktop-app-icons.md (qui couvre icônes, .ico, taskbar).

Élément Fichier(s) Vérification
Titre fenêtre Windows tauri.conf.json → windows[].title Apparaît dans la barre de titre et la taskbar
Titre HTML <title> frontend/index.html Apparaît dans l'onglet si ouvert en navigateur
Tagline/sous-titre UI Composant layout principal Cohérent avec le splash
Description package Rust src-tauri/Cargo.toml → description Metadata de l'exécutable
productName tauri.conf.json Nom du .exe généré
Icône .exe / taskbar src-tauri/icons/ Voir desktop-app-icons.md
Favicon navigateur frontend/public/favicon.ico Pour le mode dev navigateur
Splash screen frontend/public/splashscreen.html Logo, titre, tagline cohérents
Installeur NSIS tauri.conf.json → bundle Nom affiché pendant l'installation

Règle : grep "Ancien Nom" dans *.{json,html,tsx,toml} pour vérifier qu'aucune référence obsolète ne reste.


Dernière mise à jour : mars 2026