Performance follow-ups

This commit is contained in:
adilallo
2026-05-26 07:24:36 -06:00
parent 3be188a3cc
commit eded97559d
16 changed files with 432 additions and 72 deletions
@@ -23,14 +23,13 @@ const ContentThumbnailTemplateContainer = memo<ContentThumbnailTemplateProps>(
}) => {
const variant = variantProp;
const sizing = sizingProp;
// Get article-specific background image from frontmatter
const getBackgroundImage = (
post: ContentThumbnailTemplateProps["post"],
variant: "vertical" | "horizontal",
orientation: "vertical" | "horizontal",
): string => {
if (post.frontmatter?.thumbnail) {
const imageName =
variant === "vertical"
orientation === "vertical"
? post.frontmatter.thumbnail.vertical
: post.frontmatter.thumbnail.horizontal;
@@ -47,12 +46,21 @@ const ContentThumbnailTemplateContainer = memo<ContentThumbnailTemplateProps>(
? slug
: contentCatalogSlugForFallback(slug);
return variant === "vertical"
return orientation === "vertical"
? contentBlogVerticalPath(resolvedSlug)
: contentBlogHorizontalPath(resolvedSlug);
};
const backgroundImage = getBackgroundImage(post, variant);
// For "responsive", emit both orientations so the <picture> source can
// swap at smd without a second card in the DOM.
const backgroundImage =
variant === "responsive"
? getBackgroundImage(post, "horizontal")
: getBackgroundImage(post, variant);
const backgroundImageSmd =
variant === "responsive"
? getBackgroundImage(post, "vertical")
: undefined;
return (
<ContentThumbnailTemplateView
@@ -61,6 +69,7 @@ const ContentThumbnailTemplateContainer = memo<ContentThumbnailTemplateProps>(
variant={variant}
sizing={sizing}
backgroundImage={backgroundImage}
backgroundImageSmd={backgroundImageSmd}
/>
);
},
@@ -1,6 +1,9 @@
import type { BlogPost } from "../../../../lib/content";
export type ContentThumbnailTemplateVariantValue = "vertical" | "horizontal";
export type ContentThumbnailTemplateVariantValue =
| "vertical"
| "horizontal"
| "responsive";
export type ContentThumbnailTemplateSizingValue = "fluid" | "fixed";
@@ -8,7 +11,8 @@ export interface ContentThumbnailTemplateProps {
post: BlogPost;
className?: string;
/**
* Content thumbnail variant.
* vertical | horizontal — single layout. responsive — horizontal at <smd,
* vertical at ≥smd (Learn grid); single card, viewport-swapped via <picture>.
*/
variant?: ContentThumbnailTemplateVariantValue;
/**
@@ -21,7 +25,9 @@ export interface ContentThumbnailTemplateProps {
export interface ContentThumbnailTemplateViewProps {
post: BlogPost;
className: string;
variant: "vertical" | "horizontal";
variant: ContentThumbnailTemplateVariantValue;
sizing: ContentThumbnailTemplateSizingValue;
backgroundImage: string;
/** Wide-viewport image source for variant="responsive" (≥smd). */
backgroundImageSmd?: string;
}
@@ -9,7 +9,41 @@ function ContentThumbnailTemplateView({
variant,
sizing,
backgroundImage,
backgroundImageSmd,
}: ContentThumbnailTemplateViewProps) {
if (variant === "responsive") {
// Single card; <picture> swaps the orientation-specific image at smd
// (530px), aspect-ratio and content positioning switch via Tailwind.
return (
<Link
href={`/blog/${post.slug}`}
className={`group block w-full transition-transform duration-200 hover:scale-[1.02] ${className}`}
>
<div className="relative aspect-[320/225.5] w-full overflow-hidden smd:aspect-[260/390]">
<div className="absolute inset-0 z-0">
<picture>
{backgroundImageSmd ? (
<source
media="(min-width: 530px)"
srcSet={backgroundImageSmd}
/>
) : null}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={backgroundImage}
alt=""
className="pointer-events-none size-full object-cover"
/>
</picture>
</div>
<div className="absolute left-[4.375%] top-[6.099%] z-20 w-[71.875%] smd:left-[6.923%] smd:top-[4.615%] smd:w-[76.923%]">
<ContentContainer post={post} size="xs" />
</div>
</div>
</Link>
);
}
if (variant === "vertical") {
if (sizing === "fixed") {
return (
@@ -5,11 +5,20 @@
*/
import { memo } from "react";
import Image from "next/image";
import { useTranslation } from "../../../contexts/MessagesContext";
import ContentLockup from "../../type/ContentLockup";
import HeroDecor from "./HeroDecor";
import { ASSETS, getAssetPath } from "../../../../lib/assetUtils";
/**
* Intrinsic dimensions of `public/assets/marketing/hero-image.png` (2560×1600,
* 16:10). Passed to `next/image` to reserve aspect ratio + drive responsive
* srcset generation. Actual rendered size is governed by `sizes`.
*/
const HERO_IMAGE_WIDTH = 2560;
const HERO_IMAGE_HEIGHT = 1600;
interface HeroBannerProps {
title?: string;
subtitle?: string;
@@ -50,13 +59,14 @@ const HeroBanner = memo<HeroBannerProps>(
{/* Hero Image Container */}
<div className="w-full h-full md:flex-1 rounded-[8px] overflow-hidden relative z-10 flex items-center justify-center">
{/* eslint-disable-next-line @next/next/no-img-element -- dynamic path from getAssetPath */}
<img
<Image
src={getAssetPath(ASSETS.HERO_IMAGE)}
alt={imageAlt}
width={HERO_IMAGE_WIDTH}
height={HERO_IMAGE_HEIGHT}
priority
sizes="(min-width: 768px) 50vw, 100vw"
className="w-full h-auto"
loading="eager"
fetchPriority="high"
/>
</div>
</div>