kodingup.camp

Beranda/Next.js Mastery: Dasar, Routing & Rendering

Gambar, Font & Aset

Modul 9 dari 105 menit bacaImages, Fonts & Assets

Isi modul

    Kenapa ini penting

    Gambar biasanya bagian terberat dari sebuah halaman. Satu foto produk 4 MB dari kamera HP bisa membuat halaman terasa lambat di koneksi 4G, dan tata letak yang "loncat" saat gambar selesai dimuat membuat user salah klik. Font dari Google Fonts juga bisa membuat teks berkedip atau tidak terbaca beberapa detik pertama. Next.js punya komponen next/image dan modul next/font yang mengurus masalah ini otomatis, plus konvensi file untuk ikon dan gambar share sosial media. Hasilnya langsung terasa di skor Core Web Vitals (ukuran kecepatan halaman dari Google) dan SEO.

    Konsep inti

    Folder public

    File di folder public/ disajikan apa adanya dari akar URL. public/logo.png bisa diakses di /logo.png. Cocok untuk logo, file robots.txt statis, PDF, atau gambar yang dipakai via URL. Jangan taruh file rahasia di sini, karena semua orang bisa mengunduhnya. Nama file jangan sama dengan nama rute di app/.

    next/image: <img> yang pintar

    Komponen Image dari next/image memperluas <img> dengan:

    Analoginya seperti tukang jahit: alih-alih memberi semua orang baju ukuran XXL, ia menjahit sesuai ukuran badan masing-masing.

    Props penting:

    Prop Fungsi
    src Path lokal (/foto.jpg), hasil import, atau URL remote
    alt Teks alternatif, wajib untuk aksesibilitas dan SEO
    width, height Ukuran asli/rasio gambar (wajib untuk gambar remote, kecuali pakai fill)
    fill Gambar memenuhi elemen induk; induk harus position: relative (relative di Tailwind)
    sizes Petunjuk lebar gambar di layar, misalnya "(max-width: 768px) 100vw, 800px"
    placeholder="blur" Tampilkan versi buram dulu (otomatis untuk gambar yang di-import)
    preload Muat sangat awal lewat <link> di <head>, untuk gambar hero/LCP
    quality Kualitas kompresi; nilainya harus ada di images.qualities

    Perubahan di Next.js 16 yang perlu kamu tahu:

    Gambar lokal vs remote

    next/font: font tanpa kedipan

    next/font mengunduh font saat build dan menyajikannya dari domainmu sendiri. Browser tidak perlu menghubungi Google, lebih cepat, lebih menjaga privasi, dan tanpa layout shift karena ukuran fallback disesuaikan otomatis.

    File metadata khusus di app/

    Beberapa nama file di app/ otomatis diubah menjadi tag <head>:

    File Hasil
    app/favicon.ico Ikon tab browser
    app/icon.png / icon.svg Ikon aplikasi
    app/apple-icon.png Ikon saat disimpan ke layar utama iPhone
    app/opengraph-image.jpg Gambar saat link dibagikan (WhatsApp, Facebook, LinkedIn)
    app/twitter-image.jpg Gambar khusus X/Twitter
    opengraph-image.jsx Gambar OG dibuat dengan kode memakai ImageResponse dari next/og

    File di subfolder lebih spesifik dan mengalahkan yang di atasnya: app/blog/opengraph-image.jpg khusus untuk /blog. ImageResponse memakai JSX dengan subset CSS (flexbox, bukan grid).

    Contoh kode

    Daftarkan domain gambar remote:

    // next.config.mjs
    const nextConfig = {
      cacheComponents: true,
      partialPrefetching: true,
      images: {
        remotePatterns: [
          { protocol: 'https', hostname: 'images.unsplash.com' },
        ],
        qualities: [75, 90], // izinkan quality={90}
      },
    }
    
    export default nextConfig
    

    Tiga cara memakai Image:

    // app/galeri/page.jsx
    import Image from 'next/image'
    import logo from '../../public/next.svg' // import statis → ukuran diketahui otomatis
    
    export default function GaleriPage() {
      return (
        <main className="space-y-6 p-6">
          {/* 1. Import statis: tanpa width/height */}
          <Image src={logo} alt="Logo Next.js" />
    
          {/* 2. Remote: wajib width/height + domain di remotePatterns */}
          <Image
            src="https://images.unsplash.com/photo-1509042239860-f550ce710b93"
            alt="Secangkir kopi di meja kayu"
            width={800}
            height={533}
            preload                                  // gambar hero (pengganti 'priority')
            sizes="(max-width: 768px) 100vw, 800px"
          />
    
          {/* 3. fill: mengikuti ukuran induk yang relative */}
          <div className="relative h-64 w-full">
            <Image src="/vercel.svg" alt="Logo Vercel" fill className="object-contain" />
          </div>
        </main>
      )
    }
    // → di HTML: <img srcset="/_next/image?url=...&w=640&q=75 640w, ..."> dengan format WebP
    

    Font Google dan font lokal sekaligus, dihubungkan ke Tailwind:

    // app/layout.jsx
    import { Inter } from 'next/font/google'
    import localFont from 'next/font/local'
    import './globals.css'
    
    const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })
    
    const judul = localFont({
      src: './fonts/Judul-Bold.woff2', // path relatif terhadap file ini
      variable: '--font-judul',
    })
    
    export default function RootLayout({ children }) {
      return (
        <html lang="id" className={`${inter.variable} ${judul.variable}`}>
          <body className="font-sans">{children}</body>
        </html>
      )
    }
    
    /* app/globals.css */
    @import "tailwindcss";
    
    @theme inline {
      --font-sans: var(--font-inter);
      --font-judul: var(--font-judul);
    }
    /* → kelas font-sans memakai Inter, kelas font-judul memakai font lokal */
    

    Gambar Open Graph yang dibuat dengan kode untuk setiap artikel:

    // app/blog/[slug]/opengraph-image.jsx
    import { ImageResponse } from 'next/og'
    import { getPost, getPosts } from '@/lib/posts'
    
    export const size = { width: 1200, height: 630 }
    export const contentType = 'image/png'
    export const alt = 'Gambar artikel Kopiku'
    
    export async function generateStaticParams() {
      const posts = await getPosts()
      return posts.map((post) => ({ slug: post.slug }))
    }
    
    export default async function Image({ params }) {
      const { slug } = await params
      const post = await getPost(slug)
      return new ImageResponse(
        (
          <div
            style={{
              width: '100%', height: '100%', display: 'flex', alignItems: 'center',
              justifyContent: 'center', background: '#3b2414', color: 'white',
              fontSize: 72, padding: 60,
            }}
          >
            {post?.judul ?? 'Kopiku'}
          </div>
        ),
        size
      )
    }
    // → /blog/halo-nextjs/opengraph-image → PNG 1200×630 bertuliskan "Halo, Next.js!"
    // → tag <meta property="og:image"> ditambahkan otomatis ke halaman artikel
    

    Kesalahan umum

    1. Gambar remote tanpa remotePatterns. Muncul error bahwa hostname belum dikonfigurasi. Tambahkan domain di next.config (sespesifik mungkin, bahkan dengan pathname).
    2. fill tanpa induk relative dan tinggi. Gambar tidak tampil atau memenuhi seluruh halaman. Beri induk relative dan ukuran (misalnya h-64).
    3. Masih memakai priority. Deprecated di Next.js 16. Ganti dengan preload, atau loading="eager"/fetchPriority="high".
    4. quality={90} tapi hasilnya tetap 75. Default images.qualities hanya [75]. Tambahkan nilai yang dibutuhkan ke config.
    5. Memanggil fungsi font di dalam komponen. Inter({...}) harus di level modul agar font hanya dimuat sekali. Pakai <link> Google Fonts manual juga tidak perlu lagi.

    Latihan

    1. Mudah, Taruh kopi.jpg di public/, lalu tampilkan dengan <Image> berukuran 600×400 dan alt yang deskriptif.
    2. Sedang, Buat kartu produk dengan gambar fill di kotak rasio 4:3 (relative aspect-[4/3]) dan sizes yang benar untuk grid 3 kolom di desktop dan 1 kolom di HP.
    3. Menantang, Buat app/opengraph-image.jsx untuk seluruh situs berisi nama toko dan slogan di dua baris, latar cokelat, ukuran 1200×630.
    Lihat solusi

    1.

    // app/page.jsx
    import Image from 'next/image'
    
    export default function HomePage() {
      return (
        <Image
          src="/kopi.jpg"
          alt="Biji kopi arabika Gayo di atas karung goni"
          width={600}
          height={400}
        />
      )
    }
    

    2.

    // components/KartuProduk.jsx
    import Image from 'next/image'
    
    export default function KartuProduk({ nama, gambar }) {
      return (
        <div className="overflow-hidden rounded-lg border">
          <div className="relative aspect-[4/3]">
            <Image
              src={gambar}
              alt={nama}
              fill
              className="object-cover"
              sizes="(max-width: 768px) 100vw, 33vw" // HP: selebar layar; desktop: sepertiga
            />
          </div>
          <p className="p-3 font-semibold">{nama}</p>
        </div>
      )
    }
    

    3.

    // app/opengraph-image.jsx
    import { ImageResponse } from 'next/og'
    
    export const size = { width: 1200, height: 630 }
    export const contentType = 'image/png'
    export const alt = 'Kopiku, kopi nusantara'
    
    export default function Image() {
      return new ImageResponse(
        (
          <div
            style={{
              width: '100%', height: '100%', display: 'flex', flexDirection: 'column',
              alignItems: 'center', justifyContent: 'center',
              background: '#4a2c17', color: '#fde68a',
            }}
          >
            <div style={{ fontSize: 96, fontWeight: 700 }}>Kopiku</div>
            <div style={{ fontSize: 40, marginTop: 20 }}>Kopi nusantara, diseduh sepenuh hati</div>
          </div>
        ),
        size
      )
    }
    

    Ringkasan

    Video pendamping

    Kalau penjelasan di atas belum masuk, tonton ini. Video dari kanal YouTube pihak lain.

    6. Optimization Images & Fonts | Tutorial Nextjs untuk Pemula IndonesiaRobertus Adrian · Bahasa IndonesiaBuka di YouTube

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.