Το LCP — Largest Contentful Paint — είναι το μετρικό Core Web Vitals στο οποίο αφιερώνω τον περισσότερο χρόνο για sites πελατών. Μετράει πόσο χρόνο χρειάζεται μέχρι να αποδοθεί το μεγαλύτερο ορατό στοιχείο στη σελίδα. Για τα περισσότερα marketing sites, αυτό το στοιχείο είναι μια εικόνα hero. Για τα περισσότερα Next.js sites που έχω ελέγξει, είναι επίσης εκεί που βρίσκεται το μεγαλύτερο κενό βαθμολογίας.
Η επίτευξη LCP κάτω από 2,5 δευτερόλεπτα σε κινητό είναι εφικτή με το ενσωματωμένο component εικόνας του Next.js, αλλά μόνο αν το χρησιμοποιείς σωστά. Τα περισσότερα sites δεν το κάνουν.
Τι κάνει πραγματικά το <Image> του Next.js
Το next/image είναι ένα wrapper γύρω από ένα pipeline βελτιστοποίησης εικόνας από την πλευρά του διακομιστή. Όταν εξυπηρετεί μια εικόνα:
- Τη μετατρέπει σε WebP ή AVIF βάσει του header
Acceptτου browser - Αλλάζει το μέγεθός της στις ακριβείς διαστάσεις που ζητήθηκαν
- Την εξυπηρετεί από CDN edge cache σε επόμενες αιτήσεις
- Προσθέτει attributes
widthκαιheightγια αποφυγή layout shift (CLS)
Τίποτα από αυτά δεν είναι μαγεία — ο browser εξακολουθεί να πρέπει να κατεβάσει την εικόνα. Αλλά κατεβάζεις WebP στο ακριβές μέγεθος εμφάνισης αντί για ένα JPEG 4MB σε 4000px πλάτος. Η διαφορά σε κινητό είναι 5-10× μικρότερα μεγέθη αρχείων.
Το prop priority δεν είναι προαιρετικό για εικόνες πάνω από τη γραμμή κύλισης
Από προεπιλογή, το next/image φορτώνει εικόνες lazy — δεν αρχίζει να τις ανακτά μέχρι η εικόνα να είναι κοντά στο viewport. Για μια εικόνα hero που είναι ορατή αμέσως κατά τη φόρτωση σελίδας, αυτό είναι ανάποδο. Το lazy loading του στοιχείου LCP καθυστερεί το LCP.
Πρόσθεσε priority σε οποιαδήποτε εικόνα είναι ορατή χωρίς κύλιση:
<Image
src="/hero.jpg"
alt="..."
fill
priority // προφορτώνει την εικόνα· χωρίς lazy loading
sizes="100vw"
/>
Χωρίς priority, το Lighthouse θα επισημάνει μια προειδοποίηση "Largest Contentful Paint image was lazily loaded" και το LCP σου θα είναι 0,5-1,5 δευτερόλεπτα χειρότερο από ό,τι χρειάζεται.
Το prop sizes και γιατί έχει σημασία
Το next/image παράγει ένα srcset με πολλαπλές επεξεργασμένες εκδόσεις της εικόνας. Ο browser χρησιμοποιεί το attribute sizes για να αποφασίσει ποια έκδοση να κατεβάσει. Αν το sizes είναι λάθος, ο browser κατεβάζει μια μεγαλύτερη εικόνα από ό,τι χρειάζεται.
// Hero — πλήρες πλάτος viewport
<Image src="..." fill sizes="100vw" priority />
// Κάρτα σε πλέγμα 3 στηλών
<Image src="..." fill sizes="(max-width: 768px) 100vw, 33vw" />
// Μικρογραφία sidebar
<Image src="..." width={120} height={90} sizes="120px" />
Λανθασμένη τιμή sizes σημαίνει ότι ο browser κατεβάζει μια εικόνα 1200px για μια θέση 400px. Αυτό είναι χαμένο bandwidth και πιο αργό LCP.
Απομακρυσμένες εικόνες και η παγίδα remotePatterns
Αν οι εικόνες σου φιλοξενούνται σε εξωτερική υπηρεσία (Cloudinary, Uploadthing, S3), πρέπει να προσθέσεις το hostname στο remotePatterns στο next.config.ts. Αυτό τεκμηριώνεται, αλλά το μήνυμα σφάλματος όταν το ξεχνάς δείχνει στο component εικόνας, όχι στο config.
// next.config.ts
const config: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**.cloudinary.com',
},
],
},
};
Το glob με διπλό αστερίσκο (**) ταιριάζει με οποιοδήποτε subdomain.
Στατικές εισαγωγές για τοπικές εικόνες
Για εικόνες που βρίσκονται στο repository σου (λογότυπα, εικονίδια, στατικές εικόνες hero), χρησιμοποίησε στατικές εισαγωγές αντί για string paths:
import heroImage from '@/public/hero.jpg';
<Image src={heroImage} alt="..." priority />
Οι στατικές εισαγωγές επιτρέπουν στο Next.js να γνωρίζει τις διαστάσεις εικόνας κατά τη χρόνο κατασκευής, που σημαίνει ότι μπορεί να παράγει σωστά attributes width και height χωρίς να τα καθορίσεις. Ενεργοποιεί επίσης τη δημιουργία blurHash για το prop placeholder="blur" — γεμίζει τον χώρο εικόνας με προεπισκόπηση χαμηλής ανάλυσης ενώ φορτώνει η πλήρης εικόνα.
Το prop fill και η απαίτηση container
Το fill τοποθετεί την εικόνα για να γεμίσει τον πλησιέστερο τοποθετημένο γονέα. Το πιο κοινό λάθος είναι να ξεχνάς να προσθέσεις position: relative στον container.
// Σωστό
<div className="relative h-[500px] w-full">
<Image src="..." fill alt="..." sizes="100vw" />
</div>
// Σπασμένο — η εικόνα δεν έχει τοποθετημένο γονέα
<div className="h-[500px] w-full">
<Image src="..." fill alt="..." sizes="100vw" />
</div>
Χωρίς τον τοποθετημένο container, η εικόνα γεμίζει τον πλησιέστερο τοποθετημένο γονέα ψηλότερα στο δέντρο — συχνά το viewport.
Μέτρηση του αποτελέσματος
Μετά την εφαρμογή αυτών των αλλαγών σε ένα site πελάτη, η τυπική μέτρηση πριν/μετά είναι Lighthouse στο Chrome DevTools με επιβράδυνση CPU 4× και περιορισμό δικτύου κινητού. Τρέξε το τρεις φορές και υπολόγισε τον μέσο όρο· οι μεμονωμένες εκτελέσεις έχουν πολύ διακύμανση.
Οι αλλαγές παραπάνω — priority prop, σωστά sizes, στατικές εισαγωγές για εικόνες πάνω από τη γραμμή — συνήθως μεταφέρουν το LCP από 4-7 δευτερόλεπτα σε 1,5-2,5 δευτερόλεπτα σε προσομοιωμένη σύνδεση κινητού. Αυτή είναι η διαφορά μεταξύ βαθμολογίας Google στα 40 και βαθμολογίας στα 80.
