Panduan Next.js SEO

cara membuat sebuah Next.js situs dapat di-crawl, dapat diindeks, dan rankable — two routers, rendering modes (SSG/SSR/ISR/server Components), App Router Metadata API, sitemap.ts, robots.ts, next/image, next/tautan, dan mistakes itu quietly break ini.

Pertama kali diterbitkan: 26 Jun 2026 · Terakhir diperbarui: 3 Agu 2026 · Advanced
Bahasa

Next.js solves hardest JavaScript SEO masalah oleh default — jika Anda gunakan ini right. App Router's server Components dan SSG/ISR put konten di HTML so there's no render-queue delay; native Metadata API resolves judul, canonicals, dan Open Graph pada server; sitemap.ts dan robots.ts adalah file conventions. framework gives Anda infrastructure tetapi writes none dari Anda tags untuk Anda. failures adalah predictable: missing metadataBase, no priority pada LCP image, metadata exported dari sebuah Client Component (silently melakukan nothing), dan 404 views returning 200.

TL;DR — Next.js solves hardest JavaScript SEO masalah oleh default when Anda gunakan ini right. App Router defaults untuk server Components dan mendukung SSG/SSR/ISR — semua dari which ship rendered HTML, so there’s no render-queue delay. Set metadata dengan native Metadata API (metadata / generateMetadata), dan remember metadataBase atau Anda canonicals dan OG images go relative. gunakan app/sitemap.ts dan app/robots.ts, pre-bangun dynamic routes dengan generateStaticParams, set priority pada LCP image, dan pertahankan tautan sebagai nyata next/link anchors. halaman Router adalah juga SEO-capable via next/head. failures adalah predictable — dan sebagian besar dari them aren’t Next.js’s fault, mereka’re yours.

Where Next.js fits

Next.js adalah sebuah React framework, so everything di JavaScript SEO applies. What membuat ini worth -nya own guide adalah itu Next.js ships pertama-class jawaban untuk sebagian besar JS-SEO masalah: server rendering, static generation, sebuah metadata sistem, dan sitemap/robots conventions. hard bagian isn’t whether Google dapat read ini — Google memiliki rendered JavaScript untuk years — ini adalah choosing right rendering mode dan not leaving SEO basics unwired. ini adalah sebuah specialized case dari CMS headless SEO: CMS barely penting, frontend’s rendering decisions decide everything.

One framing untuk pertahankan straight: “this is a Next.js site” (terjemahan) “ini adalah sebuah Next.js situs” doesn’t tell Anda how apa pun single URL adalah delivered. rendering mode, caching, dan server/Client Component boundaries adalah set per route (sometimes per segment) — sebuah project dapat mix sebuah static marketing halaman, sebuah SSR product halaman, dan sebuah Client Component dashboard. Don’t extrapolate one route’s perilaku untuk “the whole app” (terjemahan) “ whole app”; test spesifik URL.

Two routers, two sets dari mechanics

Next.js memiliki two routers, dan mereka handle SEO differently:

  • halaman Router ( older model) — data fetching via getStaticProps / getServerSideProps; metadata via <Head> dari next/head (atau next-seo package); no server Components.
  • App Router (v13+, saat ini dan recommended approach) — React server Components oleh default; native Metadata API (metadata export / generateMetadata); file conventions untuk app/sitemap.ts dan app/robots.ts; generateStaticParams untuk dynamic routes. Evidence for this claim The App Router uses Server Components and supports generateStaticParams plus metadata file conventions. Scope: Current Next.js App Router behavior; route rendering can become dynamic based on APIs used. Confidence: high · Verified: Next.js: Server and Client Components Next.js: generateStaticParams

Both dapat peringkat well. App Router gives Anda sebuah cleaner, integrated metadata sistem (no next/head juggling) dan server Components out dari box, which adalah why I’d reach untuk ini pada sebuah baru bangun. tetapi “App Router or you can’t do SEO” (terjemahan) “App Router atau Anda dapat’t melakukan SEO” adalah sebuah myth — plenty dari halaman Router situs peringkat fine.

