Nazad na blog

Tehnički SEO za Next.js App Router: praktična kontrolna lista

Tehnički SEO za Next.js App Router: praktična kontrolna lista

9. oktobar 2026. — SEO

Objavljeno

9. oktobar 2026.

Vreme čitanja

3 min čitanja

Kategorija

SEO

Oznake

  • SEO
  • Next.js
  • App Router
  • Metadata
  • Structured Data

(Uvod)Metadata, kanonski URL-ovi, sitemap, robots.txt, strukturirani podaci, hreflang i Core Web Vitals u Next.js App Routeru, uz kod koji možete da kopirate.

Next.js vam daje sve što je potrebno za tehnički SEO, ali gotovo ništa od toga nije uključeno podrazumevano. Ovo je lista koju prođem na svakom App Router projektu, otprilike po redosledu uticaja.

1. Renderujte važan sadržaj na serveru

Pretraživači mogu da izvršavaju JavaScript, ali HTML renderovan na serveru indeksiraju brže i pouzdanije. U App Routeru su Server Components podrazumevane — držite naslove, tekst i linkove u njima, a Client Components koristite za interakciju, ne za sadržaj.

Brz test: otvorite stranicu sa isključenim JavaScriptom ili pokrenite curl https://yoursite.com/page. Ako glavnog teksta nema u odgovoru, nema ga ni u prvom krugu indeksiranja.

2. Podesite metadataBase i šablon naslova

U glavnom layoutu:

app/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  metadataBase: new URL('https://example.com'),
  title: {
    default: 'Example Studio — Web Design and Development',
    template: '%s — Example Studio',
  },
  description: 'Websites and products for small teams: design, development and launch.',
  openGraph: { siteName: 'Example Studio', type: 'website' },
}

metadataBase pretvara relativne URL-ove (kanonske, Open Graph slike) u apsolutne. Zahvaljujući šablonu, podstranice postavljaju samo svoj deo naslova.

3. Jedinstveni naslovi i opisi za svaku stranicu

Svaka stranica koja treba da se indeksira mora da ima sopstveni <title> (oko 50–60 karaktera, ključna reč pri početku) i meta opis (oko 150–160 karaktera, napisan tako da ljudi požele da kliknu). Za dinamičke stranice koristite generateMetadata:

app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  const post = await getPost(slug)
  if (!post) return {}
  return {
    title: post.title,
    description: post.excerpt,
    alternates: { canonical: `/blog/${post.slug}` },
    openGraph: {
      type: 'article',
      publishedTime: post.date,
      images: [{ url: post.cover, width: 1200, height: 630 }],
    },
  }
}

4. Kanonski URL-ovi

Podesite alternates.canonical na svakoj stranici. Štiti vas od duplikata koje stvaraju parametri upita (?utm_source=...), kose crte na kraju i alternativni domeni. Izaberite jedan oblik — sa ili bez www, sa ili bez kose crte na kraju — i ostale preusmerite na njega na nivou hostinga.

5. Sitemap

app/sitemap.ts generiše /sitemap.xml, i to radi i kod statičkog izvoza:

app/sitemap.ts
import type { MetadataRoute } from 'next'
import { getAllPosts } from '@/lib/blog'

export const dynamic = 'force-static'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const base = 'https://example.com'
  const posts = await getAllPosts()
  return [
    { url: base, changeFrequency: 'monthly', priority: 1 },
    ...posts.map((post) => ({
      url: `${base}/blog/${post.slug}`,
      lastModified: post.date,
      changeFrequency: 'yearly' as const,
      priority: 0.7,
    })),
  ]
}

Prijavite ga jednom u Google Search Console i Bing Webmaster Tools.

6. robots.txt

app/robots.ts
import type { MetadataRoute } from 'next'

export const dynamic = 'force-static'

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

Ne koristite robots.txt da biste sakrili stranice iz indeksa — blokirana stranica i dalje može biti indeksirana preko linkova. Umesto toga koristite robots: { index: false } u metapodacima stranice.

7. Strukturirani podaci (JSON-LD)

Strukturirani podaci pomažu pretraživačima da razumeju šta stranica jeste. Za članak, renderujte script tag na stranici:

tsx
const jsonLd = {
  '@context': 'https://schema.org',
  '@type': 'BlogPosting',
  headline: post.title,
  description: post.excerpt,
  datePublished: post.date,
  image: `https://example.com${post.cover}`,
  author: { '@type': 'Person', name: 'Jane Doe', url: 'https://example.com' },
}

return (
  <>
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, '\\u003c') }}
    />
    <article>{/* ... */}</article>
  </>
)

Korisni tipovi za mala preduzeća: Organization ili Person na početnoj stranici, BlogPosting za članke, BreadcrumbList za dublje stranice, Product za prodavnicu. Proverite ih Google-ovim alatom Rich Results Test.

8. Open Graph slike

Linkovi podeljeni na LinkedInu, X-u, Slacku i u aplikacijama za poruke koriste Open Graph oznake. Dajte svakoj stranici sliku od 1200×630 piksela. Možete dodati statički opengraph-image.png pored stranice ili je generisati pomoću ImageResponse u opengraph-image.tsx (kod statičkog izvoza generiše se tokom build-a, pa ne može da zavisi od zahteva).

9. Višejezični sajtovi: pravi URL-ovi i hreflang

Svaki jezik mora da ima svoj URL, inače će pretraživači videti samo jedan. Menjanje jezika na klijentu preko localStorage je u redu za posetioce, ali je nevidljivo za Google. Koristite segment za jezik — app/[lang]/... — i navedite alternative:

ts
alternates: {
  canonical: '/en/blog/my-post',
  languages: {
    en: '/en/blog/my-post',
    'zh-CN': '/zh/blog/my-post',
    'sr-Latn': '/sr/blog/my-post',
    'x-default': '/en/blog/my-post',
  },
}

Svaka jezička verzija treba da navede sve ostale, uključujući i sebe.

10. Interni linkovi i naslovi

  • Jedan <h1> po stranici, zatim <h2>/<h3> redom. Naslovi su struktura, ne veličina fonta.
  • Povezujte srodne stranice opisnim tekstom linka („vodič za statički izvoz", ne „kliknite ovde"). Koristite <Link> da bi linkovi bili pravi <a href> elementi.
  • Važne stranice držite na najviše tri klika od početne.

11. Core Web Vitals

Brzina je signal za rangiranje, a još važnije — signal za konverziju. Merite pomoću PageSpeed Insights (terenski podaci dolaze od stvarnih Chrome korisnika) i ciljajte LCP ≤ 2,5 s, INP ≤ 200 ms i CLS ≤ 0,1. Uobičajeni krivci u App Routeru: prevelika naslovna slika, previše JavaScripta na klijentu i pomeranje rasporeda zbog fontova ili slika bez dimenzija. Ako je vaš sajt pun animacija, pogledajte Core Web Vitals za animirane sajtove.

12. Slike

  • Opisan alt tekst za sadržajne slike; prazan alt="" za dekorativne.
  • Eksplicitni width/height ili aspect-ratio da se spreči pomeranje rasporeda.
  • Moderni formati (AVIF, WebP) i razumne veličine.
  • Opisni nazivi fajlova: nextjs-sitemap-example.png, ne IMG_2041.png.

13. 404 i preusmerenja

Za nepostojeće stranice vratite pravi 404 (notFound() u App Routeru). Kada premeštate ili preimenujete stranice, dodajte 301 preusmerenje sa starog URL-a — kod statičkog izvoza to je pravilo u _redirects na hostingu.

Kratka verzija

  • Sadržaj u Server Components
  • metadataBase i šablon naslova u glavnom layoutu
  • Jedinstven naslov, opis i kanonski URL na svakoj stranici
  • sitemap.ts i robots.ts, sitemap prijavljen
  • JSON-LD za članke, organizaciju, breadcrumbs
  • Open Graph slika na svakoj stranici
  • Jedan URL po jeziku, uz hreflang
  • Logični naslovi i opisni interni linkovi
  • Core Web Vitals u zelenom
  • Pravi 404 i 301 za premeštene stranice

Pravite statički sajt? Pročitajte vodič za Next.js statički izvoz za delove koji rade drugačije bez servera.

Bilten

Novi tekstovi na email

Kratak email kad objavim nešto novo. Bez spama, a pretplatu možete otkazati u bilo kom trenutku.

Markdown blog u Next.js bez CMS-a i baze podataka
Markdown blog u Next.js bez CMS-a i baze podataka
Sledeći tekst →