← Guider för utvecklare
Prestanda10 min läsning

Bildoptimering med next/image

En djupdykning i next/image: automatisk formatkonvertering, lazy loading, blur placeholder, responsive bilder och vanliga fallgropar.

Bilder är ofta den tyngsta resursen på en webbsida och den vanligaste orsaken till dåliga Core Web Vitals. En okomprimerad JPEG som levereras i fel storlek till fel enhet kan ensam fälla ett LCP-betyg. next/image löser de flesta av de problemen automatiskt, men komponenten har fler inställningar än de flesta utvecklare känner till. Den här guiden går igenom allt från grundläggande användning till responsiva storlekar, prioritering och blur placeholders.

Vad next/image löser automatiskt

En vanlig <img>-tagg gör exakt vad du säger åt den: den laddar bilden du pekar på, i den storlek den råkar ha, i det format den råkar vara sparad i. Allt annat är ditt ansvar.

next/image tar hand om det du annars hade behövt göra manuellt:

Formatkonvertering. Komponenten levererar automatiskt WebP eller AVIF till webbläsare som stöder dessa format, och faller tillbaka till JPEG eller PNG för äldre klienter. WebP är typiskt 25-30 procent mindre än JPEG vid samma kvalitet. AVIF krymper ytterligare, men genereras något långsammare.

Automatisk storleksändring. Next.js genererar en version av bilden i exakt den storlek som begärs. En 4000 pixlar bred fotografibild serveras aldrig till en mobiltelefon som bara behöver 400 pixlar.

Lazy loading som standard. Bilder under the fold laddas inte förrän användaren är på väg att nå dem. Det sparar bandbredd och snabbar upp den initiala sidladdningen.

Reserverad plats i layouten. Komponenten injicerar width och height-attribut baserat på bildens dimensioner, vilket förhindrar Cumulative Layout Shift. Sidan hoppar inte till när bilden laddas in.

Med en vanlig <img> är var och en av dessa punkter ett separat problem att lösa, ofta med extra JavaScript, build-steg eller manuell konfiguration.

Grundläggande användning

Importera komponenten från next/image:

import Image from "next/image";

Det finns två grundläggande sätt att använda den, och de beter sig lite olika beroende på om bilden är lokal eller URL-baserad.

Lokal bild via import

import Image from "next/image";
import profilBild from "@/public/profil.jpg";

// Lokal bild - dimensioner härleds automatiskt
<Image src={profilBild} alt="Profilbild" />;

När du importerar en lokal bildfil analyserar Next.js filen vid byggtid och extraherar bredden och höjden. Du behöver inte ange width eller height eftersom de redan är kända. Du får också ett kompileringsfel om du försöker skicka fel typ till src.

URL-baserad bild

// URL-baserad bild - dimensioner måste anges
<Image
  src="https://cdn.exempel.se/hero.jpg"
  width={1200}
  height={630}
  alt="Hero"
/>

För externa bilder vet Next.js ingenting om filen i förväg. Dimensionerna måste anges explicit så att webbläsaren kan reservera rätt plats i layouten redan innan bilden har laddats. Utan dem riskerar du layout shift.

Skillnaden i korthet: lokala importer ger automatiska dimensioner och typkontroll, URL-bilder kräver att du anger dem manuellt.

sizes och responsiva bilder

Flest utvecklare missar den här inställningen, och den har störst effekt på faktisk bandbreddsanvändning.

När du inte anger sizes antar webbläsaren att bilden tar upp hela viewportens bredd, 100vw. Det innebär att en mobiltelefon med en 390 pixlar bred skärm laddar ner en bild optimerad för 390 pixlar, men en desktopanvändare med 1440 pixlar bred skärm laddar ner en som är 1440 pixlar bred. Det är korrekt för en hero-bild som faktiskt spänner över hela bredden, men om bilden bara är ett kort i ett rutnät som tar upp halva skärmen på desktop laddas ändå en onödigt stor version ner.

sizes-attributet berättar för webbläsaren hur bred bilden faktiskt är vid varje breakpoint, med CSS media-query-syntax:

// Hero - täcker hela bredden
<Image src={hero} alt="Hero" fill sizes="100vw" />

// Kort i ett rutnät
<Image
  src={bild}
  alt="Produktbild"
  fill
  sizes="(max-width: 768px) 100vw, 50vw"
/>