rendering modes dan what setiap berarti untuk SEO

How Google handles JavaScript adalah sebuah three-phase pipeline — crawl, lalu sebuah deferred render wave, lalu indeks. “All pages with a 200 HTTP status code are sent to the rendering queue.” (terjemahan) “semua halaman dengan sebuah 200 HTTP kode status adalah dikirim untuk rendering queue.” whole game di Next.js adalah choosing sebuah mode itu puts Anda konten di HTML sebelum itu render wave, so there’s nothing untuk wait untuk. Evidence for this claim Google crawls, renders, and indexes JavaScript pages, and successful pages can enter the rendering queue. Scope: Google Search processing, not a promise that a URL will be indexed. Confidence: high · Verified: Google: JavaScript SEO basics

  • Static situs Generation (SSG) — halaman pre-rendered di bangun time. HTML adalah immediately available dengan no render-queue risk. Best untuk konten itu doesn’t perubahan setiap minute. generateStaticParams() (App Router) / getStaticPaths() (halaman Router) decides which dynamic routes get pre-dibangun.
  • Incremental Static Regeneration (ISR) — static halaman itu revalidate setelah sebuah set interval (export const revalidate = 3600). crawler get static HTML dengan rendah TTFB dan konten stays fresh. sebuah strong default — dengan one trap: setelah window expires next permintaan (possibly Googlebot) masih gets stale halaman; fresh versi menyajikan pada permintaan setelah itu. untuk genuinely volatile data (prices, stock), SSR adalah safer.
  • rendering sisi server (SSR) — HTML rendered per permintaan. crawler get fully rendered HTML immediately; tradeoff adalah server latency, so watch TTFB dan LCP. export const dynamic = 'force-dynamic' atau menggunakan permintaan-time APIs (cookies, headers) opts sebuah route ke SSR.
  • React server Components (App Router default) — render pada server dan kirim HTML; no JavaScript ships untuk component itself. konten adalah di initial respons dengan no hydration gap. ini adalah best default untuk SEO. Interactivity lives di Client Components marked 'use client'.
  • rendering sisi klien (CSR) — rendered entirely di browser. Googlebot dapat indeks ini setelah render wave (median ~10 seconds, tetapi 90th percentile stretches untuk hours), dan lainnya crawler — Bingbot, AI bot, social preview bot — dapat get sebuah empty halaman. di App Router, CSR adalah opt-di ('use client'); di halaman Router, hindari fetching primary konten di useEffect. Don’t gunakan ini untuk konten Anda ingin diperingkatkan.

sebuah reminder I pertahankan coming back untuk: “Googlebot can render it” (terjemahan) “Googlebot dapat render ini” adalah not yang sama sebagai “you should make Googlebot render it.” (terjemahan) “Anda harus membuat Googlebot render ini.” rendering adalah expensive, deferred, dan not universal di seluruh crawler.

Metadata API (App Router)

Metadata API adalah server Component hanya — metadata resolves pada server sebelum halaman renders, so ini lands di initial HTML. Export metadata dari layout.js atau page.js:

export const metadata: Metadata = {
  title: 'My Page',
  description: 'Page description',
}

atau, when tags depend pada fetched data, gunakan generateMetadata():

export async function generateMetadata({ params }) {
  const post = await getPost(params.slug)
  return { title: post.title, description: post.description }
}

fields itu penting untuk SEO:

  • title — mendukung sebuah string, sebuah template ('%s | Brand'), sebuah default, dan sebuah absolute override. atur template once di root layout dan per-halaman judul inherit ini.
  • description, alternates.canonical ( correct cara untuk set sebuah canonical di App Router), openGraph (images harus resolve untuk absolute URLs), twitter (juga digunakan oleh LinkedIn dan Slack previews), dan robots (indeks/ikuti plus googleBot-spesifik directives like max-snippet, max-image-preview).
  • metadataBasediperlukan untuk canonical dan OG image URLs untuk resolve correctly. Forgetting ini adalah single sebagian besar umum Next.js metadata bug: relative URLs leak ke Anda canonical dan Open Graph tags, breaking social previews dan muddying canonical signals.

