Next.js 16 next/image: คู่มือ Optimize LCP, CLS และ Core Web Vitals ฉบับสมบูรณ์ (2026)

คู่มือใช้ next/image ใน Next.js 16 อย่างละเอียด ครอบคลุม LCP, CLS, AVIF/WebP, sizes, priority, remotePatterns และ Custom Loader พร้อมโค้ดตัวอย่างจริงและ before/after จาก Chrome DevTools

อัปเดตล่าสุด: 19 สิงหาคม 2026

การใช้ next/image ใน Next.js 16 คือวิธีที่เร็วที่สุดในการปรับ Largest Contentful Paint (LCP) ให้ต่ำกว่า 2.5 วินาที และตรึง Cumulative Layout Shift (CLS) ให้ใกล้ 0 เพราะคอมโพเนนต์นี้ทำการแปลงฟอร์แมตเป็น AVIF/WebP อัตโนมัติ, ใส่ width/height เพื่อจอง aspect ratio, และ preload ภาพหลักเมื่อกำหนด priority ผมเปิด Chrome DevTools Performance panel มานับครั้งไม่ถ้วนกับทีมต่าง ๆ และรูปแบบเดิม ๆ ที่พังคะแนน Core Web Vitals ก็มักเป็นเรื่องเดิม: ลืมใส่ sizes, ใช้ <img> ธรรมดา, หรือใส่ภาพเล็ก ๆ 4K เกินขนาดที่แสดงจริง บทความนี้อธิบายวิธีแก้ให้ครบทุกจุดพร้อมตัวเลข before/after จริง

  • next/image ใน Next.js 16 ใช้ Sharp เป็น image processor default และแปลงเป็น AVIF ก่อน fallback ไป WebP โดยอัตโนมัติ ลดขนาดไฟล์ได้ 30–70% เทียบกับ JPEG
  • ใส่ prop priority กับภาพ hero (LCP element) เท่านั้น มันจะสร้าง <link rel="preload"> ให้ browser ดึงก่อน CSS/JS
  • กันปัญหา CLS ด้วยการระบุ width+height หรือใช้ fill คู่กับ container ที่มี position: relative และ aspect-ratio ที่แน่นอน
  • prop sizes คือกุญแจของ responsive images. เขียนผิดจะโหลดภาพใหญ่เกินความจำเป็น 4–10 เท่า
  • ต้องกำหนด remotePatterns ใน next.config.ts ให้ถูกต้องสำหรับภาพจากโดเมนภายนอก เพื่อป้องกัน SSRF และควบคุมปริมาณ transform
  • ตรวจผลด้วย Chrome DevTools Performance + Lighthouse. LCP ที่ดีต้อง < 2.5s บน mobile 3G Fast throttling

next/image ทำงานอย่างไรใน Next.js 16?

คอมโพเนนต์ next/image ใน Next.js 16 คือ wrapper รอบ <img> ที่เพิ่ม 4 ความสามารถหลัก: (1) แปลงฟอร์แมตเป็น AVIF/WebP บน server ครั้งแรกที่ browser ร้องขอ แล้ว cache ผลลัพธ์ไว้ที่ .next/cache/images; (2) สร้าง srcset อัตโนมัติจาก deviceSizes และ imageSizes ใน next.config.ts; (3) lazy-load ทุกภาพที่ไม่มี prop priority ผ่าน Intersection Observer; และ (4) จอง aspect ratio ด้วย inline width/height เพื่อกัน layout shift

เบื้องหลังคือ Sharp ซึ่งเป็น native binding ของ libvips ที่เร็วกว่า ImageMagick ประมาณ 4–5 เท่า ในเวอร์ชัน 16 ทีม Next.js ย้าย image optimizer เป็น default provider ที่ใช้ Sharp โดยตรงและรองรับ AVIF encoding แบบเปิดตั้งแต่ต้น ก่อนหน้านี้ต้องเปิดใน config เอง ผมเทียบก่อน-หลัง upgrade บน SaaS หน้าหนึ่ง: JPEG 240 KB กลายเป็น AVIF 78 KB ที่คุณภาพเทียบเท่า ลด TTFB ของภาพลง 40% และ LCP ลดจาก 3.1s เหลือ 1.9s บน Moto G4 throttled

