Skip to main content
BlogWeb Dev

SEO-First Next.js: Checklist Lengkap untuk 2026

Checklist praktis yang mencakup Metadata API Next.js, Core Web Vitals, data terstruktur JSON-LD, sitemap, dan SEO i18n dalam satu tempat.

Matthews Wong

Software Engineer

Juni 20259 menit baca
SEO-First Next.js: Checklist Lengkap untuk 2026

Foto oleh Unsplash

Saya sudah mengaudit banyak situs Next.js dengan Lighthouse dan kesalahan SEO yang sama muncul di 80% dari mereka: canonical URL yang hilang, tidak ada OG image, sitemap yang menunjuk ke localhost, dan gambar LCP yang tidak di-preload. Checklist ini adalah semua yang saya periksa sebelum menganggap situs App Router siap SEO. Ini bukan teoritis — ini yang saya terapkan pada situs portfolio saya sendiri untuk mendapatkan skor Lighthouse 90+ yang konsisten.

Lima Layer Sinyal SEO di Next.js

Pikirkan SEO Next.js sebagai lima layer yang ditumpuk: (1) Metadata API — judul, deskripsi, canonical, dan tag sosial dasar. (2) Structured Data (JSON-LD) — konten yang dapat dibaca mesin untuk rich result. (3) Core Web Vitals — sinyal performa yang digunakan Google sebagai faktor peringkat. (4) Sitemap dan robots.txt — panduan crawl. (5) Optimasi gambar — baik untuk performa maupun berbagi sosial.

Metadata API vs Statis vs Dinamis

Next.js App Router memiliki tiga cara untuk mengatur metadata. Yang paling sederhana adalah mengekspor objek metadata dari halaman — bagus untuk halaman statis seperti About atau Contact. Untuk halaman dinamis (posting blog, halaman produk), gunakan generateMetadata() yang menerima parameter route dan dapat mengambil data. Untuk root layout, atur metadata default yang diwarisi semua halaman anak.

Canonical URL dan hreflang untuk i18n

Jika situsmu multibahasa (milik saya adalah Inggris dan Indonesia), kamu memerlukan canonical dan alternates.languages di metadata setiap halaman. Canonical menunjuk ke URL locale saat ini. Objek alternates.languages memetakan kode locale ke URL lengkap. Ini memberi tahu Google versi bahasa mana yang ditunjukkan kepada pengguna mana, mencegah penalti konten duplikat.

Next.js SEO Checklist — Signal Flow

┌─────────────────────────────────────────────┐
│  1. Metadata API (app/layout.tsx)           │
│     title · description · openGraph        │
│     twitter · robots · canonical           │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│  2. Structured Data (JSON-LD)               │
│     Article · BreadcrumbList               │
│     Person · WebSite · FAQPage             │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│  3. Core Web Vitals                         │
│     LCP < 2.5s · FID < 100ms · CLS < 0.1  │
│     TTFB < 600ms · INP < 200ms             │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│  4. Sitemap + robots.txt                    │
│     /sitemap.xml (dynamic) · /robots.txt   │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│  5. Image Optimization                      │
│     next/image · WebP/AVIF · alt text      │
│     priority on LCP image                  │
└─────────────────────────────────────────────┘

Gunakan generateStaticParams() untuk semua route posting blog untuk memaksa static generation. Halaman yang dihasilkan secara statis memiliki TTFB hampir nol dari edge cache Vercel, yang merupakan keuntungan terbesar untuk Core Web Vitals dan SEO. Halaman posting blog yang dirender secara dinamis pada 800ms TTFB versus yang statis pada 40ms adalah perbedaan yang bermakna dalam LCP.

Structured Data dengan JSON-LD

JSON-LD structured data mengubah kontenmu menjadi sinyal yang dapat dibaca mesin yang digunakan Google untuk rich result — byline artikel, dropdown FAQ, dan breadcrumb yang kamu lihat di hasil pencarian. Untuk blog, implementasikan skema Article di setiap posting dan BreadcrumbList di halaman listing. Render JSON-LD sebagai tag script di komponen atau layout halaman-mu.

Pembuatan Sitemap Dinamis

File app/sitemap.ts di Next.js App Router mengembalikan array MetadataRoute.Sitemap yang disajikan Next.js sebagai /sitemap.xml. Untuk situs bilingual, sertakan kedua varian locale dari setiap URL. Atur lastModified dari tanggal updatedAt konten aktual, bukan nilai yang dikodekan keras. Atur changeFrequency dan priority dengan tepat.

// app/[locale]/blog/[slug]/page.tsx
import type { Metadata } from "next"
import { getTranslations } from "next-intl/server"

type Props = { params: { slug: string; locale: string } }

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug, locale } = params
  const post = await getBlogPost(slug)

  return {
    title: post.title,
    description: post.excerpt,
    alternates: {
      canonical: `https://www.matthewswong.com/${locale}/blog/${slug}`,
      languages: {
        "en": `https://www.matthewswong.com/en/blog/${slug}`,
        "id": `https://www.matthewswong.com/id/blog/${slug}`,
      },
    },
    openGraph: {
      title: post.title,
      description: post.excerpt,
      url: `https://www.matthewswong.com/${locale}/blog/${slug}`,
      siteName: "Matthews Wong",
      images: [{ url: post.image, width: 1200, height: 630 }],
      type: "article",
      publishedTime: post.datePublished,
      authors: ["Matthews Wong"],
    },
    twitter: {
      card: "summary_large_image",
      title: post.title,
      description: post.excerpt,
      images: [post.image],
    },
  }
}

