Sfoglia il codice sorgente

perf: optimize page speed (caching, font loading, lazy loading) and fix admin ui bug

unknown 2 giorni fa
parent
commit
4e7163df2f
6 ha cambiato i file con 71 aggiunte e 8 eliminazioni
  1. 7 0
      index.html
  2. 37 2
      nginx.conf
  3. 24 3
      src/components/HeroSection.vue
  4. 2 1
      src/components/ModelUploadSection.vue
  5. 1 1
      src/index.css
  6. 0 1
      src/pages/Admin.vue

+ 7 - 0
index.html

@@ -4,7 +4,14 @@
     <meta charset="UTF-8" />
     <link rel="icon" type="image/png" href="/favicon.png" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    
+    <link rel="preconnect" href="https://fonts.googleapis.com">
+    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+    <link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Outfit:wght@400;500;600;700;800&display=swap">
+    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet" media="print" onload="this.media='all'">
+    
     <title>Radionica 3D | Professional 3D Printing in Montenegro</title>
+    <link rel="canonical" href="https://radionica3d.me/" />
     <meta name="description" content="Professional 3D printing and rapid prototyping services in Montenegro. Instant quotes, industrial materials (PLA, ABS, PETG, Resin), and high-precision results." />
     
     <!-- Open Graph / Facebook -->

+ 37 - 2
nginx.conf

@@ -6,10 +6,42 @@ server {
 
     # Gzip Compression
     gzip on;
-    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
+    gzip_static on;
+    gzip_vary on;
+    gzip_proxied any;
+    gzip_comp_level 6;
+    gzip_buffers 16 8k;
+    gzip_http_version 1.1;
+    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml font/woff2;
+
+    # Security Headers
+    add_header X-Frame-Options "SAMEORIGIN";
+    add_header X-XSS-Protection "1; mode=block";
+    add_header X-Content-Type-Options "nosniff";
+    add_header Referrer-Policy "strict-origin-when-cross-origin";
+    # add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
 
     location / {
         try_files $uri $uri/ /index.html;
+        
+        # Caching for index.html (don't cache)
+        location = /index.html {
+            add_header Cache-Control "no-store, no-cache, must-revalidate";
+        }
+    }
+
+    # Static assets in /assets/ (Vite)
+    location /assets/ {
+        expires 1y;
+        add_header Cache-Control "public, immutable";
+        access_log off;
+    }
+
+    # Other static files
+    location ~* \.(?:ico|gif|jpe?g|png|woff2?|eot|otf|ttf|svg|webp|avif)$ {
+        expires 7d;
+        add_header Cache-Control "public";
+        access_log off;
     }
 
     # Proxy API requests to backend
@@ -17,6 +49,8 @@ server {
         proxy_pass http://127.0.0.1:8000/;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
     }
 
     # Standalone Deploy Webhook
@@ -32,12 +66,13 @@ server {
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection "Upgrade";
         proxy_set_header Host $host;
+        proxy_read_timeout 86400;
     }
 
     # Static uploads
     location /uploads/ {
         alias /var/www/radionica3d/backend/uploads/;
-        expires 7d;
+        expires 30d;
         add_header Cache-Control "public";
     }
 

+ 24 - 3
src/components/HeroSection.vue

@@ -48,10 +48,15 @@
           </div>
         </div>
 
-        <!-- Image -->
-        <div class="relative animate-float hidden lg:block">
+        <!-- Image (Desktop Only) -->
+        <div v-if="isDesktop" class="relative animate-float block">
           <div class="relative z-10 p-1.5 bg-white/50 backdrop-blur-sm rounded-[2.5rem] shadow-[0_32px_64px_rgba(0,0,0,0.08)] border border-black/[0.03]">
-            <img src="/src/assets/hero-premium.png" alt="High Precision 3D Printing" class="w-full h-auto rounded-[2rem] aspect-[4/3] object-cover" />
+            <img 
+              src="/src/assets/hero-premium.png" 
+              alt="High Precision 3D Printing" 
+              class="w-full h-auto rounded-[2rem] aspect-[4/3] object-cover"
+              loading="lazy" 
+            />
           </div>
           <div class="absolute -top-6 -right-6 w-32 h-32 border border-primary/20 rounded-[2rem] animate-pulse" />
           <div class="absolute -bottom-10 -left-10 w-40 h-40 bg-primary/5 rounded-full blur-3xl" />
@@ -62,9 +67,25 @@
 </template>
 
 <script setup lang="ts">
+import { ref, onMounted, onUnmounted } from "vue";
 import { useI18n } from "vue-i18n";
 import { ArrowRight, Upload } from "lucide-vue-next";
 import Button from "./ui/button.vue";
 
 const { t } = useI18n();
+
+const isDesktop = ref(false);
+
+const checkIsDesktop = () => {
+  isDesktop.value = window.innerWidth >= 1024;
+};
+
+onMounted(() => {
+  checkIsDesktop();
+  window.addEventListener('resize', checkIsDesktop);
+});
+
+onUnmounted(() => {
+  window.removeEventListener('resize', checkIsDesktop);
+});
 </script>

+ 2 - 1
src/components/ModelUploadSection.vue

@@ -290,7 +290,8 @@ import { useI18n } from "vue-i18n";
 import { toast } from "vue-sonner";
 import { Upload, FileBox, X, Check, Link as LinkIcon, MapPin, User, Phone, Mail, Loader2, ShieldCheck, Hash, FileText } from "lucide-vue-next";
 import Button from "./ui/button.vue";
-import StlViewer from "@/components/StlViewer.vue";
+import { defineAsyncComponent } from "vue";
+const StlViewer = defineAsyncComponent(() => import("@/components/StlViewer.vue"));
 import { submitOrder, getCurrentUser, getMaterials, getPriceEstimate, uploadFilesToServer } from "@/lib/api";
 
 interface UploadedFile { id: string; dbId?: number; name: string; size: number; type: string; file: File; quantity: number; basePrice?: number; isUploading?: boolean; printTime?: string; filamentG?: number; }

+ 1 - 1
src/index.css

@@ -1,4 +1,4 @@
-@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Outfit:wght@400;500;600;700;800&display=swap');
+/* Fonts are loaded in index.html for better performance */
 
 @tailwind base;
 @tailwind components;

+ 0 - 1
src/pages/Admin.vue

@@ -187,7 +187,6 @@
                   <template v-for="(f, i) in order.files" :key="f.id || i">
                     <div v-if="f.id" class="relative group/file bg-background/30 border border-border/50 rounded-2xl overflow-hidden hover:border-primary/30 transition-all flex h-20">
                     <!-- Preview -->
-                    {{ console.log('DEBUG: Rendering file with RESOURCES_BASE_URL:', RESOURCES_BASE_URL, 'and f.preview_path:', f.preview_path) || "" }}
                     <div class="w-20 bg-muted/20 flex items-center justify-center border-r border-border/50 overflow-hidden">
                        <img v-if="f.preview_path" :src="`${RESOURCES_BASE_URL}/${f.preview_path}`" class="w-full h-full object-contain p-1" />
                        <FileBox v-else class="w-6 h-6 text-muted-foreground/30" />