sebuah judul-template contoh:

// app/layout.tsx
export const metadata: Metadata = {
  metadataBase: new URL('https://example.com'),
  title: { template: '%s | Brand Name', default: 'Brand Name' },
}
// app/blog/page.tsx
export const metadata: Metadata = { title: 'My Blog Post' }
// Output: <title>My Blog Post | Brand Name</title>

Two gotchas. pertama, metadata adalah shallowly merged dari layout untuk halaman — sebuah nested object like openGraph defined di sebuah child segment replaces parent’s entirely, so sebuah halaman-tingkat openGraph: { title: 'Home' } quietly drops apa pun openGraph.images set di layout. kedua — dan ini one bites people — metadata hanya berfungsi di server Components. Export ini dari sebuah 'use client' file dan ini silently melakukan nothing.

Streaming metadata. untuk dynamically rendered halaman, generateMetadata dapat stream metadata setelah initial HTML. Googlebot executes JavaScript dan inspects full DOM, so streamed metadata berfungsi untuk Google. tetapi Next.js detects “HTML-limited bots” (terjemahan) “HTML-limited bot” — Bingbot, Twitterbot, Slackbot, facebookexternalhit — dan ships them blocking metadata di <head> instead. Per Next.js docs, “streaming metadata is disabled for bots and crawlers that expect metadata to be in the <head> tag.” (terjemahan) “streaming metadata adalah disabled untuk bot dan crawler itu expect metadata untuk menjadi di undefined tag.” ini adalah automatic; no configuration needed. ini adalah sebuah detail almost no competing guide covers, dan ini adalah why streaming metadata isn’t sebuah risk untuk bot itu dapat’t wait untuk ini. Prerendered halaman adalah sebuah berbeda case entirely — metadata there resolves di bangun time, so there’s no stream untuk worry tentang. ini perilaku adalah versi-spesifik (saat ini sebagai dari Next.js 16.2.10); re-periksa generateMetadata docs when Anda upgrade. dan because delivery paths differ oleh entry poin, verify metadata two cara, not one: sebuah direct/production permintaan (curl -I atau View Source) dan sebuah client-side navigation untuk yang sama route — head dapat update differently antara two.

Evidence for this claim Prerendered Next.js pages do not use streaming metadata because metadata is resolved at build time in the documented path. Scope: route output, metadata and deployment Confidence: high · Verified: Metadata and OG images

Metadata dengan next/head (halaman Router)

pada halaman Router, metadata lives di <Head> dari next/head:

import Head from 'next/head'

export default function Page() {
  return (
    <>
      <Head>
        <title>My Page | Brand</title>
        <meta name="description" content="Description" />
        <link rel="canonical" href="https://example.com/my-page" />
      </Head>
      {/* page content */}
    </>
  )
}

Set judul dan deskripsi per halaman (not hanya di _app.js), dan put sebuah canonical pada setiap halaman including paginated variants. next-seo package standardizes ini dengan sebuah <NextSeo> component dan structured-data helpers. Migrating untuk App Router mostly berarti trading next/head dan next-seo untuk native metadata export.

melakukan Anda perlu next-seo package? ini adalah sebuah ketiga-party plugin (not bagian dari Next.js itself) — actively maintained, di v7.2,0 sebagai dari ini writing, not archived. -nya own docs adalah explicit tentang where ini fits: untuk standard meta tags pada App Router, package’s README recommends menggunakan Next.js’s dibangun-di generateMetadata/metadata export alih-alih <NextSeo>; pada halaman Router, <NextSeo> adalah masih sebuah reasonable convenience layer di atas next/head. one App Router gunakan case package masih covers adalah -nya JSON-LD helper components (ArticleJsonLd, FAQPageJsonLd, etc. dengan useAppDir), which beberapa teams prefer di atas hand-rolling <script type="application/ld+json">. Bottom line: pada sebuah baru App Router bangun, reach untuk native Metadata API pertama — package adalah optional, not sebuah requirement, dan -nya own maintainers say so.