Det andra exemplet säger: under 768 pixlar bred viewport är bilden 100 procent av viewportens bredd. Annars är den ungefär hälften. Webbläsaren använder det här för att välja rätt källbild från srcset och laddar bara ner det den behöver.

fill-läget

fill är ett alternativ till att ange explicit width och height. Istället för att ha en fast storlek fyller bilden ut sin närmaste positionerade föräldracontainer. Det är praktiskt för situationer där du vill att bilden ska fylla ett område vars storlek bestäms av CSS snarare än av bildinnehållet själv, till exempel ett kort med fast höjd.

Containern behöver position: relative (eller absolute/fixed) och explicita dimensioner. Annars vet inte bilden vad den ska fylla ut:

.bildContainer {
  position: relative;
  width: 100%;
  height: 300px;
}
<div className={styles.bildContainer}>
  <Image
    src={bild}
    alt="Produktbild"
    fill
    sizes="(max-width: 768px) 100vw, 50vw"
  />
</div>

En sak att ha koll på: fill använder object-fit: fill som standard, vilket sträcker ut bilden för att fylla containern istället för att beskära den. I nästan alla praktiska fall vill du ha object-fit: cover:

<Image
  src={bild}
  alt="Produktbild"
  fill
  sizes="(max-width: 768px) 100vw, 50vw"
  style={{ objectFit: "cover" }}
/>

Utan style={{ objectFit: 'cover' }} sträcks bilden ut för att fylla sin container istället för att beskäras så att den passar.

Utan sizes när du använder fill laddas alltid en full-viewport-bred version. Det är sällan vad du vill.

priority och LCP

Largest Contentful Paint mäter när det största synliga elementet på sidan är färdigladdat. Det är oftast hero-bilden, en stor rubrik eller en featured-bild nära toppen. Google använder LCP som en av sina Core Web Vitals-mätvärden, och det påverkar direkt hur sidan rankas i sökresultat.

next/image lazy-laddar bilder som standard. Det är rätt beteende för bilder under the fold, men för hero-bilden som visas direkt när sidan öppnas är det kontraproduktivt. Webbläsaren börjar inte ladda ner bilden förrän den har rullat den i bild, vilket med lazy loading innebär ungefär när den dyker upp i viewporten. För en bild som redan är synlig från start innebär det en onödig fördröjning och ett sämre LCP-värde.

priority-attributet stänger av lazy loading för den specifika bilden och lägger dessutom till ett <link rel="preload">-element i <head> så att webbläsaren börjar ladda bilden så tidigt som möjligt:

<Image src={heroBild} alt="Hero" fill priority sizes="100vw" />

En viktig detalj: priority ska bara sättas på bilden som faktiskt är LCP-elementet, alltså den första synliga bilden. Om du sätter det på alla bilder på sidan tappar du lazy loading helt och hållet, och sidan laddar allt på en gång oavsett om det är synligt eller inte. Det slår tillbaka och försämrar prestandan.

Tumregel: en sida, ett priority.

Blur placeholder

Blur placeholder är en teknik för att visa en suddig lågupplöst version av bilden medan den riktiga laddas in. Det ger ett mer polerat intryck jämfört med ett tomt grått fält och minskar den upplevda laddningstiden.

För lokala bilder genererar Next.js blurDataURL automatiskt vid byggtid:

// Lokal bild - blur genereras automatiskt
<Image src={lokalBild} alt="Produktbild" placeholder="blur" />

Du behöver inte göra någonting mer. Next.js analyserar bildfilen och skapar en tiny base64-kodad version som kan bäddas in direkt i HTML.

För fjärrbilder finns den informationen inte tillgänglig vid byggtid, så du måste tillhandahålla blurDataURL manuellt:

// Fjärrbild - kräver blurDataURL
<Image
  src="https://cdn.exempel.se/bild.jpg"
  width={800}
  height={600}
  alt="Produktbild"
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,/9j/4AAQ..."
/>

blurDataURL är en base64-kodad bild, vanligtvis en 10x10 till 20x20 pixlar stor version av originalet, kodad som en data-URI. Det ser klumpigt ut att hantera manuellt, men paketet plaiceholder kan generera dessa värden automatiskt vid byggtid för en lista med fjärrbilder. Du kör det som ett build-steg, sparar värdena i en JSON-fil och läser in dem i dina komponenter. Notera att plaiceholder körs vid byggtid och sparar resultatet som ett statiskt värde i din kod, inte som en beräkning som sker per förfrågan i körtid.

