Next.js App Router: základy, které potřebujete znát
Složka app, layouty, Server a Client Components, načítání dat, Server Actions a metadata v Next.js App Routeru srozumitelně a s příklady.
App Router je od Next.js 13.4 doporučený způsob, jak stavět aplikace v Next.js. Oproti starému Pages Routeru mění hlavně to, kde se kód spouští – výchozí jsou Server Components. Tento článek vychází z Next.js 14/15; mezi verzemi se mění hlavně výchozí cachování, na které upozorníme.
Založení projektu
npx create-next-app@latest moje-app
cd moje-app
npm run dev
Průvodce se zeptá na TypeScript, ESLint, Tailwind a složku src/. Na otázku „App Router” odpovězte ano. Aplikace pak běží na http://localhost:3000.
Routování podle složek
Každá složka v app/ je segment URL, stránku vytváří soubor page.tsx:
app/
├── layout.tsx → společný obal všech stránek
├── page.tsx → /
├── blog/
│ ├── page.tsx → /blog
│ └── [slug]/
│ └── page.tsx → /blog/cokoli
└── (marketing)/
└── cenik/
└── page.tsx → /cenik (závorky se do URL nepropíší)
Speciální soubory v každém segmentu:
| Soubor | Účel |
|---|---|
layout.tsx |
obal, který přetrvá při navigaci mezi podstránkami |
loading.tsx |
zobrazí se během načítání (Suspense) |
error.tsx |
zachytí chyby v segmentu (musí být Client Component) |
not-found.tsx |
stránka pro notFound() |
route.ts |
API endpoint (GET, POST…) místo stránky |
Kořenový layout
// app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: { default: 'Moje app', template: '%s | Moje app' },
description: 'Ukázková aplikace v Next.js',
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="cs">
<body>{children}</body>
</html>
);
}
Kořenový layout musí obsahovat <html> a <body>. Objekt metadata vygeneruje <title> a meta tagy – nepište je ručně do <head>.
Server vs. Client Components
Komponenty v app/ jsou standardně serverové: běží jen na serveru, mohou být async, přímo číst databázi a jejich kód se neposílá do prohlížeče. Nemohou ale používat useState, useEffect, event handlery ani window.
Interaktivní komponentu označíte direktivou na prvním řádku souboru:
// app/components/LikeButton.tsx
'use client';
import { useState } from 'react';
export default function LikeButton() {
const [likes, setLikes] = useState(0);
return <button onClick={() => setLikes(l => l + 1)}>👍 {likes}</button>;
}
Dobrá praxe: nechte stránky a layouty serverové a 'use client' dávejte co nejníž ve stromu – jen na tlačítka, formuláře a widgety. Serverová komponenta může klientskou vykreslit, opačně to přímým importem nejde (ale můžete ji předat jako children).
Načítání dat
V Server Component prostě použijete await:
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';
type Props = { params: Promise<{ slug: string }> };
export default async function PostPage({ params }: Props) {
const { slug } = await params;
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 3600 },
});
if (!res.ok) notFound();
const post = await res.json();
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
);
}
Poznámka k verzím: v Next.js 15 jsou params a searchParams Promise a musíte je awaitovat; v Next.js 14 to byly obyčejné objekty. Také výchozí cachování fetch se změnilo – v 14 se odpovědi cachovaly automaticky, v 15 ne. Proto je lepší chování určit explicitně:
{ cache: 'no-store' }– vždy čerstvá data,{ next: { revalidate: 3600 } }– cache s obnovou po hodině,{ cache: 'force-cache' }– cachovat natrvalo.
Dynamické metadata a statické generování
export async function generateMetadata({ params }: Props) {
const { slug } = await params;
const post = await getPost(slug);
return { title: post.title, description: post.excerpt };
}
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map(p => ({ slug: p.slug }));
}
generateStaticParams řekne Next.js, které stránky předgenerovat při buildu.
Server Actions: formuláře bez API endpointu
// app/kontakt/page.tsx
import { revalidatePath } from 'next/cache';
async function sendMessage(formData: FormData) {
'use server';
const email = String(formData.get('email') ?? '');
const text = String(formData.get('text') ?? '');
if (!email.includes('@') || text.length < 5) return;
await saveToDb({ email, text });
revalidatePath('/kontakt');
}
export default function ContactPage() {
return (
<form action={sendMessage}>
<input name="email" type="email" required />
<textarea name="text" required />
<button type="submit">Odeslat</button>
</form>
);
}
Funkce s 'use server' běží na serveru, i když ji formulář zavolá z prohlížeče. Formulář funguje i bez JavaScriptu. Pozor: Server Action je fakticky veřejný endpoint – vstupy vždy validujte (např. knihovnou Zod) a ověřujte oprávnění uživatele.
Navigace
import Link from 'next/link';
<Link href="/blog">Blog</Link>
Pro programovou navigaci v Client Component použijte useRouter z next/navigation (ne z next/router, ten patří Pages Routeru) a router.push('/kam').
Build a nasazení
npm run build
npm start
Výpis po buildu ukazuje u každé routy, zda je statická (○) nebo dynamická (ƒ). Pokud stránka, kterou jste čekali jako statickou, vyšla dynamická, obvykle za to může cookies(), headers() nebo cache: 'no-store'.
Pro vlastní VPS stačí Node.js a správce procesů typu PM2 nebo systemd za reverzní proxy (Nginx). V next.config můžete nastavit output: 'standalone', který vytvoří minimální složku pro nasazení bez celého node_modules.
Časté chyby
useStatev souboru bez'use client'→ chyba při buildu.- Import serverového kódu (databáze, tajné klíče) do klientské komponenty. Pro jistotu použijte balíček
server-only. - Spoléhání na výchozí cachování, které se mezi verzemi liší.