feat: Add aspect ratio support to imgproxy loader and apply 16:9 aspect ratio to featured images across blog posts and recent posts.

This commit is contained in:
2026-02-22 17:30:30 +01:00
parent 70984b9021
commit fb2354d2cc
4 changed files with 28 additions and 7 deletions

View File

@@ -76,7 +76,7 @@ export default async function BlogPost({ params }: BlogPostProps) {
<div className="relative w-full h-[70vh] min-h-[500px] overflow-hidden group"> <div className="relative w-full h-[70vh] min-h-[500px] overflow-hidden group">
<div className="absolute inset-0 transition-transform duration-[3s] ease-out scale-110 group-hover:scale-100"> <div className="absolute inset-0 transition-transform duration-[3s] ease-out scale-110 group-hover:scale-100">
<Image <Image
src={post.frontmatter.featuredImage} src={`${post.frontmatter.featuredImage}?ar=16:9`}
alt={post.frontmatter.title} alt={post.frontmatter.title}
fill fill
priority priority

View File

@@ -31,7 +31,7 @@ export default function PostNavigation({
{prev.frontmatter.featuredImage ? ( {prev.frontmatter.featuredImage ? (
<div <div
className="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-110" className="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-110"
style={{ backgroundImage: `url(${prev.frontmatter.featuredImage})` }} style={{ backgroundImage: `url(${prev.frontmatter.featuredImage}?ar=16:9)` }}
/> />
) : ( ) : (
<div className="absolute inset-0 bg-neutral-100" /> <div className="absolute inset-0 bg-neutral-100" />
@@ -82,7 +82,7 @@ export default function PostNavigation({
{next.frontmatter.featuredImage ? ( {next.frontmatter.featuredImage ? (
<div <div
className="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-110" className="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-110"
style={{ backgroundImage: `url(${next.frontmatter.featuredImage})` }} style={{ backgroundImage: `url(${next.frontmatter.featuredImage}?ar=16:9)` }}
/> />
) : ( ) : (
<div className="absolute inset-0 bg-neutral-100" /> <div className="absolute inset-0 bg-neutral-100" />

View File

@@ -43,7 +43,7 @@ export default async function RecentPosts({ locale }: RecentPostsProps) {
{post.frontmatter.featuredImage && ( {post.frontmatter.featuredImage && (
<div className="relative h-64 overflow-hidden"> <div className="relative h-64 overflow-hidden">
<Image <Image
src={post.frontmatter.featuredImage} src={`${post.frontmatter.featuredImage}?ar=16:9`}
alt={post.frontmatter.title} alt={post.frontmatter.title}
fill fill
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"

View File

@@ -23,17 +23,37 @@ export default function imgproxyLoader({
return src; return src;
} }
// Check if src contains custom gravity query parameter // Check if src contains custom gravity or aspect ratio query parameters
let gravity = 'sm'; // Use smart gravity (content-aware) by default let gravity = 'sm'; // Use smart gravity (content-aware) by default
let cleanSrc = src; let cleanSrc = src;
let calculatedHeight = 0;
let resizingType: 'fit' | 'fill' = 'fit';
try { try {
// Dummy base needed for relative URLs // Dummy base needed for relative URLs
const url = new URL(src, 'http://localhost'); const url = new URL(src, 'http://localhost');
const customGravity = url.searchParams.get('gravity'); const customGravity = url.searchParams.get('gravity');
const aspectRatio = url.searchParams.get('ar'); // e.g. "16:9"
if (customGravity) { if (customGravity) {
gravity = customGravity; gravity = customGravity;
url.searchParams.delete('gravity'); url.searchParams.delete('gravity');
}
if (aspectRatio) {
const parts = aspectRatio.split(':');
if (parts.length === 2) {
const arW = parseFloat(parts[0]);
const arH = parseFloat(parts[1]);
if (!isNaN(arW) && !isNaN(arH) && arW > 0) {
calculatedHeight = Math.round(width * (arH / arW));
resizingType = 'fill'; // Must use fill to allow imgproxy to crop
}
}
url.searchParams.delete('ar');
}
if (customGravity || aspectRatio) {
cleanSrc = src.startsWith('http') ? url.href : url.pathname + url.search; cleanSrc = src.startsWith('http') ? url.href : url.pathname + url.search;
} }
} catch (e) { } catch (e) {
@@ -41,10 +61,11 @@ export default function imgproxyLoader({
} }
// We use the width provided by Next.js for responsive images // We use the width provided by Next.js for responsive images
// Height is set to 0 to maintain aspect ratio // Height is calculated from aspect ratio if provided, otherwise 0 to maintain aspect ratio
return getImgproxyUrl(cleanSrc, { return getImgproxyUrl(cleanSrc, {
width, width,
resizing_type: 'fit', height: calculatedHeight,
resizing_type: resizingType,
gravity, gravity,
}); });
} }