Skip to content

Commit 1ef9cb7

Browse files
GH-251 Audit site for performance issues focusing on images and caching (6a3c5a5db6261834164ce41b) (#251)
Co-authored-by: netlify[bot] <no-reply@netlify.com>
1 parent 43473b9 commit 1ef9cb7

9 files changed

Lines changed: 104 additions & 4 deletions

File tree

app/projects/eternalcore/eternal-showcase.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Image from "next/image";
66
import { useEffect, useState } from "react";
77
import { createPortal } from "react-dom";
88
import { FadeIn } from "@/components/ui/motion/motion-components";
9+
import netlifyImageLoader from "@/lib/netlify-image-loader";
910

1011
const marqueeItems = [
1112
// Row 1: Essentials & Chat
@@ -89,9 +90,10 @@ const ShowcaseModal = ({
8990
alt={item.title}
9091
className="object-contain"
9192
fill
93+
loader={netlifyImageLoader}
9294
priority
95+
sizes="(max-width: 1024px) 100vw, 1024px"
9396
src={item.src}
94-
unoptimized
9597
/>
9698
{/* Close Button */}
9799
<button
@@ -153,9 +155,10 @@ const InfiniteMarquee = ({
153155
alt={item.id}
154156
className="h-full w-full transform-gpu object-cover transition-transform duration-500 will-change-transform group-hover:scale-110"
155157
fill
158+
loader={netlifyImageLoader}
159+
loading="lazy"
156160
sizes="400px"
157161
src={item.src}
158-
unoptimized
159162
/>
160163

161164
{/* Gradient Overlay & Content - Always visible on hover */}

app/projects/eternalcore/page.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Card } from "@/components/ui/card";
99
import { FacadePattern } from "@/components/ui/facade-pattern";
1010
import { FadeIn, MotionSection, ScaleIn, SlideIn } from "@/components/ui/motion/motion-components";
1111
import { slideUp } from "@/lib/animations/variants";
12+
import netlifyImageLoader from "@/lib/netlify-image-loader";
1213

1314
import { ConfigPreview } from "./config-preview";
1415
import { EternalShowcase } from "./eternal-showcase";
@@ -101,8 +102,11 @@ export default function EternalCorePage() {
101102
<Image
102103
alt="EternalCore Project Banner"
103104
className="transform-gpu object-cover transition-transform duration-700 will-change-transform group-hover:scale-105"
105+
fetchPriority="high"
104106
fill
107+
loader={netlifyImageLoader}
105108
priority
109+
sizes="(max-width: 1024px) 100vw, 50vw"
106110
src="/eternalcore/readme-banner.png"
107111
/>
108112
<div className="pointer-events-none absolute inset-0 bg-linear-to-tr from-blue-500/10 via-transparent to-purple-500/10 mix-blend-overlay" />

assets.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
declare module "*.svg" {
2+
const src: string;
3+
export default src;
4+
}
5+
6+
declare module "*.png" {
7+
import type { StaticImageData } from "next/image";
8+
9+
const src: StaticImageData;
10+
export default src;
11+
}

components/home/about/about-section.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import PolandMap from "@/components/home/about/poland-map";
77
import PeopleGroupIcon from "@/components/icons/people-group";
88
import { Button } from "@/components/ui/button";
99
import { SlideIn } from "@/components/ui/motion/motion-components";
10+
import netlifyImageLoader from "@/lib/netlify-image-loader";
1011
import AboutImage from "@/public/hero image.png";
1112

1213
export default function About() {
@@ -103,6 +104,7 @@ export default function About() {
103104
alt="EternalCode Team"
104105
className="h-auto w-full object-cover"
105106
fetchPriority="low"
107+
loader={netlifyImageLoader}
106108
loading="lazy"
107109
placeholder="blur"
108110
sizes="(max-width: 1024px) 100vw, 600px"

components/home/projects/projects-section.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { motion } from "framer-motion";
44
import Image from "next/image";
55
import Link from "next/link";
66
import { useEffect, useRef, useState } from "react";
7+
import netlifyImageLoader from "@/lib/netlify-image-loader";
78

89
interface Project {
910
name: string;
@@ -161,6 +162,8 @@ function ProjectCard({ project }: { project: Project }) {
161162
alt={project.name}
162163
className="transform-gpu object-cover transition-transform duration-700 will-change-transform group-hover:scale-105"
163164
fill
165+
loader={netlifyImageLoader}
166+
loading="lazy"
164167
sizes="(max-width: 768px) 300px, 420px"
165168
src={project.image}
166169
/>

components/notification-generator/preview/background-image.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import Image from "next/image";
22
import { memo } from "react";
3+
import netlifyImageLoader from "@/lib/netlify-image-loader";
34

45
const BackgroundImage = memo(() => (
56
<Image
67
alt="Minecraft background"
78
className="pointer-events-none absolute inset-0 h-full w-full select-none object-cover"
9+
fetchPriority="high"
810
fill
11+
loader={netlifyImageLoader}
912
priority
13+
sizes="100vw"
1014
src="/mc-bg.png"
1115
style={{ zIndex: 0 }}
1216
/>

components/ui/mdx/mdx-image.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
"use client";
22

33
import Image from "next/image";
4+
import netlifyImageLoader, {
5+
getNetlifyImageSrcSet,
6+
getNetlifyImageUrl,
7+
} from "@/lib/netlify-image-loader";
48

59
interface MdxImageProps {
610
src?: string;
@@ -14,7 +18,8 @@ export function MdxImage({ src, alt, className }: MdxImageProps) {
1418
}
1519

1620
const isLocal = src.startsWith("/");
17-
const cdnSrc = isLocal ? `/.netlify/images?url=${encodeURIComponent(src)}` : src;
21+
const cdnSrc = isLocal ? getNetlifyImageUrl(src, 800) : src;
22+
const cdnSrcSet = isLocal ? getNetlifyImageSrcSet(src) : undefined;
1823
const isVideo = src.endsWith(".mp4") || src.endsWith(".webm") || src.endsWith(".mov");
1924
const isGif = src.endsWith(".gif");
2025

@@ -43,12 +48,15 @@ export function MdxImage({ src, alt, className }: MdxImageProps) {
4348
return (
4449
<span className="group relative my-6 block w-full overflow-hidden rounded-md bg-gray-100 shadow-sm dark:bg-gray-800">
4550
{/* biome-ignore lint/performance/noImgElement: Next.js Image Optimization warnings */}
46-
{/** biome-ignore lint/correctness/useImageSize: Decorative preview */}
4751
<img
4852
alt={alt || ""}
4953
className={`h-auto w-full object-cover ${className || ""}`}
54+
height={450}
5055
loading="lazy"
56+
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 900px"
5157
src={cdnSrc}
58+
srcSet={cdnSrcSet}
59+
width={800}
5260
/>
5361
</span>
5462
);
@@ -60,6 +68,8 @@ export function MdxImage({ src, alt, className }: MdxImageProps) {
6068
alt={alt || ""}
6169
className={`h-auto w-full rounded-md shadow-sm ${className || ""}`}
6270
height={500}
71+
loader={isLocal ? netlifyImageLoader : undefined}
72+
loading="lazy"
6373
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 900px"
6474
src={src}
6575
width={900}

lib/netlify-image-loader.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { ImageLoaderProps } from "next/image";
2+
3+
export const NETLIFY_IMAGE_WIDTHS = [400, 800, 1200] as const;
4+
5+
export function getNetlifyImageUrl(src: string, width: number, quality = 80) {
6+
const params = new URLSearchParams({
7+
url: src,
8+
w: String(width),
9+
fm: "avif",
10+
q: String(quality),
11+
});
12+
13+
return `/.netlify/images?${params.toString()}`;
14+
}
15+
16+
export function getNetlifyImageSrcSet(src: string, widths = NETLIFY_IMAGE_WIDTHS) {
17+
return widths.map((width) => `${getNetlifyImageUrl(src, width)} ${width}w`).join(", ");
18+
}
19+
20+
export default function netlifyImageLoader({ src, width, quality }: ImageLoaderProps) {
21+
return getNetlifyImageUrl(src, width, quality ?? 80);
22+
}

netlify.toml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,46 @@ publish = ".next"
77
[headers.values]
88
Cache-Control = "public, max-age=31556952, immutable"
99

10+
[[headers]]
11+
for = "/_next/static/*"
12+
[headers.values]
13+
Cache-Control = "public, max-age=31536000, immutable"
14+
15+
[[headers]]
16+
for = "/icons/*"
17+
[headers.values]
18+
Cache-Control = "public, max-age=31536000, immutable"
19+
20+
[[headers]]
21+
for = "/*.png"
22+
[headers.values]
23+
Cache-Control = "public, max-age=31536000, immutable"
24+
25+
[[headers]]
26+
for = "/*.svg"
27+
[headers.values]
28+
Cache-Control = "public, max-age=31536000, immutable"
29+
30+
[[headers]]
31+
for = "/*.webmanifest"
32+
[headers.values]
33+
Cache-Control = "public, max-age=0, must-revalidate"
34+
35+
[[headers]]
36+
for = "/*"
37+
[headers.values]
38+
Netlify-CDN-Cache-Control = "public, max-age=0, stale-while-revalidate=86400"
39+
40+
[images]
41+
remote_images = [
42+
"https://github\\.com/.*",
43+
"https://avatars\\.githubusercontent\\.com/.*",
44+
"https://avatars-githubusercontent\\.webp\\.se/.*",
45+
"https://private-user-images\\.githubusercontent\\.com/.*",
46+
"https://i\\.imgur\\.com/.*",
47+
"https://imgur\\.com/.*",
48+
"https://cms\\.eternalcode\\.pl/.*"
49+
]
50+
1051
[[plugins]]
1152
package = "@netlify/plugin-nextjs"

0 commit comments

Comments
 (0)