// JSON-LD structured data component
export function ArticleJsonLd({ post }: { post: BlogPost }) {
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "Article",
    headline: post.title,
    description: post.excerpt,
    image: post.image,
    author: {
      "@type": "Person",
      name: "Matthews Wong",
      url: "https://www.matthewswong.com",
    },
    publisher: {
      "@type": "Person",
      name: "Matthews Wong",
    },
    datePublished: post.datePublished,
    dateModified: post.dateModified,
    mainEntityOfPage: {
      "@type": "WebPage",
      "@id": `https://www.matthewswong.com/blog/${post.slug}`,
    },
  }

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
    />
  )
}

// app/sitemap.ts — dynamic sitemap
import { MetadataRoute } from "next"
import { blogPosts } from "@/app/lib/blogData"

export default function sitemap(): MetadataRoute.Sitemap {
  const blogRoutes = blogPosts.flatMap((post) =>
    ["en", "id"].map((locale) => ({
      url: `https://www.matthewswong.com/${locale}/blog/${post.slug}`,
      lastModified: new Date(post.datePublished),
      changeFrequency: "monthly" as const,
      priority: 0.7,
    }))
  )

  return [
    { url: "https://www.matthewswong.com", priority: 1.0 },
    { url: "https://www.matthewswong.com/en/blog", priority: 0.8 },
    ...blogRoutes,
  ]
}

Core Web Vitals: Koneksi Performa-SEO

LCP (Largest Contentful Paint) hampir selalu adalah gambar hero-mu. Tambahkan priority ke komponen next/image untuk gambar di atas lipatan di setiap halaman — ini memicu tag preload link. Untuk CLS (Cumulative Layout Shift), selalu tentukan width dan height pada gambar dan gunakan CSS aspect-ratio untuk elemen berukuran dinamis. Untuk INP, hindari task JavaScript yang lama di main thread.

Mengatur document.title atau menyuntikkan meta tag dari useEffect tidak terlihat oleh crawler. Metadata API Next.js menghasilkan tag ini di sisi server dalam HTML head, yang dibaca oleh bot mesin pencari. Jika kamu melihat judul yang hilang di Search Console untuk halaman yang terlihat benar di browser, periksa apakah metadata diatur melalui Next.js API atau JavaScript sisi klien.

SEO Gambar: Teks alt, Format, dan OG Image

Setiap gambar memerlukan atribut alt yang deskriptif — bukan 'gambar' atau nama file. Gunakan komponen next/image untuk semua gambar konten untuk mendapatkan penyajian WebP/AVIF otomatis dan lazy loading. Untuk OG image (gambar pratinjau saat berbagi di LinkedIn atau X), gunakan library next/og Next.js untuk membuatnya secara dinamis. OG image harus berukuran 1200x630px.

Memverifikasi Implementasi SEO-mu

Jalankan Lighthouse dalam mode Incognito (untuk menghindari ekstensi yang mempengaruhi hasil). Gunakan Rich Results Test Google untuk memverifikasi JSON-LD. Kirimkan sitemap di Search Console dan tunggu laporan cakupan. Gunakan alat URL Inspection di Search Console untuk melihat halaman seperti yang dilihat Googlebot — ini mengungkap masalah rendering JS.

Sumber & Bacaan Lanjutan

Artikel Terkait

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Apa saja lima layer sinyal SEO di Next.js yang wajib diterapkan pada setiap situs App Router?

Lima layer tersebut adalah: Metadata API (judul, deskripsi, canonical, tag sosial), Structured Data via JSON-LD (untuk rich result), Core Web Vitals (LCP, CLS, INP sebagai faktor peringkat Google), Sitemap dan robots.txt (panduan crawl), serta optimasi gambar (performa dan berbagi sosial). Melewatkan salah satu layer berarti meninggalkan poin peringkat yang seharusnya bisa didapat.

Kapan sebaiknya menggunakan generateMetadata() dibanding objek metadata statis di Next.js?

Gunakan ekspor metadata statis untuk halaman dengan konten tetap seperti About atau Contact. Untuk route dinamis seperti posting blog atau halaman produk, gunakan generateMetadata() yang menerima parameter route dan dapat mengambil data untuk membangun judul, deskripsi, dan tag OG yang spesifik per halaman. Hierarkinya adalah layout → page, dengan page selalu menang jika terjadi konflik.

Mengapa static generation dengan generateStaticParams() penting untuk performa SEO?

Halaman yang dihasilkan secara statis dan disajikan dari edge cache Vercel memiliki TTFB hampir nol — sekitar 40ms dibanding 800ms untuk halaman yang dirender secara dinamis. Perbedaan ini berdampak langsung pada LCP, yang merupakan salah satu sinyal ranking Core Web Vitals Google. Memaksa static generation untuk semua route posting blog disebut sebagai keuntungan terbesar untuk Core Web Vitals dan SEO.

Bagaimana canonical URL dan hreflang bekerja bersama untuk situs Next.js dua bahasa?

Untuk situs multibahasa (Inggris dan Indonesia), metadata setiap halaman memerlukan canonical yang menunjuk ke URL locale saat ini dan objek alternates.languages yang memetakan kode locale ke URL lengkap. Ini memberi tahu Google versi bahasa mana yang ditampilkan kepada pengguna mana, mencegah penalti konten duplikat, dan menghindari versi /en/ dan /id/ bersaing satu sama lain di peringkat pencarian.

Apa cara yang benar untuk menambahkan structured data (JSON-LD) di Next.js, dan mengapa itu penting?

Render JSON-LD sebagai tag script di dalam komponen atau layout halaman — Next.js menghasilkan ini di sisi server sehingga crawler dapat membacanya tanpa mengeksekusi JavaScript. Untuk blog, implementasikan skema Article di setiap posting dan BreadcrumbList di halaman listing. Ini mengaktifkan rich result seperti byline artikel, dropdown FAQ, dan breadcrumb di hasil pencarian Google.