diff --git a/client/src/components/netflix-grid.tsx b/client/src/components/netflix-grid.tsx index b0cff60..2570099 100644 --- a/client/src/components/netflix-grid.tsx +++ b/client/src/components/netflix-grid.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from "react"; +import { useState, useRef, useEffect, useMemo } from "react"; import { useLocation } from "wouter"; import { type Video } from "@shared/schema"; import VideoCard from "./video-card"; @@ -35,8 +35,8 @@ export default function NetflixGrid({ videos, isLoading }: NetflixGridProps) { setSelectedVideo(video); }; - // Organize videos into categories - const getCategories = (): VideoCategory[] => { + // Memoize categories to avoid recalculation on every render + const categories = useMemo((): VideoCategory[] => { if (!videos.length) return []; // Sort by views for top content @@ -109,7 +109,7 @@ export default function NetflixGrid({ videos, isLoading }: NetflixGridProps) { videos: videos.slice(0, 12) } ]; - }; + }, [videos]); if (isLoading && videos.length === 0) { return ( @@ -141,7 +141,7 @@ export default function NetflixGrid({ videos, isLoading }: NetflixGridProps) { ); } - const categories = getCategories(); + // Categories are now memoized above return ( <> diff --git a/client/src/components/video-card.tsx b/client/src/components/video-card.tsx index bfc4dc0..82b85dc 100644 --- a/client/src/components/video-card.tsx +++ b/client/src/components/video-card.tsx @@ -52,16 +52,16 @@ export default function VideoCard({ video, onClick, className = "", hideOverlay const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); - // Delay preview start to avoid loading on quick mouse passes + // Disable video preview for better performance - just keep hover effects useEffect(() => { if (isHovered) { - // Only enable preview on desktop, disable on mobile - if (window.innerWidth >= 768) { - const delay = 800; - hoverTimeoutRef.current = setTimeout(() => { - setShowPreview(true); - }, delay); - } + // Disable preview completely for performance + // if (window.innerWidth >= 768) { + // const delay = 800; + // hoverTimeoutRef.current = setTimeout(() => { + // setShowPreview(true); + // }, delay); + // } } else { if (hoverTimeoutRef.current) { clearTimeout(hoverTimeoutRef.current); @@ -136,7 +136,7 @@ export default function VideoCard({ video, onClick, className = "", hideOverlay {video.title} console.log('Preview loading for:', video.title)} - onError={(e) => console.log('Preview failed for:', video.title)} - onCanPlay={() => console.log('Preview ready for:', video.title)} + onLoadStart={() => {}} + onError={() => {}} + onCanPlay={() => {}} onTimeUpdate={(e) => setCurrentTime(e.currentTarget.currentTime)} onLoadedMetadata={(e) => setDuration(e.currentTarget.duration)} onMouseMove={(e) => { diff --git a/client/src/pages/home.tsx b/client/src/pages/home.tsx index 9e296ef..257609a 100644 --- a/client/src/pages/home.tsx +++ b/client/src/pages/home.tsx @@ -53,11 +53,15 @@ export default function Home() { } }, [videosResponse]); - // Only refetch when search changes + // Debounce search to reduce API calls (500ms delay) useEffect(() => { - if (searchQuery !== undefined) { - refetch(); - } + const debounceTimer = setTimeout(() => { + if (searchQuery !== undefined) { + refetch(); + } + }, 500); + + return () => clearTimeout(debounceTimer); }, [searchQuery, refetch]); return ( diff --git a/server/index.ts b/server/index.ts index 9d7f7c7..172370b 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,4 +1,5 @@ import express, { type Request, Response, NextFunction } from "express"; +import compression from "compression"; import { registerRoutes } from "./routes"; import { videoSyncService } from "./videoSync"; import { setupVite, serveStatic, log } from "./vite"; @@ -7,18 +8,36 @@ import fs from "fs"; import path from "path"; const app = express(); + +// Enable gzip compression for faster responses +app.use(compression()); + app.use(express.json()); app.use(express.urlencoded({ extended: false })); -// Prevent caching issues middleware +// Performance and caching middleware app.use((req, res, next) => { - // Set no-cache headers for HTML pages to prevent blank screen issues + // Set no-cache headers only for HTML pages to prevent blank screen issues if (req.path === '/' || req.path.endsWith('.html') || !req.path.includes('.')) { res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); + } + // Cache static assets for better performance + else if (req.path.includes('/assets/') || req.path.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg)$/)) { + res.set({ + 'Cache-Control': 'public, max-age=31536000', // 1 year + 'ETag': `"${Date.now()}"` // Simple ETag + }); + } + // Cache API responses for 30 seconds + else if (req.path.startsWith('/api/videos') && req.method === 'GET') { + res.set({ + 'Cache-Control': 'public, max-age=30', + 'ETag': `"videos-${Date.now()}"` + }); } next(); }); diff --git a/server/videoSync.ts b/server/videoSync.ts index d7e6742..2a00af4 100644 --- a/server/videoSync.ts +++ b/server/videoSync.ts @@ -211,13 +211,13 @@ class VideoSyncService { } private startPeriodicSync() { - // Sync every 60 seconds + // Sync every 5 minutes for better performance this.syncInterval = setInterval(() => { console.log('⏰ Starting scheduled video sync...'); this.syncVideos(); - }, 60 * 1000); + }, 5 * 60 * 1000); - console.log('📅 Scheduled video sync every 60 seconds'); + console.log('📅 Scheduled video sync every 5 minutes for optimal performance'); } getVideos(limit: number = 20, offset: number = 0, search?: string) {