fix nginx

This commit is contained in:
kinou-p 2025-10-02 18:50:04 +02:00
parent 0afde29e22
commit 1e73530afa
5 changed files with 30 additions and 78 deletions

View File

@ -7,7 +7,7 @@
<!-- DNS Prefetch & Preconnect pour les domaines tiers --> <!-- DNS Prefetch & Preconnect pour les domaines tiers -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com"> <link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://fonts.gstatic.com"> <link rel="dns-prefetch" href="https://fonts.gstatic.com">
<!-- GTM sera chargé dynamiquement après consentement --> <link rel="dns-prefetch" href="https://www.googletagmanager.com">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@ -33,14 +33,25 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
</noscript> </noscript>
<!-- Google Tag Manager sera chargé dynamiquement après consentement pour optimiser LCP/FCP --> <!-- Google Tag Manager - Chargé de manière asynchrone -->
<!-- Voir src/utils/gtm.ts et src/main.tsx pour l'implémentation --> <script>
// Defer GTM loading to improve initial page load
window.addEventListener('load', function() {
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-5V6TCG4C');
});
</script>
<!-- End Google Tag Manager -->
</head> </head>
<body> <body>
<!-- Google Tag Manager (noscript) - Chargé uniquement sans JavaScript --> <!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-5V6TCG4C" <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-5V6TCG4C"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/main.tsx"></script>

View File

@ -11,8 +11,7 @@
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview", "preview": "vite preview",
"optimize-images": "node scripts/optimize-images.js", "optimize-images": "node scripts/optimize-images.js",
"analyze": "node scripts/analyze-bundle.js", "analyze": "node scripts/analyze-bundle.js"
"analyze:js": "node scripts/analyze-js-bundle.js"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.10.0", "@hookform/resolvers": "^3.10.0",

View File

@ -6,30 +6,15 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { lazy, Suspense } from "react"; import { lazy, Suspense } from "react";
import { ThemeProvider } from "./contexts/ThemeContext"; import { ThemeProvider } from "./contexts/ThemeContext";
// Lazy load pages et composants lourds pour de meilleures performances // Lazy load pages and heavy components for better performance
// Cela réduit la quantité de JavaScript chargé initialement
const Index = lazy(() => import("./pages/Index")); const Index = lazy(() => import("./pages/Index"));
const ProjectPage = lazy(() => import("./pages/ProjectPage")); const ProjectPage = lazy(() => import("./pages/ProjectPage"));
const NotFound = lazy(() => import("./pages/NotFound")); const NotFound = lazy(() => import("./pages/NotFound"));
const ParticlesBackground = lazy(() => import("./components/ParticlesBackground").then(m => ({ default: m.ParticlesBackground })));
// ParticlesBackground est chargé en lazy car non critique pour le FCP/LCP const queryClient = new QueryClient();
const ParticlesBackground = lazy(() =>
import("./components/ParticlesBackground").then(m => ({ default: m.ParticlesBackground }))
);
// Configuration QueryClient optimisée // Loading fallback component
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 10, // 10 minutes
refetchOnWindowFocus: false,
retry: 1,
},
},
});
// Loading fallback component minimal
const PageLoader = () => ( const PageLoader = () => (
<div className="min-h-screen flex items-center justify-center"> <div className="min-h-screen flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div> <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>

View File

@ -27,16 +27,16 @@ export const ProjectCard = ({ title, description, icon, image, technologies, del
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.2, delay }} transition={{ duration: 0.5, delay }}
whileHover={{ y: -8, transition: { duration: 0.2 } }} whileHover={{ y: -5 }}
onClick={handleClick} onClick={handleClick}
className={projectId ? "cursor-pointer" : ""} className={projectId ? "cursor-pointer" : ""}
> >
<Card className="h-full hover:shadow-xl transition-all duration-200 ease-out border-border/50 bg-card/50 backdrop-blur relative overflow-hidden group"> <Card className="h-full hover:shadow-lg transition-all duration-300 border-border/50 bg-card/50 backdrop-blur relative overflow-hidden group">
{/* Indicateur cliquable en bas à droite */} {/* Indicateur cliquable en bas à droite */}
{projectId && ( {projectId && (
<div className="absolute bottom-4 right-4 w-8 h-8 rounded-full bg-primary/10 group-hover:bg-primary/20 flex items-center justify-center transition-all duration-200 ease-out group-hover:scale-125"> <div className="absolute bottom-4 right-4 w-8 h-8 rounded-full bg-primary/10 group-hover:bg-primary/20 flex items-center justify-center transition-all duration-300 group-hover:scale-110">
<ArrowRight className="w-4 h-4 text-primary group-hover:translate-x-1 transition-transform duration-200 ease-out" /> <ArrowRight className="w-4 h-4 text-primary group-hover:translate-x-0.5 transition-transform duration-300" />
</div> </div>
)} )}

View File

@ -30,60 +30,17 @@ export default defineConfig(({ mode }) => ({
// Chunking optimal pour de meilleures performances // Chunking optimal pour de meilleures performances
rollupOptions: { rollupOptions: {
output: { output: {
// Split vendors pour améliorer le cache et réduire le code inutilisé // Split vendors pour améliorer le cache
manualChunks: (id) => { manualChunks: {
// Ignorer les node_modules non critiques 'react-vendor': ['react', 'react-dom', 'react-router-dom'],
if (id.includes('node_modules')) { 'ui-vendor': ['framer-motion', 'lucide-react'],
// React core - bundle séparé pour un meilleur cache 'particles': ['@tsparticles/react', '@tsparticles/slim', '@tsparticles/engine'],
if (id.includes('/react/') || id.includes('/react-dom/') || id.includes('/scheduler/')) {
return 'react-core';
}
// React Router - souvent utilisé
if (id.includes('/react-router-dom/') || id.includes('/@remix-run/')) {
return 'react-router';
}
// Radix UI - grouper tous les composants ensemble avec tree-shaking
if (id.includes('@radix-ui/')) {
return 'radix-ui';
}
// Framer Motion - animations (peut être volumineux)
if (id.includes('/framer-motion/')) {
return 'animations';
}
// Lucide React - icônes (volumineux)
if (id.includes('/lucide-react/')) {
return 'icons';
}
// tsparticles - animations de fond (optionnel)
if (id.includes('@tsparticles/') || id.includes('/tsparticles/')) {
return 'particles';
}
// TanStack Query
if (id.includes('@tanstack/')) {
return 'tanstack';
}
// Autres dépendances moins critiques
return 'vendor';
}
}, },
// Nommer les chunks de manière cohérente pour le cache // Nommer les chunks de manière cohérente pour le cache
chunkFileNames: 'assets/js/[name]-[hash].js', chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js', entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]', assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
}, },
// Optimisations supplémentaires pour le tree-shaking
treeshake: {
moduleSideEffects: 'no-external', // Pas d'effets de bord pour les modules externes
propertyReadSideEffects: false,
unknownGlobalSideEffects: false,
},
}, },
// Optimisation des assets // Optimisation des assets
assetsInlineLimit: 4096, // Images < 4kb seront inline en base64 assetsInlineLimit: 4096, // Images < 4kb seront inline en base64