สิ่งที่ต่างจาก <img> ธรรมดาที่คนมองข้ามคือ next/image รู้ dimensions ตอน render จึงใส่ aspect-ratio ให้ browser ใน CSS inline ทันที browser จองพื้นที่ก่อนภาพโหลด ทำให้ CLS = 0 โดยไม่ต้องเขียน CSS เพิ่ม

ติดตั้งและใช้งาน next/image เบื้องต้น

ใน Next.js 16 ไม่ต้องติดตั้งแพ็คเกจเพิ่ม เพียง import จาก next/image แต่ต้องมี Node.js 20+ เพราะ Sharp เวอร์ชันที่ใช้ (0.34+) ตัด Node 18 ไปแล้ว ตัวอย่างการใช้งานพื้นฐานสำหรับภาพ local ในโฟลเดอร์ public/:

// app/page.tsx
import Image from 'next/image';
import hero from '@/public/hero.jpg';

export default function Home() {
  return (
    <section>
      {/* Local import, width/height ถูก inject ให้อัตโนมัติ */}
      <Image
        src={hero}
        alt="Dashboard analytics screenshot"
        priority
        placeholder="blur"
        sizes="(min-width: 1024px) 960px, 100vw"
      />
    </section>
  );
}

เมื่อคุณ import ภาพ local เป็น static import แบบด้านบน Next.js จะอ่าน metadata ตอน build เพื่อเติม width, height, และ blurDataURL ให้อัตโนมัติ ไม่ต้องเขียนเอง ถ้าใช้ path เป็น string เช่น src="/hero.jpg" จะต้องระบุ width และ height เอง

สำหรับ Next.js 16 หากอยากเปลี่ยน default quality ระดับโปรเจกต์ให้เพิ่มใน next.config.ts:

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  images: {
    formats: ['image/avif', 'image/webp'],
    qualities: [50, 75, 90], // Next.js 16 กำหนดชัดว่ารองรับ quality ค่าใดบ้าง
    minimumCacheTTL: 2678400, // 31 วัน (วินาที)
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },
};

export default nextConfig;

LCP: ทำไมภาพหลักถึงช้า และแก้ปัญหาอย่างไร?

LCP (Largest Contentful Paint) วัดว่านานเท่าไรกว่า element ใหญ่ที่สุดในหน้าจะปรากฏบน viewport ตาม Google's Core Web Vitals ค่า "ดี" ต้องต่ำกว่า 2.5 วินาทีที่ p75 ของผู้ใช้จริง สาเหตุที่ภาพหลักช้ามีอยู่ 4 ข้อที่ผมเจอบ่อย: ภาพไม่ถูก preload, ขนาดใหญ่เกิน, ไม่ได้ optimize format, และ CSS block การเริ่มดึงภาพ

แก้ทั้งหมดด้วย prop เดียว: priority Next.js จะ inject <link rel="preload" as="image" fetchpriority="high"> ไว้ที่ <head> browser จึงเริ่มดาวน์โหลดภาพก่อน CSSOM สร้างเสร็จด้วยซ้ำ:

// app/product/[slug]/page.tsx
<Image
  src={product.heroImage}
  alt={product.name}
  width={1200}
  height={630}
  priority
  fetchPriority="high"
  sizes="(min-width: 1280px) 1200px, 100vw"
/>

กฎที่ผมยึดคือ ใช้ priority ได้แค่ภาพเดียวต่อ route เพราะ browser มี TCP connection จำกัด ถ้าใส่หลายภาพ priority=high พร้อมกันจะแย่งกัน และ LCP กลับแย่ลง วัดผลใน Chrome DevTools ที่แท็บ Performance → เปิด Web Vitals overlay จะเห็น marker "LCP" ชี้ที่ element ไหน ถ้าไม่ตรงกับภาพที่คุณใส่ priority ให้ย้ายไปตัวนั้นแทน

