Optimizacija
Optimizacija

Optimizacija aplikacije

Nakon što smo implementirali više različitih funkcionalnosti na našoj aplikaciji, kratko ćemo se osvrnuti i na dio razvoja aplikacija koji možda nije toliko "zabavan" ali je također važan dio razvoja. Pogledati ćemo nekoliko primjera na koje načine sve možemo optimizirati našu aplikaciju - bilo da se radi o SEO (optimiziaciji za tražilice) ili optimizaciji učitavanja i korisničkog iskustva.

SEO

Renderiranje na strani poslužitelja i SSG nam već daju prednost u odnosu na klasično klijentsko renderiranje: pretraživač dobije gotov HTML, a ne prazan div koji tek treba hidraciju (dohvat i prikaz podataka). Ali sadržaj bez pravih meta-oznaka (meta-tag) gubi vrijednost: Google (ili neki drugi pretraživač) ne zna što točno stranica predstavlja, a dijeljenje poveznice na društvenim mrežama prikazuje neugledan naslov “/pokemon/1”. Next.js uvodi Metadata API — deklarativan način opisivanja svake rute koristeći <title>, <meta name="description">, Open Graph atribute, robots direktivu i ikone. Sve navedeno možemo vrlo jednostavno dodati putem JavaScripta/TypeScripta.

Za početak možemo malo proširiti zadani layout za sve stranice i nadograditi metadata objekt kojeg će Next.js automatski pretvoriti u odgovarajuće HTML oznake i dodati u <head> dio stranice:

app/layout.js
export const metadata = {
  title: "Next.js Pokédex │ JuniorDev",
  description: "Proučite sve Pokémone uz Next.js aplikaciju.",
  keywords: ["Pokédex", "Next.js", "React", "Pokémon"],
  icons: {
    icon: "/favicon.ico",
  },
  authors: [{ name: "JuniorDev", url: "https://juniordev.example" }],
  twitter: {
    card: "summary_large_image",
    site: "@JuniorDev",
  },
};

Također ćemo i u dinamičkoj stranici za prikaz Pokemona dodati izvoz funkcije generateMetadata pomoću koje možemo generirati dinamičke podatke koji će biti prilagođeni za svaku stranicu. Za svakog Pokemona ćemo dohvatiti njegove podatke (i sliku), te ćemo ih iskoristiti kako bi generirali dodatne metapodatke i sliku koja će se prikazati ako kopiramo (i podijelimo) link za stranicu sa odabranim Pokemonom

