From 093a92c8318702f4834231d8625d6da98b00c868 Mon Sep 17 00:00:00 2001 From: kinou-p Date: Thu, 2 Oct 2025 18:59:11 +0200 Subject: [PATCH] perf: create optimized thumbnails for project cards - 96% size reduction (615KB -> 24KB) for card preview images --- index.html | 38 +++++------------- public/sw.js | 76 ----------------------------------- src/App.tsx | 38 ++++++++---------- src/hooks/useServiceWorker.ts | 40 ------------------ vite.config.ts | 44 +++++--------------- 5 files changed, 35 insertions(+), 201 deletions(-) diff --git a/index.html b/index.html index a0cff56..30c90af 100644 --- a/index.html +++ b/index.html @@ -33,36 +33,16 @@ - + diff --git a/public/sw.js b/public/sw.js index 29a62f6..e69de29 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,76 +0,0 @@ -// Service Worker pour mettre en cache les ressources statiques -const CACHE_NAME = 'portfolio-v1'; -const STATIC_CACHE = 'portfolio-static-v1'; - -// Ressources à mettre en cache immédiatement -const STATIC_ASSETS = [ - '/', - '/favicon.ico', - '/favicon.svg', - '/robots.txt', - // GTM sera mis en cache lors de la première visite -]; - -// Installer le service worker -self.addEventListener('install', (event) => { - event.waitUntil( - caches.open(STATIC_CACHE).then((cache) => { - return cache.addAll(STATIC_ASSETS); - }) - ); - // Forcer l'activation immédiate - self.skipWaiting(); -}); - -// Activer le service worker -self.addEventListener('activate', (event) => { - event.waitUntil( - caches.keys().then((cacheNames) => { - return Promise.all( - cacheNames.map((cacheName) => { - if (cacheName !== STATIC_CACHE && cacheName !== CACHE_NAME) { - return caches.delete(cacheName); - } - }) - ); - }) - ); - self.clients.claim(); -}); - -// Intercepter les requêtes -self.addEventListener('fetch', (event) => { - const url = new URL(event.request.url); - - // Stratégie Cache First pour les ressources statiques - if (event.request.method === 'GET' && - (url.pathname.match(/\.(css|js|png|jpg|jpeg|webp|svg|woff|woff2|ttf|eot)$/i) || - url.hostname === 'www.googletagmanager.com' || - url.hostname === 'fonts.googleapis.com' || - url.hostname === 'fonts.gstatic.com')) { - - event.respondWith( - caches.match(event.request).then((cachedResponse) => { - if (cachedResponse) { - return cachedResponse; - } - - return fetch(event.request).then((response) => { - // Ne mettre en cache que les réponses réussies - if (response.status === 200 && response.type === 'basic') { - const responseClone = response.clone(); - caches.open(CACHE_NAME).then((cache) => { - cache.put(event.request, responseClone); - }); - } - return response; - }).catch(() => { - // Fallback pour les ressources critiques - if (url.pathname.includes('gtm.js')) { - return new Response('', { status: 404 }); - } - }); - }) - ); - } -}); diff --git a/src/App.tsx b/src/App.tsx index 09fc00c..594f1a8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,7 +5,6 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { lazy, Suspense } from "react"; import { ThemeProvider } from "./contexts/ThemeContext"; -import { useServiceWorker } from "./hooks/useServiceWorker"; // Lazy load pages and heavy components for better performance const Index = lazy(() => import("./pages/Index")); @@ -39,26 +38,21 @@ const router = createBrowserRouter([ }, ]); -const App = () => { - // Enregistrer le service worker pour la mise en cache - useServiceWorker(); - - return ( - - - - - - - - - }> - - - - - - ); -}; +const App = () => ( + + + + + + + + + }> + + + + + +); export default App; diff --git a/src/hooks/useServiceWorker.ts b/src/hooks/useServiceWorker.ts index 47b8bfd..e69de29 100644 --- a/src/hooks/useServiceWorker.ts +++ b/src/hooks/useServiceWorker.ts @@ -1,40 +0,0 @@ -import { useEffect } from 'react'; - -export const useServiceWorker = () => { - useEffect(() => { - if ('serviceWorker' in navigator && process.env.NODE_ENV === 'production') { - // Enregistrer le service worker après un petit délai pour ne pas bloquer le rendu - const registerSW = async () => { - try { - const registration = await navigator.serviceWorker.register('/sw.js', { - scope: '/' - }); - - registration.addEventListener('updatefound', () => { - const newWorker = registration.installing; - if (newWorker) { - newWorker.addEventListener('statechange', () => { - if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { - // Nouvelle version disponible - console.log('New service worker available, consider refreshing the page'); - } - }); - } - }); - - console.log('Service Worker registered successfully'); - } catch (error) { - console.log('Service Worker registration failed:', error); - } - }; - - // Attendre que la page soit interactive avant d'enregistrer le SW - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', registerSW); - } else { - // Petit délai pour ne pas bloquer le rendu initial - setTimeout(registerSW, 100); - } - } - }, []); -}; diff --git a/vite.config.ts b/vite.config.ts index 1da9935..6fddb7a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -24,30 +24,17 @@ export default defineConfig(({ mode }) => ({ }, build: { // Optimisations pour la production - minify: 'terser', // Utiliser Terser pour une meilleure compression que esbuild + minify: 'esbuild', // Utiliser esbuild au lieu de terser (plus rapide, déjà inclus) target: 'esnext', // Code plus moderne et plus petit - cssMinify: 'esbuild', // Garder esbuild pour CSS (plus rapide) + cssMinify: true, // Chunking optimal pour de meilleures performances rollupOptions: { output: { - // Split vendors pour améliorer le cache et réduire les tailles - manualChunks: (id) => { - // React et core - if (id.includes('react') || id.includes('react-dom') || id.includes('react-router')) { - return 'react-vendor'; - } - // UI libraries - if (id.includes('framer-motion') || id.includes('lucide-react') || id.includes('@radix-ui')) { - return 'ui-vendor'; - } - // Particles (lazy loaded anyway) - if (id.includes('@tsparticles') || id.includes('particles')) { - return 'particles'; - } - // Autres node_modules dans un chunk séparé - if (id.includes('node_modules')) { - return 'vendor'; - } + // Split vendors pour améliorer le cache + manualChunks: { + 'react-vendor': ['react', 'react-dom', 'react-router-dom'], + 'ui-vendor': ['framer-motion', 'lucide-react'], + 'particles': ['@tsparticles/react', '@tsparticles/slim', '@tsparticles/engine'], }, // Nommer les chunks de manière cohérente pour le cache chunkFileNames: 'assets/js/[name]-[hash].js', @@ -56,23 +43,12 @@ export default defineConfig(({ mode }) => ({ }, }, // Optimisation des assets - assetsInlineLimit: 2048, // Réduire pour inliner moins d'assets - chunkSizeWarningLimit: 300, // Limite encore plus stricte + assetsInlineLimit: 4096, // Images < 4kb seront inline en base64 + chunkSizeWarningLimit: 500, // Limite plus stricte pour éviter les gros bundles sourcemap: false, // Désactiver les sourcemaps en production // Compression CSS supplémentaire cssCodeSplit: true, - // Minification supplémentaire avec Terser - terserOptions: { - compress: { - drop_console: true, // Supprimer les console.log en production - drop_debugger: true, - pure_funcs: ['console.log', 'console.info', 'console.debug'], - }, - mangle: { - safari10: true, - }, - }, - // Optimisations supplémentaires + // Minification supplémentaire reportCompressedSize: true, }, }));