Remote patterns

För att next/image ska kunna optimera externa bilder måste deras ursprungsdomäner vara tillåtna i konfigurationen. Det är en säkerhetsåtgärd: utan en allowlist kan vem som helst konstruera en URL till Next.js bildoptimerings-API och tvinga din server att ladda ner och bearbeta godtyckliga externa bilder, på din bekostnad.

Konfigurationen görs i next.config.js under images.remotePatterns:

/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "ditt-cdn.se",
        pathname: "/bilder/**",
      },
    ],
  },
};

module.exports = nextConfig;

Håll allowlisten så specifik som möjligt. Ange hostname exakt och begränsa gärna med pathname om bilderna ligger i en känd sökväg. En bred wildcard på hostname som tillåter alla subdomäner på ett domänsläkte öppnar upp mer än nödvändigt.

Om du försöker använda en extern bild som inte är allowlistad kastar Next.js ett fel och vägrar ladda bilden. Det är irriterande i utveckling men skyddar dig i produktion.

Du kan också begränsa vilka portar och protokoll som är tillåtna:

remotePatterns: [
  {
    protocol: 'https',
    hostname: 'images.sanity.io',
  },
  {
    protocol: 'https',
    hostname: '**.cloudfront.net',
    pathname: '/uploads/**',
  },
],

Notera att ** matchar flera path-segment, medan * bara matchar ett segment utan snedstreck. I ett hostname-mönster matchar ** hur många subdomäner som helst, så **.cloudfront.net matchar både xyz.cloudfront.net och abc.xyz.cloudfront.net.

Vanliga misstag

1. Saknad sizes på fill- och responsiva bilder

Utan sizes antar webbläsaren att bilden är lika bred som viewporten och laddar en onödigt stor version på mobil. Det är det vanligaste orsaken till att next/image inte levererar den bandbreddsbesparingen man förväntar sig. Sätt alltid sizes när du använder fill eller när bilden är smalare än hela viewporten.

2. priority på alla bilder

Det är frestande att lägga till priority på alla bilder för att "vara säker", men det stänger av lazy loading helt. Varje bild på sidan laddas direkt vid sidladdning, oavsett om den är synlig eller inte. Resultatet är en tyngre initial nedladdning och paradoxalt nog ett sämre LCP för hero-bilden, eftersom nätverket konkurrerar med allt annat. Begränsa priority till den ena bild som faktiskt är LCP-elementet.

3. Tomt eller saknat alt-attribut

alt="" är korrekt för rent dekorativa bilder som inte tillför information. För alla andra bilder ska alt beskriva vad bilden visar, på ett sätt som är meningsfullt för någon som inte kan se den. Saknat alt är ett tillgänglighetsfel och ett förlorat SEO-signal. Undvik generiska värden som "bild" eller filnamn.

4. Saknade dimensioner på URL-bilder

En URL-baserad bild utan width och height orsakar layout shift: webbläsaren reserverar ingen plats i layouten innan bilden laddas, och sidan hoppar till när den väl dyker upp. Det ger ett högt CLS-värde och en märkbart ryckig upplevelse. Ange alltid dimensioner explicit för externa bilder.

5. Vanlig <img> istället för <Image>

Det händer lätt vid kopiering från ett gammalt projekt eller ett externt kodexempel. En vanlig <img> kringgår all optimering som Next.js erbjuder: ingen formatkonvertering, ingen automatisk storleksändring, inget lazy loading, ingen CLS-prevention. Next.js varnar för det här i development-läge, men varningen är lätt att missa i ett aktivt terminalfönster. Sätt upp ESLint-regeln @next/next/no-img-element för att fånga det vid linting.

6. Alltför bred eller saknad remote patterns-konfiguration

Antingen är domänen inte allowlistad alls, och bilderna laddas inte i produktion, eller så är wildcard-mönstret så brett att det tillåter optimering av bilder från domäner du inte kontrollerar. Båda är fel. Gå igenom vilka externa bilddomäner din app faktiskt använder och allowlista exakt dem, med så specifika sökvägar som möjligt.

Kevin Sommerstein
Kevin SommersteinGrundare, Developly

Medgrundare av Developly Sweden och webbutvecklare med 8 års erfarenhet inom JavaScript, React och Next.js.

LinkedIn →