Programování

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.

12. 10. 2026 · 4 min čtení

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

  • useState v 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ší.