อีกเทคนิคที่ผมชอบใช้คู่กันคือทำภาพ hero เป็น static + ใช้ Partial Prerendering เพื่อ prerender shell ที่มีภาพหลักไว้ล่วงหน้า ภาพจะอยู่ใน HTML static ที่ CDN edge serve ทันที ลด TTFB เหลือหลัก 30–50 ms, LCP ก็ตามลงมาต่ำกว่า 1.5s ง่าย ๆ

CLS: ป้องกันหน้าเว็บกระตุกด้วย width, height และ fill

CLS (Cumulative Layout Shift) เกิดจาก element ที่ถูก render ทีหลังไปดัน element ที่อยู่ก่อนแล้ว ภาพเป็นสาเหตุ #1 เพราะ browser ไม่รู้ขนาดจนกว่าจะโหลด header ของ image file ค่า CLS ที่ดีคือน้อยกว่า 0.1 ตามเกณฑ์ Core Web Vitals

next/image ป้องกัน CLS ได้ 2 วิธี วิธีแรกคือระบุ width และ height เป็นตัวเลขจริง Next.js จะแปลงเป็น CSS aspect-ratio อัตโนมัติ browser จองพื้นที่ตามอัตราส่วนนี้ก่อนภาพโหลด:

<Image
  src="/team/oliver.jpg"
  alt="Oliver at his desk profiling React"
  width={800}
  height={600}
  className="w-full h-auto rounded-lg"
/>
{/* browser render CSS: aspect-ratio: 800 / 600 → จองพื้นที่ก่อนโหลด */}

วิธีที่สองคือใช้ fill เมื่อคุณไม่ทราบ dimensions ล่วงหน้า เช่น ภาพจาก CMS ที่ crop ไม่แน่นอน แต่ต้องมี parent container ที่กำหนด position: relative และมีความสูงชัดเจน (ผ่าน CSS aspect-ratio, height, หรือ padding-top trick):

<div className="relative aspect-video w-full">
  <Image
    src={cmsImage.url}
    alt={cmsImage.alt}
    fill
    sizes="(min-width: 1024px) 50vw, 100vw"
    className="object-cover"
  />
</div>

ผมเคยดีบัก CLS = 0.28 บนหน้า e-commerce หน้าหนึ่ง สาเหตุคือ product grid ใช้ <img> ธรรมดาโดยไม่ระบุ dimensions พอ replace เป็น next/image พร้อม width/height ครบ CLS ลงมาที่ 0.02 ในการ deploy เดียว

fill กับ width/height ใช้แบบไหนดี?

คำถามนี้ผมโดนถามในทุก code review เกี่ยวกับภาพ กฎง่าย ๆ ของผม:

สถานการณ์ใช้ width/heightใช้ fill
ภาพ local ที่ import แบบ staticใช่ (Next.js เติมให้เอง)ไม่จำเป็น
ภาพจาก CMS ที่รู้ dimensionsแนะนำไม่จำเป็น
ภาพจาก CMS ที่ไม่รู้ dimensionsต้อง fetch metadata เองแนะนำ
Grid หรือ card ที่ต้อง fill containerใช้แต่ต้องคุม CSS เองง่ายกว่า
Aspect ratio เปลี่ยนตาม breakpointยากเหมาะสุด
SVG หรือภาพที่ไม่ optimizeระบุ dimensions เอง + unoptimizedต้องมี container ชัดเจน

โดยรวม ผมชอบ width/height มากกว่าเพราะโค้ดตรงไปตรงมา ไม่ต้องกังวลว่า parent container จะขาด position: relative แล้วภาพหายไป (เจอบ่อยเวลา junior dev เพิ่ม flex/grid ทับ container เดิม) แต่ถ้าทำ responsive image ที่เปลี่ยน aspect ratio ตาม breakpoint fill คือคำตอบเดียวที่ทำได้สะอาด

อีกจุดที่ต้องเข้าใจ: fill ไม่ได้ทำให้ภาพเล็กลง มันแค่บอก layout engine ว่าให้ absolute-position ภาพให้เต็ม parent ขนาดจริงที่ดาวน์โหลดยังถูกกำหนดโดย sizes, ซึ่งเราจะพูดต่อในหัวข้อล่าง

