perf(images): convert remaining <img> tags to next/image project-wide

Clears every remaining @next/next/no-img-element warning — automatic
responsive srcset, lazy-loading, and format optimization instead of
always loading the original file at full size. Fixed-size icons got
explicit width/height; dynamic-aspect photos got fill inside a
relative wrapper.
This commit is contained in:
Marco
2026-07-21 12:44:03 +00:00
parent af00fd091f
commit 9c9f0b02c0
18 changed files with 92 additions and 42 deletions
+5 -2
View File
@@ -1,3 +1,4 @@
import Image from "next/image";
import { Reveal } from "./Reveal";
export function About() {
@@ -75,10 +76,12 @@ export function About() {
style={{ minHeight: "14rem" }}
delay={0.15}
>
<img
<Image
alt="Björn"
src="/about-author.jpg"
className="absolute inset-0 w-full h-full object-cover object-center pointer-events-none"
fill
sizes="(min-width: 768px) 58vw, 100vw"
className="object-cover object-center pointer-events-none"
/>
{/* Left gradient: wide enough to cover the text-column overlap — md+ only */}
<div className="hidden md:block absolute inset-y-0 left-0 w-72 bg-gradient-to-r from-bg-dark to-transparent pointer-events-none" />
+2 -1
View File
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useRef, useState } from "react";
import Image from "next/image";
import { addToCart } from "../lib/cart";
import { useCartFly } from "./CartFly";
@@ -61,7 +62,7 @@ export function AddToCartInlineButton({
>
{added ? "Hinzugefügt ✓" : label}
</span>
<img alt="" src="/icon-cart-outline.png" className="h-[1.875rem] w-8 object-contain" />
<Image alt="" src="/icon-cart-outline.png" width={32} height={30} className="h-[1.875rem] w-8 object-contain" />
</button>
);
}
+3 -2
View File
@@ -1,3 +1,4 @@
import Image from "next/image";
import { Reveal } from "./Reveal";
function Word({ children }: { children: string }) {
@@ -16,7 +17,7 @@ function Arrow() {
<div className="flex items-center justify-center shrink-0">
<div className="-scale-y-100 rotate-180">
<div className="relative" style={{ height: "var(--divider-arrow-h)", width: "var(--divider-arrow-w)" }}>
<img alt="" src="/icon-separator.svg" className="absolute inset-0 w-full h-full" />
<Image alt="" src="/icon-separator.svg" fill sizes="48px" />
</div>
</div>
</div>
@@ -60,7 +61,7 @@ export function Divider() {
className="relative"
style={{ height: "var(--divider-sparkle-inner-h)", width: "var(--divider-sparkle-inner-w)" }}
>
<img alt="" src="/icon-separator-right.svg" className="absolute inset-0 w-full h-full" />
<Image alt="" src="/icon-separator-right.svg" fill sizes="48px" />
</div>
</div>
</div>
+1 -5
View File
@@ -61,11 +61,7 @@ export function Hero() {
Starte mit der 7-Tage-Challenge
</span>
<div className="relative h-[1.1875rem] w-[1.5625rem] shrink-0">
<img
alt=""
src="/icon-check.svg"
className="absolute inset-0 w-full h-full"
/>
<Image alt="" src="/icon-check.svg" fill sizes="26px" />
</div>
</Link>
+6 -3
View File
@@ -1,4 +1,5 @@
import type { ReactNode } from "react";
import Image from "next/image";
import type { TOCSection } from "./SectionTOC";
// Minimal Lexical JSON → JSX renderer for Payload's richText fields.
@@ -146,8 +147,8 @@ function renderNode(node: LexicalNode, key: string): ReactNode {
return (
<div key={key} className="relative flex items-start gap-6 w-full my-6">
<div className="flex items-center gap-2 shrink-0">
<span className="flex h-7 w-6 items-center justify-center shrink-0">
<img alt="" src="/icon-sparkle-merke-dir.png" className="w-full h-full object-contain" />
<span className="relative flex h-7 w-6 items-center justify-center shrink-0">
<Image alt="" src="/icon-sparkle-merke-dir.png" fill sizes="24px" className="object-contain" />
</span>
<span
className="font-bold text-text-primary text-[1.625rem] whitespace-nowrap"
@@ -158,9 +159,11 @@ function renderNode(node: LexicalNode, key: string): ReactNode {
</div>
{/* Hand-drawn underline image, not a plain bar — exported
straight from the Figma node (label-underline). */}
<img
<Image
alt=""
src="/icon-merke-dir-underline.png"
width={136}
height={23}
className="absolute left-8 top-[2.1875rem] w-[8.5rem] h-[1.4375rem] object-cover pointer-events-none"
/>
<div className="w-px self-stretch bg-brand shrink-0" />
+3 -2
View File
@@ -1,4 +1,5 @@
import Link from "next/link";
import Image from "next/image";
import { Reveal, RevealGroup, RevealItem } from "./Reveal";
import { getWerkzeugeCards } from "../lib/payload";
@@ -38,8 +39,8 @@ export async function Tools() {
className="md:col-span-4 flex gap-8 items-start rounded-md transition-transform duration-300 hover:-translate-y-1"
>
{/* Icon — uniform box, pre-flipped/rotated source asset */}
<div className="flex items-center justify-center shrink-0 size-14">
<img alt="" src={tool.icon} className="max-w-full max-h-full object-contain" />
<div className="relative flex items-center justify-center shrink-0 size-14">
<Image alt="" src={tool.icon} fill sizes="56px" className="object-contain" />
</div>
{/* Card content — self-stretch + h-full + justify-between so
+4 -1
View File
@@ -1,3 +1,4 @@
import Image from "next/image";
import { getTrustBadges } from "../lib/payload";
// Content now lives in Payload (TrustBadges collection) instead of being
@@ -15,7 +16,9 @@ export async function TrustRow() {
<div key={item.id} className="flex items-center gap-6 md:gap-12">
{i > 0 && <div className="hidden md:block h-10 w-px bg-border" />}
<div className="flex gap-4 items-center">
<img alt="" src={item.icon} className="size-8 shrink-0 object-contain" />
<div className="relative size-8 shrink-0">
<Image alt="" src={item.icon} fill sizes="32px" className="object-contain" />
</div>
<div className="flex flex-col gap-0.5 items-start">
<p className="font-semibold text-body text-text-primary whitespace-nowrap">{item.title}</p>
<p className="text-body-sm text-text-muted whitespace-nowrap">{item.description}</p>