diff --git a/next.config.mjs b/next.config.mjs index 4678774..534c6b7 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,12 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + images: { + imageSizes: [16, 32, 48, 64, 96, 128, 160, 208, 256, 320, 384, 416, 512], + deviceSizes: [ + 320, 375, 393, 400, 430, 480, 512, 640, 750, 828, 1080, 1200, 1920, 2048, 3840, + ], + qualities: [50, 75], + }, +}; export default nextConfig; diff --git a/src/components/compress-image.tsx b/src/components/compress-image.tsx new file mode 100644 index 0000000..81057a3 --- /dev/null +++ b/src/components/compress-image.tsx @@ -0,0 +1,37 @@ +/** + * getCompressedImageProps + * + * Returns Next.js props with responsive sizes and quality. + * Use with fill for correct responsive image delivery. + * + * Usage: + * {alt} + * + * @param displaySizeDesktop - The largest dimension the image is rendered at (px) on desktop + * @param displaySizeMobile - The largest dimension the image is rendered at (px) on mobile + * @param quality - Compression quality 1–100 (default: 50) + */ +export function getCompressedImageProps( + displaySizeDesktop: number, + displaySizeMobile: number, + quality = 50, +) { + return { + sizes: `(max-width: 768px) ${displaySizeMobile}px, ${displaySizeDesktop}px`, + quality, + }; +} + +export function getCompressedBannerImageProps( + displaySizeDesktop: number, + displaySizeMobile: number, + quality = 75, +) { + return { + priority: true, + fetchPriority: 'high' as const, + loading: 'eager' as const, + sizes: `(max-width: 768px) ${displaySizeMobile}px, ${displaySizeDesktop}px`, + quality, + }; +} diff --git a/src/components/page-header.tsx b/src/components/page-header.tsx index f3fb98e..59d5268 100644 --- a/src/components/page-header.tsx +++ b/src/components/page-header.tsx @@ -1,4 +1,5 @@ import Image from 'next/image'; +import { getCompressedBannerImageProps } from './compress-image'; export default function PageHeader({ title, image }: { title: string; image: string }) { return ( @@ -10,6 +11,7 @@ export default function PageHeader({ title, image }: { title: string; image: str fill src={image} alt={title} + {...getCompressedBannerImageProps(1920, 200)} />
@@ -28,6 +30,11 @@ export default function PageHeader({ title, image }: { title: string; image: str fill src={image} alt={title} + sizes="100vw" + quality={75} + priority + fetchPriority="high" + loading="eager" />
diff --git a/src/components/profile-card.tsx b/src/components/profile-card.tsx index 34aa024..51bfd4a 100644 --- a/src/components/profile-card.tsx +++ b/src/components/profile-card.tsx @@ -3,6 +3,7 @@ import Image from 'next/image'; import React from 'react'; import { motion } from 'framer-motion'; import { IProfile } from '@/data/team'; +import { getCompressedImageProps } from '@/components/compress-image'; export default function ProfileCard({ profile, @@ -28,6 +29,7 @@ export default function ProfileCard({ alt={profile?.name} fill className="object-cover" + {...getCompressedImageProps(208, 208)} />
diff --git a/src/components/profile-modal.tsx b/src/components/profile-modal.tsx index 4b9ea96..6308ce0 100644 --- a/src/components/profile-modal.tsx +++ b/src/components/profile-modal.tsx @@ -12,6 +12,7 @@ import { import Image from 'next/image'; import { IProfile } from '@/data/team'; import { useEffect, useState } from 'react'; +import { getCompressedImageProps } from '@/components/compress-image'; export default function ProfileModal({ profile, @@ -90,6 +91,7 @@ export default function ProfileModal({ alt={profile?.name} fill className="object-cover" + {...getCompressedImageProps(208, 208)} />