export async function generateMetadata({ params }) {
  const { name } = params;
  const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`);
  if (!res.ok) return { title: "Pokémon ne postoji" };
 
  const data = await res.json();
 
  const image = data.sprites.other["official-artwork"].front_default;
 
  return {
    title: `Pokédex │ ${data.name}`,
    description: `${data.name} – visina ${data.height} cm, težina ${data.weight} kg.`,
    openGraph: {
      images: [{ url: image, width: 400, height: 400 }],
    },
    twitter: {
      card: "summary_large_image",
      images: [image],
    },
  };
}
 
export default async function PokemonPage({ params }) {
  /* ...isto dohvaćanje i prikaz detalja kao dosad... */
}

Performanse aplikacije

Osim optimizacije za tražilice, možemo malo poboljšati i performanse same aplikacije. Google rangira stranice i po brzini. Pri tome imamo tri ključna parametra koje se nazivaju Core Web Vitals:

  • LCPLargest Contentful Paint (“koliko brzo vidim glavni sadržaj?”)

  • CLSCumulative Layout Shift (“skače li mi layout dok se učitava?”)

  • INPInteraction to Next Paint (“koliko brzo UI reagira na moj klik?”)

Next.js već odrađuje dobar dio posla: renderira HTML, optimizira slike i automatski izbacuje neiskorišteni CSS. Ipak, uz sve navedeno, možemo napraviti i nekoliko "ručnih" prilagodbi kako bi poboljšali performanse aplikacije.

Lighthouse

Pogledajmo prvo kako izmjeriti performanse aplikacije - većina web preglednika ima ugrađene alate za testiranje. Preglednici temeljeni na Chromium pregledniku imaju ugrađen alat Lighthouse pomoću kojeg možemo jednostavno dobiti detaljnu analizu performansi stranice. Postupak je jednostavan:

  1. Otvorite DevTools prozor (desna tipka miša + "Inspect/Provjeri" ili F12)
  2. Otvorite "Lighthouse" karticu
  3. Odaberite parametre analize i pritisnite "Analyze page load"
  4. Nakon završetka pogledajte detaljnu analizu performansi stranice

Uvidom u analizu možete pogledati prilagođene savjete kako poboljšati performanse svoje aplikacije. Naravno, konkretni prijedlozi mogu varirati ovisno o sadržaju aplikacije i načinu na koji su određene funkcionalnosti implementirane. Ipak, možemo navesti nekoliko generalnih savjeta koji mogu pomoći pri optimizaciji.

LCP - optimizacija slike

Optimizacija Largest Contenful Paint se u većini slučajeva svodi na optimizaciju najveće slike koja se prikazuje na stranici. Na našem primjeru stranice sa detaljima Pokemona imamo samo dvije slike - logo u header-u i sliku samog Pokemona. S obzirom da smo slike Pokemona stavili u relativno male dimenzije, vjerojatno će nam logo slika najviše utjecati na LCP.

Srećom, optimizacija slika u Next.js je jednostavna. Dio toga se odvija automatski (kao lazy-loading), a dio možemo sami podešavati. Najveću sliku na stranici je dobro označiti kao "priority" kako bi je Next.js unaprijed dohvatio i tako ubrzao proces učitavanja. U slučaju loga, otvoriti ćemo layout.js datoteku i napraviti sitnu promjenu:

app/layout.js
export default function RootLayout({ children }) {
  return (
    <html lang="hr">
      <body>
        <header className="info">
          <Image
            src={logo}
            alt="JuniorDev Next.js"
            placeholder="blur"
            blurDataURL="../next.svg"    
            height={50}
            priority
            />
          </header>
        {children}
      </body>
    </html>
  );
}

Nakon ovoga možemo ponovno pokrenuti Lightouse test i provjeriti utjecaj na performanse.

CLS - pomicanje sadržaja

Osim slika, dinamične kartice ili naknadno učitani moduli najčešći su krivci za “layout shift". Djelomičan problem možemo vidjeti u našoj komponenti za dodavanje Pokemona u favorite - tekst na button elementu se mijenja (imamo nekoliko varijanti: "provjera", "spremi u favorite", "spremljen", itd...) i samim time može doći do promjene dimenzija samog elementa što uzrokuje layout shift.

Nekoliko je načina kako utjecati na to - npr. nakon što smo implementirali provjeru na poslužitelju, više nema "skoka" sa stanja "provjeravam" u finalno stanje. Također, promjenjive dijelove sučelja možemo ubaciti u roditeljski div (container) koji će biti fiksne visine i time ćemo smanjiti mogućnost CLS-a. Primjer:

<div className="relative h-[300px]">
  {/* <Image fill objectFit="contain" … /> */}
</div>

LCP/INP - cache strategija

Više puta smo kroz lekcije spominjali opcije cache-iranja sadržaja pa ćemo samo kratko ponoviti dostupne opcije i kako utječu na LCP i INP parametre:

StrategijaLCPINPKada primijeniti
no-storeLošiji (server obrađuje svaki request)Odličan (podaci su svježi, nema re-hidratacije)Dinamični podaci, dashboard
revalidate = nVrlo dobar (HTML već postoji; povremeno se regenerira)DobarPolustatični sadržaj poput Pokédexa
force-cache (SSG)Najbolji (CDN služi statički dio)NajboljiBlog, statične stranice

INP

Što se tiče INP, tu je najvažnije dobro osmisliti logiku osvježavanja podataka na stranici - tzv. hidracija. Nema smisla osvježavati dijelove stranica na kojima nije došlo do nikakve promjene. Također, poslužitelj je u pravilu bolji odabir za statički sadržaj, a interaktivni dio može biti i na klijenstkoj strani. Neki od općenitih savjeta su:

  • Hidrirajte samo ono što mora biti interaktivno. Npr. u našem primjeru samo tipka FavoriteButton koristi "use client" i njeno stanje je potrebno osvježiti. Sve ostale podatke je dovoljno samo jednom učitati i prikazati.

  • Velike lista (kao npr. niz Pokémona) je bolje ostaviti kao poslužiteljsku komponentu.

  • koristite različite metode za izbjegavanje blokiranja korisničkog sučelja (UI blocking), te na taj način INP ostaje nizak - npr. koristili smo useTransiton hook.

  • Izbjegavajte globalne console.log u produkciji — sitno, ali utječe na vrijeme izvršavanja procesa/niti.

Produkcijska verzija aplikacije

Nakon što smo doradili aplikaciju, možemo je pokušati i objaviti na "pravom" poslužitelju tj. pokrenuti aplikaciju u produkcijskom okruženju. Za to će nam trebati dvije stvari:

  • GitHub korisnički račun
  • Vercel račun (podržava GitHub prijavu)

GitHub

Prvo što moramo je našu aplikaciju postaviti na GitHub repozitorij. Next.js projekti automatski imaju postavljen lokalni repozitorij pa ga je samo potrebno poslati na remote repozitorij koji ćete otvoriti na GitHubu:

  1. Kreirajte repo na GitHubu (New → Repository).
  2. Dodajte remote (udaljeni) repo:
git remote add origin https://github.com/<user>/next-pokedex.git
  1. Pošaljite kôd na remote repo:
git push -u origin main

Vercel

Sada možemo aplikaciju postaviti na Vercel:

  1. Prijavite se na https://vercel.com (opens in a new tab) s GitHub računom.
  2. Klik Add New → Project i odaberite next-pokedex repo.
  3. Vercel prepoznaje Next.js projekt i automatski postavlja:
    • Build Command next build
    • Output Dir .next
    • Environment vars – ostavite prazno (PokéAPI ne traži ključ).
  4. Potvrdite Deploy.

Nakon toga bi vam se trebao prikazati log deploy procesa. Ako je sve prošlo u redu, možete otvoriti vlastitu aplikaciju na generiranoj adresi (npr. https://next-pokedex.vercel.app (opens in a new tab).). Testirajte sve dijelove aplikacije i provjerite rade li ispravno. Također možete provjeriti performanse aplikacije na pravom poslužitelju.

Jednom kada se povezali GitHub repo sa Vercel projektom, svaki push na odabranu granu će pokrenuti novi deploy. Vercel podržava još dosta dodatnih opcija kao što su preview build, dodavanje vlastitih domena, certifikata, analize performansi i slično, ali to možete samostalno istražiti.

Last updated on April 27, 2025
Digitalna Dalmacija