Instalacija
Pogledajmo sada kako izraditi novu "Next.js" aplikaciju i krenuti sa osnovnim primjerom dohvaćanja podataka sa vanjskog API-ja. Započeti ćemo jednostavnom, statičkom listom Pokemona na našoj početnoj stranici (pages/index.js). Na ovaj način ćemo se odmah upoznati sa konceptom file-based routinga, korištenjem funkcije getStaticProps i osnovnom strukturom Next.js projekta.
Stvaranje novog projekta
Kako bismo jednostavno postavili početni projekt, poslužit ćemo se naredbom:
npx create-next-app@latest pokedex-appGdje je naravno "pokedex-app" ime našeg projekta, pa ga možete po želji prilagoditi. Ova naredba će instalirati sve dodatne pakete potrebne za pokretanje "Next.js" aplikacije i kreirati početnu strukturu projekta. Prilikom instalacije ćete vjerojatno morati potvrditi instalaciju određenih paketa (ako nisu već instalirani na računalu), te ćete također dobiti niz upita koji služe za inicijalnu konfiguraciju projekta.
- Would you like to use TypeScript?
Ako odaberemo "Yes", projekt će se postaviti s TypeScript konfiguracijom (.ts/.tsx datotekama), što je korisno za veće projekte i lakše otkrivanje pogrešaka. "No" znači da nastavljate koristiti klasične JavaScript (.js/.jsx) datoteke bez tipizacije.
- Would you like to use ESLint?
ESLint je alat za provjeru (lintanje) koda koji pomaže održati konzistentan stil i otkriti potencijalne pogreške ili loše prakse prilikom kodiranja. Ako odaberemo "Yes", dobit ćemo inicijalnu ".eslintrc" konfiguraciju.
- Would you like to use Tailwind CSS?
Tailwind CSS je popularni CSS framework, olakšava brzo oblikovanje bez pisanja puno vlastitih CSS klasa.Ako želimo ručno oblikovati komponente ili koristiti neki drugi pristup, odabrati ćemo opciju "No".
- Would you like your code inside a src/ directory?
Next.js postavlja početnu strukturu s direktorijem "pages/" u glavnoj razini (root) projekta. Ova opcija omogućava stvaranje zasebnog "src/" direktorija (npr. src/pages/), što može olakšati organizaciju i preglednost. Ovo možete odabrati ovisno o vlastitim preferencijama.
- Would you like to use App Router? (recommended)
App Router je noviji pristup (od Next.js 13) za organizaciju ruta u direktoriju "app/". Ako odaberemo "Yes", dobit ćemo novu arhitekturu (sa app/ umjesto pages/) i neke dodatne značajke koje nisu podržane u starijim verzijama (npr. nested layout). Ako odaberemo "No", nastavit ćemo s klasičnom "pages/" strukturom. Za potrebe većine projekata preporučuje se "Yes" (ali treba znati da je “pages router” i dalje podržan).
- Would you like to use Turbopack for next dev?
Turbopack je nova generacija bundlera (nasljednik Webpacka) koji je tek relativno nedavno objavljen u stabilnoj verziji. Ako odaberemo "Yes", razvojni server će koristiti Turbopack (brže osvježavanje koda), ali može se dogoditi da neke funkcionalnosti možda nisu u potpunosti implementirane. Odabir "No" znači da ostajemo na klasičnom Webpack razvojnom serveru.
- Would you like to customize the import alias (@/ by default)?*
Import alias olakšava uvoz datoteka unutar projekta (npr. import { Komponenta } from "@/components/Komponenta";). Ako odaberemo "Yes", možemo prilagoditi "alias" u tsconfig.json ili jsconfig.json datotekama.
Nakon dovršene instalacije, možemo (koristeći Terminal) ući u direktorij projekta i pokrenuti razvojni poslužitelj koji će prikazati našu početnu aplikaciju u web pregledniku (isto kao i Vite u prethodnim lekcijama).
cd pokedex-app
npm run devOsnovna struktura projekta
Slično kao i kod Vite-a, dobili smo početnu strukturu projekta sa nekoliko direktorija i datoteka. Većina datoteka u root direktoriju su konfiguracijske datoteke i neke smo već imali prilike vidjeti u prethodnim primjerima (npr. "package.json").
Za početak nam je najvažniji direktorij app koji unutar sebe ima dvije datoteke:
- app/layout.js - glavni izgled (layout) za cijelu aplikaciju. U većini slučajeva sadrži
<head>i<body>oznake, globalne stilove, fontove i slično... - app/page.js - Ovo je početna stranica naše aplikacije i dostupna je preko početne (root) adrese - "/". Svaki poddirektorij unutar "app/" predstavlja novu rutu tj. adresu stranice. Npr. ako napravimo datoteku "app/pokemon/page.js", toj stranici možemo pristupiti preko adrese "/pokemon".
Kada koristimo "App Router", svaki .js ili .jsx modul u app/ je definiran kao “server component” tj. poslužiteljska komponenta, što znači da se izvršava na serveru. To nam omogućuje da direktno dohvaćamo podatke (npr. fetch) unutar komponenti, bez potrebe za korištenjem getStaticProps ili getServerSideProps koje se koriste sa pages router pristupom.
Podešavanje layout.js
Možemo za probu modificirati layout.js datoteku i vidjeti kako to utječe na prikaz stranice. Trenutno imamo samo jednu stranicu ali ćemo svejedno modificirati ovu datoteku kako bi postavili naš izgled koji definira cjelokupni izgled i strukturu našeg HTML-a. Primjer najjednostavnije strukture izgleda ovako:
import "./globals.css";
export const metadata = {
title: 'Next.js Pokédex',
description: 'Pokédex aplikacija u Next.js uz App Router',
};
export default function RootLayout({ children }) {
return (
<html lang="hr">
<body>
<header>JuniorDev Next.js</header>
{children}
</body>
</html>
);
}Obratimo pozornost na par detalja:
-
metadata objekt služi za definiranje
<title>i<meta>oznaka za SEO i semantičke svrhe. Nakon ove promjene možemo odmah vidjeti da se ime kartice (tab) u pregledniku postavilo na novu vrijednost. -
RootLayout komponenta prima children svojstvo – sve podstranice će se prikazati na mjestu tog svojstva. Ovime smo zapravo sve podstranice "omotali" sa
<html>i<body>oznakama. -
Za potrebe demonstracije smo dodali header element koji će se u ovom slučaju prikazivati na vrhu svake stranice. Ovo možemo i izbrisati kako ne bi utjecalo na dizajn naše aplikacije (ili ga oblikovati tako da se uklapa u stranicu)
-
na početku smo napravili uvoz globalnog CSS stila koji će se primjenjivati na sve stranice.
Sada možemo nastaviti na uređivanje početne stranice.
Početna stranica page.js
Kao što je već spomenuto, početna stranica (/) je definirana datotekom app/page.js. Modificirati ćemo stranicu koja je generirana automatski te ćemo implementirati slanje GET zahtjeva na otvoreni PokeAPI kako bi dohvatili popis 20 početnih Pokemona.
S obzirom da koristimo "App Router", ova stranica je poslužiteljska komponenta i možemo raditi dohvat (fetch) u samom tijelu komponente. U slučaju kada bi se ova komponenta izvršavala na klijentskoj strani, morali bi implementirati dohvat na sličan način kao što smo radili u prethodnim lekcijama (npr. sa useEffect hook-om)
export default async function Home() {
// 1. Dohvaćamo podatke (server-side, unutar posluziteljske komponente)
const res = await fetch('https://pokeapi.co/api/v2/pokemon?limit=20');
const data = await res.json();
// 2. Izdvajamo polje results koje sadrži niz Pokémona
const pokemoni = data.results || [];
// 3. Vraćamo JSX koji se renderira na serveru, a zatim šalje klijentu
return (
<main>
<h1>Pokédex Aplikacija</h1>
<ul>
{pokemoni.map((pokemon, index) => (
<li key={index}>{pokemon.name}</li>
))}
</ul>
</main>
);
}Pogledajmo sada nekoliko detalja koji su vezani uz koncepte poslužiteljskog renderiranja i SSG (generiranje statičkih stranica)
Upravljanje predmemoriranjem i revalidacijom
Stranice koje su generirane na poslužitelju, Next.js sprema u svoju predmemoriju (cache) te ih može ponovno koristiti u slučaju ponovljenog zahtjeva. Naravno, to nije uvijek poželjna opcija, pogotovo kada se radi o stranicama koje sadrže dinamičke podatke koji se mogu mijenjati u stvarnom vremenu. U takvim situacijama, Next.js nam dozvoljava da definiramo koliko dugo će stranica generirana na poslužitelju "vrijediti" tj. koliko je dugo možemo koristiti prije nego je potrebno generirati novu verziju te stranice.
Ovo možemo postići slanjem dodatnih opcija fetch metodi, koristimo revalidate opciju pomoću koje definiramo nakon koliko sekundi je potrebno osvježavanje stranice. Ovako izgleda modificirani primjer naredbe za dohvat podataka iz prethodnog primjera:
const res = await fetch('https://pokeapi.co/api/v2/pokemon?limit=20', {
next: { revalidate: 60 }, // Ponovna validacija nakon 60 sekundi
});Ukoliko na stranici/komponenti imamo više zahtjeva, možemo jednostavno na početku stranice definirati i globalnu konstantu za ponovnu validaciju:
export const revalidate = 60; Također možemo ići i u "drugom smjeru" tj. po želji možemo postaviti strogi SSG (static site generation) - statičko generiranje stranica koje se izvršava samo jednom (prilikom build-a aplikacije)
const res = await fetch('https://pokeapi.co/api/v2/pokemon?limit=20', {
next: { cache: 'force-cache' },
});