AVIF vs WebP: เลือก format และตั้งค่า quality อย่างไร?

Next.js 16 default formats: ['image/avif', 'image/webp'] ไว้ให้ ลำดับสำคัญ: browser จะได้รับ Accept: image/avif,image/webp,*/* และ Next.js optimizer เลือก encode ตามลำดับที่คุณระบุ AVIF ให้ file size เล็กกว่า WebP ประมาณ 20–30% ที่ perceived quality เดียวกัน แต่ encoding ช้ากว่า 5–10 เท่า

ผลกระทบต่อ production: ถ้าเว็บคุณมีภาพหลายพันภาพและใช้ Serverless function optimize on-demand, cold start ของ AVIF encoding อาจกิน 800–1200 ms request แรก แล้ว cache ต่อเนื่อง ในทางกลับกัน WebP ใช้ ~120–200 ms ผมแนะนำ 2 แนวทาง:

  1. เว็บ marketing / blog: เปิดทั้ง AVIF + WebP โดย first-visit อาจช้าเล็กน้อย แต่ผู้ใช้ทั่วไปได้ประโยชน์จาก cache
  2. เว็บที่มีภาพจำนวนมากมาก ๆ (e-commerce catalog): pre-generate ภาพระหว่าง build ผ่าน Turbopack production build หรือใช้ Image CDN แยกต่างหาก แล้วปิด optimizer default ด้วย unoptimized: true

ส่วน quality ค่า default คือ 75 ซึ่งเป็น sweet spot ที่ Google และ Mozilla แนะนำ ผมใช้ quality={90} เฉพาะภาพ hero, portrait หรือ product page และใช้ quality={50} สำหรับ thumbnail ในภาพ grid

// ใช้ต่างกันตามบริบท
<Image src={hero} alt="..." quality={90} priority ... />         {/* hero */}
<Image src={product} alt="..." quality={75} ... />               {/* default */}
<Image src={thumb} alt="..." quality={50} width={64} height={64} /> {/* thumb */}

Remote Images: ตั้งค่า remotePatterns อย่างปลอดภัย

ถ้า src เป็น URL ภายนอก Next.js ต้องรู้ว่าโดเมนไหน "อนุญาต" ให้ optimize ก่อน มิฉะนั้นจะ throw ที่ runtime, นี่เป็นมาตรการกัน SSRF (Server-Side Request Forgery) ไม่ให้คนสร้าง URL แล้วบังคับให้ server คุณดึงภาพจากไหนก็ได้ ตั้งใน next.config.ts:

// next.config.ts
const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.mycms.com',
        pathname: '/media/**',
      },
      {
        protocol: 'https',
        hostname: '*.supabase.co',
        pathname: '/storage/v1/object/public/**',
      },
    ],
  },
};

ในเวอร์ชัน 16 เพิ่ม option localPatterns สำหรับควบคุม path ภาพใน public/ ที่อนุญาตให้ผ่าน optimizer ด้วย ประโยชน์คือกันคนสร้าง URL แบบ /_next/image?url=/secret-file.pdf&w=64&q=75 เพื่อ probe ไฟล์ที่ไม่ได้ตั้งใจให้ optimize

sizes, priority, placeholder: 3 props ที่ทุกภาพต้องมี

ผมพูดตรง ๆ ว่าถ้าคุณเข้าใจ 3 props นี้ครบ ประสิทธิภาพภาพในเว็บของคุณจะดีขึ้น 80% ทันที

sizes: บอก browser ว่าภาพใหญ่แค่ไหนตาม viewport

sizes คือ CSS media query string ที่บอก browser ว่าภาพจะ render กว้างเท่าไรที่แต่ละขนาดจอ browser จะเลือก URL จาก srcset ที่ใกล้เคียงที่สุด ตัวอย่าง:

sizes="(min-width: 1280px) 800px, (min-width: 768px) 50vw, 100vw"

แปลว่า: จอกว้าง ≥1280px ภาพกว้าง 800px, จอกว้าง ≥768px ภาพกว้าง 50% ของ viewport, ต่ำกว่านั้นเต็มจอ ถ้าไม่ใส่ Next.js default เป็น 100vw ซึ่งจะโหลดภาพขนาดใหญ่สุดใน deviceSizes เสมอ — เผลอ ๆ 1.5 MB ทั้งที่ภาพจริงกว้าง 400px

priority: บอก browser ให้ preload

ใช้กับ LCP element เท่านั้น (มักเป็นภาพ hero, product image, article featured image) การใส่ priority กับภาพที่อยู่ below the fold จะทำให้ browser ดึงภาพนอกจอก่อนภาพในจอ LCP กลับแย่ลง

placeholder: กัน blank space ตอนโหลด

Next.js รองรับ placeholder="blur" ที่ใช้ blurDataURL เป็น base64 ภาพเบลอเล็ก ๆ 8-16 px แสดงระหว่างรอภาพจริง ถ้า import static Next.js สร้างให้เอง ถ้าเป็น remote image ต้อง generate เอง ผมชอบใช้ plaiceholder ตอน build:

// lib/blur.ts
import { getPlaiceholder } from 'plaiceholder';

export async function blurDataFor(url: string) {
  const res = await fetch(url);
  const buffer = Buffer.from(await res.arrayBuffer());
  const { base64 } = await getPlaiceholder(buffer, { size: 10 });
  return base64;
}

Custom Loader และการใช้ร่วมกับ Image CDN

ถ้าคุณ deploy Next.js บน Vercel image optimizer default ทำงานได้ดีมาก แต่ถ้าโฮสต์เองบน Docker/Kubernetes หรืออยากใช้ Image CDN ระดับพันล้าน request/เดือน (Cloudinary, Imgix, Cloudflare Images) คุณจะอยาก override loader

// lib/cloudinary-loader.ts
'use client';
import type { ImageLoaderProps } from 'next/image';

export default function cloudinaryLoader({
  src,
  width,
  quality,
}: ImageLoaderProps) {
  const params = [
    'f_auto',              // format auto (AVIF/WebP)
    'c_limit',             // crop = limit ไม่ upscale
    `w_${width}`,
    `q_${quality || 'auto'}`,
  ];
  return `https://res.cloudinary.com/mycloud/image/upload/${params.join(
    ','
  )}/${src}`;
}

ใช้ในคอมโพเนนต์:

import Image from 'next/image';
import cloudinaryLoader from '@/lib/cloudinary-loader';

<Image
  loader={cloudinaryLoader}
  src="products/laptop-2026.jpg"
  alt="Laptop 2026"
  width={800}
  height={600}
  sizes="(min-width: 1024px) 800px, 100vw"
/>

หรือถ้าจะใช้ทั้งโปรเจกต์ ระบุ loaderFile ใน next.config.ts: images: {{ loader: 'custom', loaderFile: './lib/cloudinary-loader.ts' }} — ครั้งเดียวจบ ทุก <Image> ใช้ loader นี้อัตโนมัติ

ข้อดีของ Image CDN คือ transformation cache กระจายทั่วโลก มี edge point ใกล้ผู้ใช้ และไม่กิน compute ของ Next.js server ข้อเสียคือมีค่าใช้จ่าย (Cloudinary ฟรี 25K transformation/เดือน) และต้องระวังไม่ให้ URL เปลี่ยนบ่อย เพราะจะ invalidate cache ทั้งชุด

Debug ประสิทธิภาพภาพด้วย Chrome DevTools

ผมใช้ 4 tab ประจำใน DevTools เมื่อดีบักภาพ:

  1. Network → filter Img: ดู size, format, และเวลาโหลดจริง สิ่งที่ควรเห็นคือ format image/avif หรือ image/webp ถ้ายังเป็น JPEG แปลว่า optimizer ไม่ทำงาน (อาจเพราะ unoptimized เผลอเปิด หรือใช้ <img> ธรรมดา)
  2. Performance → Record: เปิด Web Vitals overlay ดู marker LCP ตกที่ element ไหน
  3. Lighthouse: รันแบบ Mobile + Fast 3G ให้เห็นค่าที่ผู้ใช้จริงเจอ อย่าเชื่อ Desktop score
  4. Coverage: ดูว่า CSS ที่ block render จริง ๆ ใช้แค่ไหน CSS ยักษ์ๆ ที่ inline อยู่บน head จะ push LCP

Trick ที่ผมชอบ: เปิด Network throttling เป็น "Slow 4G" แล้ว throttle CPU 4x ถ้า LCP ยังต่ำกว่า 2.5s เว็บของคุณจะรอดในโลกจริง 95% แต่ถ้ายัง 4-5s ให้ย้อนกลับไปเช็ค 3 อย่าง: (1) hero image มี priority หรือยัง (2) sizes เขียนถูกไหม (3) มี render-blocking script ก่อน hero หรือเปล่า

คู่มือเพิ่มเติมสำหรับ progressive rendering ที่จะช่วย LCP มากยิ่งขึ้น ดูที่ Streaming และ Suspense ใน Next.js 16 ซึ่งสอนวิธี stream shell ที่มีภาพ hero ออกก่อน แล้วค่อย stream ส่วนที่ต้องรอ data ตามมาทีหลัง

อ้างอิงเอกสารทางการเพิ่มเติมจาก Next.js Image API reference ซึ่งอัปเดตทุก minor release และมีตารางเทียบ props ครบทุกตัว

คำถามที่พบบ่อย

next/image ทำงานได้ไหมโดยไม่ต้องมี server?

ได้ ตั้ง output: 'export' ใน next.config.ts Next.js จะ export ภาพ static ทั้งหมด แต่ image optimizer จะปิดอัตโนมัติ คุณจะต้อง pre-generate ภาพเอง (หรือใช้ Image CDN) เพราะไม่มี runtime มา optimize on-demand

ทำไม LCP ยังสูงถึงแม้จะใส่ priority?

สาเหตุที่พบบ่อยคือ (1) priority ถูกใส่ผิด element ไม่ตรงกับ LCP element จริง (2) มี render-blocking CSS/JS ที่โหลดก่อน HTML head แสดง preload tag (3) ภาพใหญ่เกินจริงเพราะ sizes เขียนผิดหรือไม่ได้ใส่ ตรวจใน Chrome DevTools → Performance → Web Vitals ว่า LCP element ตรงกับภาพที่คุณตั้งใจไหม

ควรตั้ง quality เท่าไรถึงจะดีที่สุด?

ค่า default 75 คือ sweet spot ที่ Google และ Mozilla แนะนำสำหรับภาพส่วนใหญ่ ใช้ 85–90 กับภาพ hero, portrait หรือ product page และใช้ 50–60 กับ thumbnail หรือ avatar เล็ก ๆ ที่ไม่ต้องการรายละเอียดสูง อย่าใช้ 100 เพราะขนาดไฟล์โตขึ้นมากโดยผู้ใช้แทบไม่เห็นความต่าง

ต่างระหว่าง next/image กับ <img> ธรรมดาคืออะไร?

next/image เพิ่ม 4 อย่าง: (1) แปลง format เป็น AVIF/WebP อัตโนมัติ (2) สร้าง srcset ตามหลาย viewport (3) lazy-load ผ่าน Intersection Observer (4) ใส่ aspect ratio เพื่อกัน CLS ในทางกลับกัน <img> เร็วกว่าเล็กน้อยตอน render initial แต่ประสิทธิภาพจริงบน production ต่ำกว่ามากเพราะไม่มี optimization เหล่านี้

Next.js 16 บังคับใช้ Sharp ไหม แล้ว squoosh ยังใช้ได้ไหม?

Next.js 16 ตัด squoosh ออกจาก default image processor เหลือ Sharp เท่านั้น ต้องติดตั้ง Node.js 20+ และ Sharp มาให้พร้อมกับ next package ถ้า deploy บน serverless ที่ไม่รองรับ native binaries (บาง edge runtime) ต้องใช้ Image CDN ภายนอกแทน หรือ pre-generate ภาพระหว่าง build

Oliver Schmidt
เกี่ยวกับผู้เขียน Oliver Schmidt

React performance engineer. Lives in DevTools. Will explain to anyone listening why Suspense changes everything.