perf: Admin-Bildvorschauen ueber next/image statt als rohes <img>
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m24s

Die Vorschaukacheln in admin/inhalte, admin/produkte und admin/ernten
luden das vollstaendig gespeicherte Bild, um es auf 80x80 bzw. in einer
Vierer-Rasterkachel darzustellen. Bei einem Produktformular mit mehreren
Bildern summierte sich das zu mehreren Megabyte allein fuer Miniaturen.

Der urspruengliche Kommentar ("next/image lohnt hier nicht") stimmte, als
Uploads klein waren; gespeichert wird inzwischen bis MAX_IMAGE_DIMENSION
Kantenlaenge. Beide Felder rendern die Vorschau jetzt ueber next/image
mit passendem sizes, der Optimierer liefert damit genau die gebrauchte
Groesse. Layout und Bedienung bleiben unveraendert.
This commit is contained in:
Nick Szaunig committed 2026-09-13 23:16:35 +02:00
1 parent cd8e307faf
commit be5fb32a4a
2 files changed
+18 -4

No files matched your search

+10 -2
View File
@@ -1,5 +1,6 @@
"use client";
import Image from "next/image";
import { useRef, useState, type ChangeEvent } from "react";
import { useI18n } from "@/i18n/i18n-provider";
@@ -56,8 +57,15 @@ export function AdminImageField({ name, label, initialValue }: AdminImageFieldPr
<input type="hidden" name={name} value={value} />
<div className="mt-2 flex items-center gap-4">
{value ? (
// eslint-disable-next-line @next/next/no-img-element -- kleine Admin-Vorschau, next/image lohnt hier nicht.
<img src={value} alt="" className="h-20 w-20 object-cover border border-ink/10" />
// Ueber next/image statt als rohes <img>: gespeichert wird ein Bild
// mit bis zu MAX_IMAGE_DIMENSION Kantenlaenge (siehe lib/uploads.ts),
// also durchaus ein paar hundert KB. Als rohes <img> laedt die
// Admin-Seite dieses Vollbild fuer eine 80x80-Kachel — bei einem
// Formular mit mehreren Bildern summiert sich das spuerbar. Der
// Optimierer liefert stattdessen genau die gebrauchte Groesse.
<div className="relative h-20 w-20 flex-shrink-0 overflow-hidden border border-ink/10">
<Image src={value} alt="" fill sizes="80px" className="object-cover" />
</div>
) : (
<div className="h-20 w-20 border border-dashed border-ink/20 flex items-center justify-center text-center text-[10px] text-ink/40 px-1">
{t.adminImageField.none}
+8 -2
View File
@@ -1,5 +1,6 @@
"use client";
import Image from "next/image";
import { useRef, useState, type ChangeEvent } from "react";
import { useI18n } from "@/i18n/i18n-provider";
@@ -67,8 +68,13 @@ export function AdminImageListField({ name, label, initialValues }: AdminImageLi
<div className="mt-2 grid grid-cols-4 gap-3">
{images.map((src, i) => (
<div key={`${i}-${src}`} className="relative">
{/* eslint-disable-next-line @next/next/no-img-element -- kleine Admin-Vorschau, next/image lohnt hier nicht. */}
<img src={src} alt="" className="aspect-square w-full object-cover border border-ink/10" />
{/* Ueber next/image statt als rohes <img> — siehe Begruendung in
admin-image-field.tsx. Hier wiegt es schwerer, weil ein
Produkt mehrere Bilder tragen kann und sonst JEDES davon in
voller Groesse fuer eine Miniatur geladen wuerde. */}
<div className="relative aspect-square w-full overflow-hidden border border-ink/10">
<Image src={src} alt="" fill sizes="160px" className="object-cover" />
</div>
<button
type="button"
onClick={() => removeAt(i)}