Sitemaps

di App Router, app/sitemap.ts adalah sebuah file convention itu outputs /sitemap.xml:

import type { MetadataRoute } from 'next'

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    { url: 'https://acme.com', lastModified: new Date(), priority: 1 },
    { url: 'https://acme.com/blog', lastModified: new Date(), priority: 0.8 },
  ]
}

untuk besar situs, generateSitemaps() shards ke multiple files (Google’s limit adalah 50 000 URLs per sitemap), setiap disajikan di /.../sitemap/[id].xml. sitemap output juga mendukung image sitemaps, video sitemaps, dan localized alternates.languages. pada halaman Router, gunakan next-sitemap atau generate pages/sitemap.xml.js dengan getServerSideProps.

robots.txt

app/robots.ts generates Anda robots file programmatically:

export default function robots(): MetadataRoute.Robots {
  return {
    rules: [{ userAgent: '*', allow: '/', disallow: '/private/' }],
    sitemap: 'https://acme.com/sitemap.xml',
  }
}

Per-pengguna-agent aturan dan multiple sitemaps adalah didukung. halaman Router menggunakan sebuah static public/robots.txt. aturan Anda cannot get wrong pada either router: tidak pernah disallow Anda JavaScript atau CSS — Google won’t render dari blocked files, dan pada sebuah JS framework itu dapat blank out halaman entirely.

next/image dan Core Web Vitals

next/image adalah one dari strongest alasan untuk gunakan framework untuk SEO. ini lazy-memuat below—fold images, memerlukan width/height (atau fill) so ini reserves space dan mencegah layout shift / CLS, menyajikan WebP/AVIF automatically, dan emits sebuah proper srcset dari sizes prop. single sebagian besar penting CWV optimization adalah priority prop pada Anda hero / above—fold image, which preloads ini untuk sebuah faster LCP:

<Image src="/hero.jpg" width={1200} height={630} priority alt="Hero" />

Forgetting priority pada LCP image adalah paling umum Next.js CWV mistake — dan CWV masalah adalah widespread pada nyata Next.js situs (see Stats tab untuk Salt Agency data). alt adalah diperlukan: empty untuk decorative images, descriptive untuk konten images.

next/tautan dan internal linking

next/link renders standard <a href> anchors di HTML, so Google mengikuti them normally, dan ini menambahkan client-side navigation plus background prefetching dari di-viewport tautan di production. SEO aturan adalah sederhana: gunakan next/link untuk tautan internal, dan tidak pernah substitute sebuah onClick handler atau JavaScript navigation itu doesn’t produce sebuah nyata anchor — itu tautan aren’t dapat di-crawl. gunakan prefetch={false} pada rendah-nilai tautan untuk save bandwidth jika Anda perlu.

Dynamic routes dan generateStaticParams

generateStaticParams() tells Next.js which dynamic routes untuk pre-render di bangun time:

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getPosts()
  return posts.map((post) => ({ slug: post.slug }))
}

halaman dibangun ini cara adalah fully static HTML — best untuk SEO. Without ini, dynamic routes adalah rendered pada-demand (SSR) oleh default, which adalah fine tetapi reintroduces server latency. Combine ini dengan revalidate (ISR) untuk konten itu updates regularly. pastikan semua penting dynamic URLs adalah di generateStaticParams so nothing waits pada render queue.

data terstruktur (JSON-LD)

Metadata API memiliki no structured-data field — Anda inject JSON-LD sebagai sebuah <script> di sebuah server Component, which mempertahankan ini di server-rendered HTML di zero client-bundle cost:

const jsonLd = { '@context': 'https://schema.org', '@type': 'Article', /* … */ }
return <script type="application/ld+json"
  dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />

artikel/BlogPosting, BreadcrumbList, Product, dan FAQPage adalah usual jenis. Validate dengan Rich hasil Test setelah apa pun rendering perubahan.

umum Next.js SEO mistakes

Drawn dari nyata audits dan patterns above:

  1. Missing atau relative canonicals — sering dari sebuah forgotten metadataBase, which juga breaks OG image URLs.
  2. No priority pada LCP image — biggest CWV miss.
  3. 404 views returning 200 — gunakan dibangun-di notFound() untuk mengembalikan nyata status; soft 404s adalah rampant pada Next.js situs.
  4. metadata exported dari sebuah Client Component — silently melakukan nothing; ini adalah server Component hanya.
  5. CSR untuk primary konten — fetching critical konten di useEffect berarti non-Google crawler get empty halaman.
  6. Hash (#) routing alih-alih History API — itu views aren’t separately dapat di-crawl.
  7. Not exporting generateStaticParams — dynamic routes render pada-demand alih-alih menjadi pre-dibangun.
  8. openGraph overwritten oleh layout inheritance — child segments replace, not merge.
  9. Blocking JS/CSS di robots.txt atau via sebuah konten Security Policy itu stops Googlebot’s headless Chrome dari memuat scripts — test dengan pemeriksaan URL.

Deployment notes

Next.js adalah dibangun oleh Vercel; hosting there gives tight integration (edge CDN untuk static dan ISR halaman, baik TTFB) tetapi isn’t diperlukan. Define permanent redirects di redirects() di next.config.js (mengembalikan 308, atau 301 dengan permanent: true) untuk reliable signaling untuk crawler, set security dan caching headers via headers(), dan gunakan X-Robots-Tag respons headers untuk path-based noindex aturan when per-halaman robots metadata adalah awkward.

What untuk periksa where. Cache state, kode status, redirects, dan streamed metadata don’t semua tampilkan up di yang sama test — sebuah route dapat look fine di one periksa dan masih menjadi broken di lainnya:

periksaWhere untuk lookWhy ini dapat differ dari what Anda see rendered
Cache/revalidation agerespons headers pada sebuah direct permintaan (curl -I)ISR dapat sajikan stale halaman pada permintaan right setelah window expires
Direct HTTP statuscurl -I pada production URL, not rendered UIsebuah “not found” (terjemahan) “tidak ditemukan” view without notFound() masih mengembalikan 200
Redirect perilakuactual context itu fires ini — redirect() di sebuah server tindakan, sebuah Route Handler, vs. sebuah client onClickkode status dan respons path differ oleh invocation context, not hanya destination
Streamed metadataDirect permintaan dan client-side navigation untuk yang sama routeOrdinary clients dapat get streamed metadata; HTML-limited bot get blocking metadata; two paths aren’t identical
Client-side transitionsNavigate di-app, lalu re-periksa <head>sebuah route itu’s correct pada pertama muat dapat drift setelah sebuah client transition

None dari ini adalah guaranteed oleh framework — Next.js gives Anda mechanisms (redirects(), notFound(), revalidation, streaming), tetapi cache keys, invalidation, preview state, dan deployment configuration adalah masih Anda responsibility untuk get right dan untuk test di production, not hanya locally.

One last note pada dynamic rendering — serving prerendered HTML untuk bot dan JavaScript untuk pengguna. Google memiliki deprecated ini sebagai sebuah recommendation: “dynamic rendering was a workaround and not a long-term solution.” (terjemahan) “dynamic rendering adalah sebuah workaround dan not sebuah panjang-istilah solusi.” Anda tidak perlu ini pada Next.js anyway — SSR, SSG, ISR, dan server Components semua put konten di HTML natively. Mention ini so Anda recognize ini di sebuah audit; don’t bangun pada ini.

happy path: App Router + server Components + ISR + Metadata API (dengan metadataBase) + next/image dengan priority. Get itu right dan sebagian besar dari Next.js SEO adalah handled.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.