perf: optimize image loading — resize, thumbnails, caching, lazy load
- Server: resize uploads to max 1200px + generate 600px _thumb.jpg - Server: cache /uploads with max-age=1y immutable headers - Client: CatCard uses _thumb.jpg in feed (fallback to full on error) - Client: first 3 feed cards load eagerly (LCP), rest lazy + decoding=async - Client: loading placeholder bg while image fetches - Migration: server/src/scripts/gen-thumbs.ts for existing images Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,12 +11,18 @@ interface CatCardProps {
|
||||
cat: Cat;
|
||||
onOpen: (id: number) => void;
|
||||
likedIds?: Set<number>;
|
||||
priority?: boolean;
|
||||
}
|
||||
|
||||
export default function CatCard({ cat, onOpen, likedIds }: CatCardProps) {
|
||||
export default function CatCard({ cat, onOpen, likedIds, priority }: CatCardProps) {
|
||||
const { user } = useAuth();
|
||||
const [liked, setLiked] = useState(() => likedIds?.has(cat.id) ?? false);
|
||||
const [likeCount, setLikeCount] = useState(cat.likes_count);
|
||||
const [imgLoaded, setImgLoaded] = useState(false);
|
||||
|
||||
const thumbSrc = cat.image_url.endsWith('.gif')
|
||||
? cat.image_url
|
||||
: cat.image_url.replace(/\.jpg$/, '_thumb.jpg');
|
||||
const { toast } = useToast();
|
||||
const likeMutation = useLikeCat(cat.id);
|
||||
const unlikeMutation = useUnlikeCat(cat.id);
|
||||
@@ -75,13 +81,16 @@ export default function CatCard({ cat, onOpen, likedIds }: CatCardProps) {
|
||||
</div>
|
||||
|
||||
{/* Изображение */}
|
||||
<figure className="cat-card-image">
|
||||
<figure className="cat-card-image relative min-h-[200px] bg-[var(--border-light)]">
|
||||
<img
|
||||
src={cat.image_url}
|
||||
src={thumbSrc}
|
||||
alt={cat.caption || 'Фото кота'}
|
||||
className="w-full max-h-[540px] object-contain select-none mx-auto cat-card-img"
|
||||
className={`w-full max-h-[540px] object-contain select-none mx-auto cat-card-img transition-opacity duration-300 ${imgLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
draggable={false}
|
||||
loading="lazy"
|
||||
loading={priority ? 'eager' : 'lazy'}
|
||||
decoding="async"
|
||||
onLoad={() => setImgLoaded(true)}
|
||||
onError={e => { e.currentTarget.src = cat.image_url; }}
|
||||
/>
|
||||
</figure>
|
||||
|
||||
|
||||
@@ -103,8 +103,8 @@ export default function FeedPage() {
|
||||
{allCats.length > 0 && (
|
||||
<>
|
||||
<div className="space-y-5">
|
||||
{allCats.map((cat) => (
|
||||
<CatCard key={cat.id} cat={cat} onOpen={setModalCatId} likedIds={likedIds} />
|
||||
{allCats.map((cat, index) => (
|
||||
<CatCard key={cat.id} cat={cat} onOpen={setModalCatId} likedIds={likedIds} priority={index < 3} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user