Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
YouTube Security VideosGoogle Cloud Tech: Gemini is coming to your city(24.09.2026 um 15:00 Uhr)
AI & KI NachrichtenGoogle’s latest moonshot to put machine learning in space(24.09.2026 um 15:12 Uhr)
Windows Tipps & SecurityPoll: What's your favorite Surface of 2026?(24.09.2026 um 14:58 Uhr)
Sichere ProgrammierungStreaming Materialized Views for Live Read Models (2026)(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungA Day Is Not 86400 Seconds: The DST Bug in Your Date Math(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungSetting up Traefik: reverse proxy with automatic HTTPS(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungA 200 OK response does not prove a secret leak(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungHow hot do you like it?(24.09.2026 um 15:05 Uhr)
YouTube Security VideosGoogle Cloud Tech: Gemini is coming to your city(24.09.2026 um 15:00 Uhr)
AI & KI NachrichtenGoogle’s latest moonshot to put machine learning in space(24.09.2026 um 15:12 Uhr)
Windows Tipps & SecurityPoll: What's your favorite Surface of 2026?(24.09.2026 um 14:58 Uhr)
Sichere ProgrammierungStreaming Materialized Views for Live Read Models (2026)(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungA Day Is Not 86400 Seconds: The DST Bug in Your Date Math(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungSetting up Traefik: reverse proxy with automatic HTTPS(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungA 200 OK response does not prove a secret leak(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungHow hot do you like it?(24.09.2026 um 15:05 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Zero-CLS Images in Next.js 16: LQIP Blur-Up Done Right

You scroll, start reading the headline, and then the hero image finally loads. Boom. The whole paragraph gets shoved down half a screen. You lose your place, your brain sighs, and your website suddenly feels like it was assembled with…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

You scroll, start reading the headline, and then the hero image finally loads.



Boom.



The whole paragraph gets shoved down half a screen.



You lose your place, your brain sighs, and your website suddenly feels like it was assembled with duct tape and hope.



On slow connections, there is another version of the same pain: the layout stays still, but the image goes from a dead gray box to a sharp photo with a harsh visual pop. No easing. No warmth. Just vibes getting absolutely tackled.



I have shipped both bugs before.



They look related, but they are actually two different image-loading problems:




  1. Layout shift

    The browser did not know how much space to reserve before the image loaded.


  2. Abrupt image pop-in

    The space was reserved, but the transition from placeholder to final image felt rough.




The first one is a correctness issue.

The second one is a polish issue.



In this post, I’ll show how I handle both in Next.js 16, using next/image, LQIP blur-up placeholders, and remote image data from a real app-style setup with React 19, Tailwind v4, and Supabase.







Why This Matters for Core Web Vitals



Layout shift is measured by Cumulative Layout Shift, usually called CLS.



Google’s guidance is simple: a good CLS score is 0.1 or less, measured at the 75th percentile of page visits. Anything above 0.25 is considered poor.



A single unreserved hero image can blow through that budget by itself because CLS is based on:




  • how much visible content moved

  • how far it moved

  • how unexpected the movement was



The image “pop” is a different problem. That one is more closely tied to perceived loading quality and often affects your Largest Contentful Paint, or LCP, especially when the image is your above-the-fold hero.



You cannot make a large remote image magically arrive instantly. Sadly, npm install faster-internet is still not real.



But you can make the wait feel intentional.



That is the job of a Low-Quality Image Placeholder, or LQIP.




Reserving space is correctness.

Blur-up is polish.

Do the correctness part even if you skip the polish.








Reserve the Image Box Before the Image Loads



The root cause of image-related CLS is simple:



The browser does not know the final image dimensions early enough, so it cannot reserve the correct space.



The fix is also simple:



Give the browser enough information to calculate the image’s aspect ratio before the image bytes arrive.



With next/image, that usually means passing the intrinsic width and height.




import Image from "next/image";

export function Figure({ src, alt }: { src: string; alt: string }) {
return (
<Image
src={src}
alt={alt}
width={1600}
height={900}
sizes="(max-width: 768px) 100vw, 768px"
style={{ width: "100%", height: "auto" }}
/>
);
}






The important part is this pair:




width={1600}
height={900}






That tells the browser the image has a 16:9 aspect ratio.



Then this part makes it responsive:




style={{ width: "100%", height: "auto" }}






Do not forget height: "auto".



If you set a responsive width but accidentally force the height, you can fight the intrinsic aspect ratio and introduce weird stretching or layout instability.



Not very slay.









When You Do Not Know the Image Dimensions



Sometimes you do not have reliable dimensions available.



Common examples:




  • user uploads

  • CMS images

  • Supabase Storage images

  • S3 images

  • third-party remote URLs



In those cases, fill can be a better fit.



But with fill, the image does not reserve space by itself. The parent container owns the layout.




import Image from "next/image";

export function FillImage({ src, alt }: { src: string; alt: string }) {
return (
<div style={{ position: "relative", aspectRatio: "16 / 9" }}>
<Image
src={src}
alt={alt}
fill
sizes="(max-width: 768px) 100vw, 768px"
style={{ objectFit: "cover" }}
/>
</div>
);
}






For fill, the parent needs:




position: "relative"






And to prevent CLS, the parent also needs a reserved shape:




aspectRatio: "16 / 9"






So your two stable layout patterns are:




<Image width={1600} height={900} />






or:




<div style={{ position: "relative", aspectRatio: "16 / 9" }}>
<Image fill />
</div>






Both can produce zero image-related CLS when used correctly.



Now we can talk about the blur.









Static Imports Get Blur Automatically. Remote Images Do Not.



This is the part that trips up a lot of people.



In Next.js, placeholder="blur" behaves differently depending on where the image comes from.




























Image source Automatic blurDataURL? What you do
Static import Yes Add placeholder="blur"
Remote URL No Generate and pass blurDataURL yourself
Dynamic CMS/Supabase/S3 URL No Store and render your own blurDataURL


For a bundled local image, this is enough:




import Image from "next/image";
import hero from "@/assets/hero.jpg";

export function Hero() {
return (
<Image
src={hero}
alt="Studio desk at dusk"
placeholder="blur"
sizes="100vw"
style={{ width: "100%", height: "auto" }}
/>
);
}






Because hero is a static import, Next.js can inspect the image at build time and generate the blur data automatically.



But real apps usually do not live in static-import paradise.



Real apps have:




  • user uploads

  • remote image URLs

  • database-driven content

  • CMS assets

  • product images

  • avatars

  • galleries



For those, Next.js does not see the image file at build time.



So if you want blur-up for remote images, you need to generate the LQIP yourself.



The cleanest time to do that is usually when the image is uploaded.









Generate a Tiny LQIP at Upload Time



A LQIP is just a very small version of the image, usually encoded as a base64 data URL.



The image can be tiny — around 10 to 20 pixels on the long edge — because next/image will enlarge and blur it anyway.



Here is a browser-side helper that uses a canvas to generate a small base64 JPEG:




// lib/lqip.ts

/**
* Downscale an image File into a tiny base64 LQIP.
*
* The result is designed for next/image's blurDataURL prop.
*/

export async function makeLqip(file: File, maxEdge = 16): Promise<string> {
const bitmap = await createImageBitmap(file);

const scale = maxEdge / Math.max(bitmap.width, bitmap.height);
const width = Math.max(1, Math.round(bitmap.width * scale));
const height = Math.max(1, Math.round(bitmap.height * scale));

const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;

const ctx = canvas.getContext("2d");

if (!ctx) {
throw new Error("2D canvas context unavailable");
}

ctx.drawImage(bitmap, 0, 0, width, height);

bitmap.close();

/**
* Low JPEG quality is fine here.
* The image will be blurred, so compression artifacts are not visible.
*/

return canvas.toDataURL("image/jpeg", 0.5);
}






A few details matter here.



First, createImageBitmap() is nicer than manually creating an <img> element and waiting for onload.



Second, keep the output tiny.



For a blur placeholder, you are not trying to preserve detail. You are trying to preserve the rough color composition while the real image loads.



You can sanity-check the output size like this:




const lqip = await makeLqip(file);

console.log(`${(lqip.length / 1024).toFixed(2)} KB`);






For a 16px JPEG placeholder, I usually expect the string to be comfortably under 1 KB.



If your placeholder is multiple kilobytes, it is too big. At that point, the placeholder starts becoming its own performance problem, which is very “we fixed the bug by adding a new bug.”









Store the LQIP Beside the Image



The placeholder belongs with the same row that owns the image.



If you are using Supabase/Postgres, you can add a nullable column:




alter table public.photos
add column blur_data_url text;






I also like adding a constraint so an accidentally huge placeholder cannot sneak into the database:




alter table public.photos
add constraint blur_data_url_len check (
blur_data_url is null or char_length(blur_data_url) <= 2048
);






This keeps the column flexible but protected.



Nullable matters because older images may not have placeholders yet. Your UI should degrade gracefully instead of crashing because one legacy row is missing blur data.



You should also store the image’s natural dimensions:




alter table public.photos
add column width integer,
add column height integer;






Then, during upload:




const blurDataURL = await makeLqip(file);

const { error } = await supabase.from("photos").insert({
storage_path: path,
width: naturalWidth,
height: naturalHeight,
blur_data_url: blurDataURL,
});

if (error) {
throw error;
}






Now each image row has the three things your frontend needs:




{
width: number;
height: number;
blur_data_url: string | null;
}






The dimensions prevent CLS.



The blur data improves perceived loading.



Separate responsibilities. Clean mental model.









Render Remote Images Safely



Now the payoff.



When you render the image, only enable placeholder="blur" if a valid blurDataURL exists.




import Image from "next/image";

type Photo = {
url: string;
alt: string;
width: number;
height: number;
blurDataURL: string | null;
};

export function PhotoCard({
url,
alt,
width,
height,
blurDataURL,
}: Photo) {
return (
<div
style={{
position: "relative",
aspectRatio: `${width} / ${height}`,
}}
>
<Image
src={url}
alt={alt}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
placeholder={blurDataURL ? "blur" : undefined}
blurDataURL={blurDataURL ?? undefined}
style={{ objectFit: "cover" }}
/>
</div>
);
}






This conditional is important:




placeholder={blurDataURL ? "blur" : undefined}
blurDataURL={blurDataURL ?? undefined}






Do not do this blindly:




placeholder="blur"






If the image is remote and blurDataURL is missing, you are asking Next.js to blur something that does not exist.



The safe version means:




  • images with LQIP get blur-up

  • old rows without LQIP still load normally

  • layout remains stable either way



That last point is the real win.



The parent’s aspectRatio reserves the space, so your CLS stays stable whether the blur placeholder exists or not.



Again:




CLS prevention should not depend on LQIP.

LQIP is visual polish, not layout correctness.










Do Not Skip sizes



This is the sneaky performance footgun.



When using responsive images, sizes tells the browser how wide the image will be at different viewport widths.



For example:




sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"






This means:




  • phones: image is full viewport width

  • tablets/small laptops: image is half viewport width

  • desktop: image is one-third viewport width



That matches a common responsive gallery layout.



If you skip sizes, the browser may assume the image renders at 100vw. That means a small grid thumbnail can accidentally download a much larger image than needed.



Your page still works, but it wastes bandwidth.



Classic “it works on my machine” energy.



For a single article image, this might be fine:




sizes="(max-width: 768px) 100vw, 768px"






For a three-column gallery, this is better:




sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"






Match sizes to the actual rendered layout.



That is the rule.









The LCP Image: Use preload, Not priority



In older Next.js projects, you might see this:




<Image priority />






In Next.js 16, priority is deprecated in favor of preload.



For your above-the-fold hero image, use:




import Image from "next/image";
import hero from "@/assets/hero.jpg";

export function HeroImage() {
return (
<Image
src={hero}
alt="Studio desk at dusk"
placeholder="blur"
preload
sizes="100vw"
style={{ width: "100%", height: "auto" }}
/>
);
}






preload tells Next.js to inject a preload link so the browser can start fetching the image earlier.



Use this carefully.



You usually want it on one image: the image most likely to be your LCP element.



Do not put preload on every image in a gallery.



That is like inviting 30 people through one doorway at the same time and wondering why nobody is moving.



For below-the-fold images, let lazy loading do its job.









One More Next.js 16 Detail: Image Qualities



In Next.js 16, if you use custom image quality values, make sure your next.config.ts allows them.



For example, if you pass:




<Image quality={90} />






Then your config should include that quality:




// next.config.ts

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
images: {
qualities: [75, 90],
},
};

export default nextConfig;






If you do not need custom quality values, the default is fine.



Most of the time, I leave images at the default unless I have a specific reason to change them.









Full Example: Remote Supabase Image With Zero CLS and Blur-Up



Here is the complete pattern in one place.




import Image from "next/image";

type Photo = {
id: string;
url: string;
alt: string | null;
width: number;
height: number;
blurDataURL: string | null;
};

type PhotoGridProps = {
photos: Photo[];
};

export function PhotoGrid({ photos }: PhotoGridProps) {
return (
<section className="grid gap-6 md:grid-cols-2 xl:grid-cols-3">
{photos.map((photo) => (
<article key={photo.id} className="overflow-hidden rounded-2xl">
<div
className="relative w-full"
style={{
aspectRatio: `${photo.width} / ${photo.height}`,
}}
>
<Image
src={photo.url}
alt={photo.alt ?? ""}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1280px) 50vw, 33vw"
placeholder={photo.blurDataURL ? "blur" : undefined}
blurDataURL={photo.blurDataURL ?? undefined}
className="object-cover"
/>
</div>
</article>
))}
</section>
);
}






This gives you:




  • reserved layout space

  • responsive image loading

  • optional blur-up

  • safe fallback for old rows

  • no dependency on static imports

  • no server-side image processing required



Very clean. Very adult. Very “I actually care about performance.”









Testing It



After implementing this, test it like a user on a cursed coffee shop Wi-Fi connection.



Open Chrome DevTools:




  1. Go to the Network tab.

  2. Enable throttling.

  3. Pick Slow 4G.

  4. Reload the page.

  5. Watch the image areas before the final images load.



You want to see:




  • the layout stays still

  • image boxes are reserved immediately

  • blur placeholders appear quickly

  • final images fade in without a harsh visual jump



Then check Lighthouse or your real-user monitoring data for CLS.



The goal is not just passing a synthetic test. The goal is making the page feel stable while real humans use it.









Common Mistakes






Mistake 1: Using placeholder="blur" on remote images without blurDataURL



This will not work reliably.



Remote images need your own placeholder.




placeholder={blurDataURL ? "blur" : undefined}
blurDataURL={blurDataURL ?? undefined}












Mistake 2: Thinking LQIP fixes CLS



It does not.



LQIP improves the loading transition.



Dimensions or aspect-ratio prevent layout shift.



Different bugs. Different fixes.









Mistake 3: Using fill without reserving parent space



This is unstable:




<div style={{ position: "relative" }}>
<Image fill src={src} alt={alt} />
</div>






This is stable:




<div style={{ position: "relative", aspectRatio: "16 / 9" }}>
<Image fill src={src} alt={alt} />
</div>












Mistake 4: Skipping sizes



If the image is responsive, write a real sizes string.



Otherwise, the browser may download bigger images than necessary.









Mistake 5: Preloading too many images



Only preload the likely LCP image.



Everything else can lazy-load.



Preloading a whole gallery is not optimization. It is network chaos in a nice jacket.









The Takeaway



Image loading has two separate problems:




  1. The page shifts

  2. The image appears too abruptly



Fix the first one by reserving layout space.



Use either:




<Image width={1600} height={900} />






or:




<div style={{ position: "relative", aspectRatio: "16 / 9" }}>
<Image fill />
</div>






Fix the second one with a tiny LQIP blur placeholder.



For remote images, generate that placeholder yourself at upload time, store it beside the image, and render it conditionally:




placeholder={blurDataURL ? "blur" : undefined}
blurDataURL={blurDataURL ?? undefined}






Then add a real sizes string, preload only your LCP image, and test the page on a throttled connection.



That is the difference between a page that feels broken and one that feels deliberate.

CTI Threat Relationship Graph2 Knoten / 1 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
SOC Incident Playbook: Remote Code Execution (RCE) Defense
title: Detect Exploitation - Zero-CLS Images in Next.js 16: LQIP Blur-Up Done Right
id: bfdc2159-ec16-46a7-8ea2-e6c419d13f5e
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-24
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-24"
        description = "YARA Signature for "
    strings:
        $str = "Zero-CLS Images in Next.js 16:" ascii wide
    condition:
        any of them
}
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich Zero-CLS Images in Next.js 16: LQIP Blur.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Zero-CLS Images in Next.js 16: LQIP Blur-Up Done Right

Thematisch verwandte Begriffe: ZeroCLS, Images, Nextjs, LQIP · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-97179 | A security vulnerability has been detected in O2OA up to 9.5.3/10.0.2. T…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel TTP ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick