Next.js SEO: prohodna i indeksabilna aplikacija
Napravite React aplikaciju, objavite je i gledate kako se analitika ne pomera. Google-ovi crawleri dođu do vaših stranica, vide prazan
<div id="root"></div> i idu dalje. Vaš sadržaj nikad ne bude indeksiran.
Next.js to rešava tako što renderuje kompletan HTML na serveru pre nego što išta stigne do pregledača. Ali samo renderovanje na serveru nije dovoljno - potrebni su vam ispravni metapodaci, strukturirani podaci i brzina da biste zaista rangirali.
Prvo: proverite šta crawler dobije
Pre nego što išta menjate, pogledajte odgovor servera, a ne renderovan DOM:
curl -s https://vas-sajt.rs/proizvod/x | grep -c "opis proizvoda"
Ako vašeg sadržaja nema u tom ispisu, vaše indeksiranje zavisi od toga da li Google renderuje vaš JavaScript - a renderuje, ali sa odlaganjem i bez garancije. To je jedina dijagnoza koja se računa, i većina je preskoči.
Strategije renderovanja
SSR - renderuje se na serveru pri svakom zahtevu. Za stranice koje izgledaju drugačije po korisniku ili po minutu: rezultati pretrage, korpa, stanje naloga.
SSG - stranice se generišu u trenutku build-a. Najbrže moguće, i tačan izbor za sve što se retko menja: tekstovi na blogu, landing stranice, dokumentacija.
ISR - generiše se statički, pa se u pozadini obnavlja po isteku zadatog intervala. Kompromis za velike kataloge proizvoda koji se menjaju dnevno, ali ne po zahtevu.
Na klijentu (CSR) - za SEO beskorisno. Koristite iza prijave (login-a), gde indeksiranje ionako nije poželjno.
Pravilo: statički gde može, ISR gde statički ne stiže, SSR samo kad je sadržaj stvarno drugačiji pri svakom zahtevu.
Metadata API
U App Router-u vam ne treba dodatni paket. Metapodaci se izvoze po ruti:
export const metadata: Metadata = {
metadataBase: new URL('https://vas-sajt.rs'),
title: 'Naslov stranice',
description: 'Opis koji poziva na klik.',
alternates: { canonical: '/proizvod/x' },
openGraph: {
type: 'website',
url: 'https://vas-sajt.rs/proizvod/x',
images: [{ url: '/og/proizvod-x.png', width: 1200, height: 630 }],
},
}
Za dinamičke rute generateMetadata, koji sme da povuče iste podatke kao i
stranica - Next će dupli poziv spojiti u jedan:
export async function generateMetadata({ params }): Promise<Metadata> {
const { slug } = await params
const proizvod = await getProizvod(slug)
if (!proizvod) return {}
return {
title: `Kupite ${proizvod.naziv}`,
description: proizvod.kratakOpis,
alternates: { canonical: `/proizvod/${slug}` },
}
}
Obratite pažnju: u Next.js-u 15 i novijem params je Promise i mora da se
sačeka (await).
Da li vam još treba next-seo?
Ukratko: u App Router-u ne. next-seo je pravljen za Pages Router, gde su
metapodaci išli kroz next/head. Metadata API nativno pokriva title,
description, canonical, OpenGraph, Twitter kartice i alternates.languages za
hreflang.
Ako ste još na Pages Router-u, next-seo i dalje ima smisla. Pri migraciji ga
izbacujete zajedno sa next/head.
Sitemap i robots
Oba kao konvencije fajlova, ne kao API rute:
// app/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const proizvodi = await getSveProizvode()
return proizvodi.map((p) => ({
url: `https://vas-sajt.rs/proizvod/${p.slug}`,
lastModified: p.izmenjeno,
}))
}
Napomena koja važi za više sajtova nego što se misli: nemojte stavljati
lastModified: new Date(). Tada svaki URL pri svakom pozivu tvrdi da je
promenjen danas, pa Google nauči da uopšte ignoriše vaš lastmod. Izostavite
polje ako nemate pravi datum.
Strukturirani podaci
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
JSON.stringify je ovde bitan. Ručno sklopljen JSON string puca na prvom
apostrofu u opisu, a ako slučajno ubacite React element, ispisaće se
[object Object] - tiho, i zato ostane pokvareno.
Core Web Vitals
LCP - next/image sa priority za sliku iznad pregiba, fontovi preko
next/font na sopstvenom serveru umesto sa tuđih.
CLS - uvek zadajte širinu i visinu, ostavite mesto za banere, bez pomeranja layouta zbog fontova koji se učitavaju kasnije.
INP - držite klijentske bundle-ove malim. Proverite koliko vaših komponenti
nosi 'use client' a da im ne treba; u App Router projektima to je najčešći
razlog za previše JavaScript-a.
Kratka kontrolna lista
- Sadržaj je u odgovoru servera, provereno sa
curl - Canonical na svakoj ruti
metadataBasepostavljen, inače su OG URL-ovi relativni- Sitemap bez izmišljenog
lastmod - JSON-LD kroz
JSON.stringify, provereno Rich Results testom hreflangsamo za jezike koje stranica stvarno ima- Slike kroz
next/image, fontovi kroznext/font
Ako imate Next.js sajt koji se obilazi ali se ne indeksira - to je dijagnoza koju radim za agencije i produktne timove, zajedno sa implementacijom posle nje, opisana na stranici tehnički SEO za Next.js i prodavnice. Primer iz prakse je studija slučaja Ading GmbH: bečki sajt firme koji sam sa hakovanog WordPress-a prebacio na Next.js - prosečna pozicija na Google-u poboljšala se sa 40 na 22.