Čeština

Objevte sílu Next.js App Routeru s naším podrobným průvodcem souborovým směrováním. Naučte se strukturovat aplikaci, tvořit dynamické trasy, spravovat layouty a další.

Next.js App Router: Podrobný průvodce souborovým směrováním

Next.js App Router, představený ve verzi Next.js 13 a standardizovaný v novějších verzích, přináší revoluci ve způsobu, jakým strukturujeme a navigujeme v aplikacích. Zavádí výkonný a intuitivní systém souborového směrování, který zjednodušuje vývoj, zlepšuje výkon a celkově zvyšuje zážitek vývojářů. Tento komplexní průvodce se podrobně ponoří do souborového směrování v App Routeru a poskytne vám znalosti a dovednosti pro tvorbu robustních a škálovatelných aplikací v Next.js.

Co je souborové směrování?

Souborové směrování je systém, kde je struktura tras vaší aplikace přímo určena organizací vašich souborů a adresářů. V Next.js App Routeru definujete trasy vytvářením souborů v adresáři `app`. Každá složka představuje segment trasy a speciální soubory v těchto složkách definují, jak bude daný segment trasy zpracován. Tento přístup nabízí několik výhod:

Začínáme s App Routerem

Chcete-li používat App Router, musíte vytvořit nový projekt Next.js nebo migrovat stávající. Ujistěte se, že používáte Next.js verze 13 nebo novější.

Vytvoření nového projektu:

Nový projekt Next.js s App Routerem můžete vytvořit pomocí následujícího příkazu:

npx create-next-app@latest my-app --example with-app

Migrace existujícího projektu:

Pro migraci existujícího projektu musíte přesunout své stránky z adresáře `pages` do adresáře `app`. Možná budete muset odpovídajícím způsobem upravit svou logiku směrování. Next.js poskytuje průvodce migrací, který vám s tímto procesem pomůže.

Základní koncepty souborového směrování

App Router zavádí několik speciálních souborů a konvencí, které definují, jak jsou vaše trasy zpracovávány:

1. Adresář `app`

Adresář `app` je kořenem tras vaší aplikace. Všechny soubory a složky v tomto adresáři budou použity k generování tras. Cokoli mimo adresář `app` (jako adresář `pages`, pokud migrujete) bude App Routerem ignorováno.

2. Soubor `page.js`

Soubor `page.js` (nebo `page.jsx`, `page.ts`, `page.tsx`) je nejzákladnější součástí App Routeru. Definuje komponentu UI, která bude vykreslena pro konkrétní segment trasy. Je to povinný soubor pro každý segment trasy, který chcete mít přímo přístupný.

Příklad:

Pokud máte takovouto strukturu souborů:

app/
  about/
    page.js

Komponenta exportovaná ze souboru `app/about/page.js` bude vykreslena, když uživatel přejde na `/about`.

// app/about/page.js
import React from 'react';

export default function AboutPage() {
  return (
    <div>
      <h1>O nás</h1>
      <p>Zjistěte více o naší společnosti.</p>
    </div>
  );
}

3. Soubor `layout.js`

Soubor `layout.js` (nebo `layout.jsx`, `layout.ts`, `layout.tsx`) definuje UI, které je sdíleno napříč několika stránkami v rámci segmentu trasy. Layouty jsou užitečné pro vytváření konzistentních hlaviček, patiček, postranních panelů a dalších prvků, které by měly být přítomny na více stránkách.

Příklad:

Řekněme, že chcete přidat hlavičku jak na stránku `/about`, tak na hypotetickou stránku `/about/team`. Můžete vytvořit soubor `layout.js` v adresáři `app/about`:

// app/about/layout.js
import React from 'react';

export default function AboutLayout({ children }) {
  return (
    <div>
      <header>
        <h1>O naší společnosti</h1>
      </header>
      <main>{children}</main>
    </div>
  );
}

Vlastnost `children` bude nahrazena UI vykresleným souborem `page.js` ve stejném adresáři nebo v jakýchkoli vnořených adresářích.

4. Soubor `template.js`

Soubor `template.js` je podobný `layout.js`, ale vytváří novou instanci komponenty pro každou podřízenou trasu. To je užitečné pro scénáře, kde chcete zachovat stav komponenty nebo zabránit opětovnému vykreslování při navigaci mezi podřízenými trasami. Na rozdíl od layoutů se šablony (templates) při navigaci znovu vykreslí. Použití šablon je skvělé pro animaci prvků při navigaci.

Příklad:

// app/template.js
'use client'

import { useState } from 'react'

export default function Template({ children }) {
  const [count, setCount] = useState(0)

  return (
    <main>
      <p>Šablona: {count}</p>
      <button onClick={() => setCount(count + 1)}>Aktualizovat šablonu</button>
      {children}
    </main>
  )
}

5. Soubor `loading.js`

Soubor `loading.js` (nebo `loading.jsx`, `loading.ts`, `loading.tsx`) umožňuje vytvořit UI pro načítání, které se zobrazí, zatímco se segment trasy načítá. To je užitečné pro poskytnutí lepšího uživatelského zážitku při načítání dat nebo provádění jiných asynchronních operací.

Příklad:

// app/about/loading.js
import React from 'react';

export default function Loading() {
  return <p>Načítání informací o nás...</p>;
}

Když uživatel přejde na `/about`, komponenta `Loading` se zobrazí, dokud nebude komponenta `page.js` plně vykreslena.

6. Soubor `error.js`

Soubor `error.js` (nebo `error.jsx`, `error.ts`, `error.tsx`) umožňuje vytvořit vlastní chybové UI, které se zobrazí, když v segmentu trasy dojde k chybě. To je užitečné pro poskytnutí uživatelsky přívětivější chybové zprávy a zabránění selhání celé aplikace.

Příklad:

// app/about/error.js
'use client'

import React from 'react';

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Došlo k chybě!</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>Zkusit znovu</button>
    </div>
  );
}

Pokud dojde k chybě při vykreslování stránky `/about`, zobrazí se komponenta `Error`. Vlastnost `error` obsahuje informace o chybě a funkce `reset` umožňuje uživateli pokusit se stránku znovu načíst.

7. Skupiny tras (Route Groups)

Skupiny tras `(groupName)` vám umožňují organizovat trasy bez ovlivnění struktury URL. Vytvářejí se obalením názvu složky do závorek. To je zvláště užitečné pro organizaci layoutů a sdílených komponent.

Příklad:

app/
  (marketing)/
    about/
      page.js
    contact/
      page.js
  (shop)/
    products/
      page.js

V tomto příkladu jsou stránky `about` a `contact` seskupeny pod skupinou `marketing` a stránka `products` je pod skupinou `shop`. Adresy URL zůstávají `/about`, `/contact` a `/products`.

8. Dynamické trasy

Dynamické trasy vám umožňují vytvářet trasy s proměnnými segmenty. To je užitečné pro zobrazování obsahu na základě dat načtených z databáze nebo API. Segmenty dynamických tras se definují obalením názvu segmentu do hranatých závorek (např. `[id]`).

Příklad:

Řekněme, že chcete vytvořit trasu pro zobrazování jednotlivých blogových příspěvků na základě jejich ID. Můžete vytvořit takovouto strukturu souborů:

app/
  blog/
    [id]/
      page.js

Segment `[id]` je dynamický segment. Komponenta exportovaná ze souboru `app/blog/[id]/page.js` bude vykreslena, když uživatel přejde na URL jako `/blog/123` nebo `/blog/456`. Hodnota parametru `id` bude dostupná ve vlastnosti `params` komponenty.

// app/blog/[id]/page.js
import React from 'react';

export default async function BlogPost({ params }) {
  const { id } = params;

  // Načtení dat pro blogový příspěvek s daným ID
  const post = await fetchBlogPost(id);

  if (!post) {
    return <p>Blogový příspěvek nenalezen.</p>;
  }

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

async function fetchBlogPost(id) {
  // Simulace načítání dat z databáze nebo API
  return new Promise((resolve) => {
    setTimeout(() => {
      const posts = {
        '123': { title: 'Můj první blogový příspěvek', content: 'Toto je obsah mého prvního blogového příspěvku.' },
        '456': { title: 'Další blogový příspěvek', content: 'Toto je další zajímavý obsah.' },
      };
      resolve(posts[id] || null);
    }, 500);
  });
}

Můžete také použít více dynamických segmentů v jedné trase. Například byste mohli mít trasu jako `/blog/[category]/[id]`.

9. Záchytné segmenty (Catch-all Segments)

Záchytné segmenty (catch-all) vám umožňují vytvářet trasy, které odpovídají libovolnému počtu segmentů. To je užitečné pro scénáře, jako je vytváření CMS, kde je struktura URL určena uživatelem. Záchytné segmenty se definují přidáním tří teček před název segmentu (např. `[...slug]`).

Příklad:

app/
  docs/
    [...slug]/
      page.js

Segment `[...slug]` bude odpovídat libovolnému počtu segmentů za `/docs`. Například bude odpovídat `/docs/getting-started`, `/docs/api/users` a `/docs/advanced/configuration`. Hodnota parametru `slug` bude pole obsahující odpovídající segmenty.

// app/docs/[...slug]/page.js
import React from 'react';

export default function DocsPage({ params }) {
  const { slug } = params;

  return (
    <div>
      <h1>Dokumentace</h1>
      <p>Slug: {slug ? slug.join('/') : 'Žádný slug'}</p>
    </div>
  );
}

Volitelné záchytné segmenty lze vytvořit obalením názvu segmentu do dvojitých hranatých závorek `[[...slug]]`. Tím se segment trasy stane volitelným. Příklad:

app/
  blog/
    [[...slug]]/
      page.js

Toto nastavení vykreslí komponentu page.js jak na adrese `/blog`, tak na `/blog/any/number/of/segments`.

10. Paralelní trasy

Paralelní trasy umožňují současně vykreslit jednu nebo více stránek ve stejném layoutu. To je zvláště užitečné pro složité layouty, jako jsou dashboardy, kde lze různé sekce stránky načítat nezávisle. Paralelní trasy se definují pomocí symbolu `@` následovaného názvem slotu (např. `@sidebar`, `@main`).

Příklad:

app/
  @sidebar/
    page.js  // Obsah pro postranní panel
  @main/
    page.js  // Obsah pro hlavní sekci
  default.js // Povinné: Definuje výchozí layout pro paralelní trasy

Soubor `default.js` je při použití paralelních tras povinný. Definuje, jak jsou různé sloty kombinovány do finálního layoutu.

// app/default.js
export default function RootLayout({ children: { sidebar, main } }) {
  return (
    <div style={{ display: 'flex' }}>
      <aside style={{ width: '200px', backgroundColor: '#f0f0f0' }}>
        {sidebar}
      </aside>
      <main style={{ flex: 1, padding: '20px' }}>
        {main}
      </main>
    </div>
  );
}

11. Přesměrovávací trasy (Intercepting Routes)

Přesměrovávací trasy (Intercepting Routes) umožňují načíst trasu z jiné části vaší aplikace v rámci aktuálního layoutu. To je užitečné pro vytváření modálních oken, galerií obrázků a dalších prvků UI, které by se měly objevit nad stávajícím obsahem stránky. Přesměrovávací trasy se definují pomocí syntaxe `(..)`, která udává, o kolik úrovní výše ve stromu adresářů se má hledat přesměrovaná trasa.

Příklad:

app/
  (.)photos/
    [id]/
      page.js  // Přesměrovaná trasa
  feed/
    page.js  // Stránka, kde se zobrazuje modální okno s fotkou

V tomto příkladu, když uživatel klikne na fotografii na stránce `/feed`, trasa `app/(.)photos/[id]/page.js` je přesměrována a zobrazena jako modální okno nad stránkou `/feed`. Syntaxe `(.)` říká Next.js, aby se podíval o jednu úroveň výše (do adresáře `app`) a našel trasu `photos/[id]`.

Načítání dat s App Routerem

App Router poskytuje vestavěnou podporu pro načítání dat pomocí Serverových komponent (Server Components) a Klientských komponent (Client Components). Serverové komponenty se vykreslují na serveru, zatímco Klientské komponenty se vykreslují na klientovi. To vám umožňuje zvolit nejlepší přístup pro každou komponentu na základě jejích požadavků.

Serverové komponenty

Serverové komponenty jsou v App Routeru výchozí. Umožňují vám načítat data přímo ve vašich komponentách bez potřeby samostatných API tras. To může zlepšit výkon a zjednodušit váš kód.

Příklad:

// app/products/page.js
import React from 'react';

export default async function ProductsPage() {
  const products = await fetchProducts();

  return (
    <div>
      <h1>Produkty</h1>
      <ul>
        {products.map((product) => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </div>
  );
}

async function fetchProducts() {
  // Simulace načítání dat z databáze nebo API
  return new Promise((resolve) => {
    setTimeout(() => {
      const products = [
        { id: 1, name: 'Produkt A' },
        { id: 2, name: 'Produkt B' },
        { id: 3, name: 'Produkt C' },
      ];
      resolve(products);
    }, 500);
  });
}

V tomto příkladu je funkce `fetchProducts` volána přímo v komponentě `ProductsPage`. Komponenta je vykreslena na serveru a data jsou načtena před odesláním HTML klientovi.

Klientské komponenty

Klientské komponenty se vykreslují na klientovi a umožňují vám používat klientské funkce jako jsou posluchače událostí, stav a API prohlížeče. Chcete-li použít Klientskou komponentu, musíte na začátek souboru přidat direktivu `'use client'`.

Příklad:

// app/counter/page.js
'use client'

import React, { useState } from 'react';

export default function CounterPage() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Počítadlo</h1>
      <p>Počet: {count}</p>
      <button onClick={() => setCount(count + 1)}>Zvýšit</button>
    </div>
  );
}

V tomto příkladu je komponenta `CounterPage` Klientskou komponentou, protože používá hook `useState`. Direktiva `'use client'` říká Next.js, aby tuto komponentu vykreslil na klientovi.

Pokročilé techniky směrování

App Router nabízí několik pokročilých technik směrování, které lze použít k vytváření složitých a sofistikovaných aplikací.

1. Route Handlers (Obsluha tras)

Route Handlers vám umožňují vytvářet API koncové body přímo v adresáři `app`. Tím se eliminuje potřeba samostatného adresáře `pages/api`. Route Handlers jsou definovány v souborech s názvem `route.js` (nebo `route.ts`) a exportují funkce, které obsluhují různé HTTP metody (např. `GET`, `POST`, `PUT`, `DELETE`).

Příklad:

// app/api/users/route.js
import { NextResponse } from 'next/server'

export async function GET(request) {
  // Simulace načítání uživatelů z databáze
  const users = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Doe' },
  ];

  return NextResponse.json(users);
}

export async function POST(request) {
  const body = await request.json()
  console.log('Přijatá data:', body)
  return NextResponse.json({ message: 'Uživatel vytvořen' }, { status: 201 })
}

Tento příklad definuje route handler na `/api/users`, který obsluhuje jak `GET`, tak `POST` požadavky. Funkce `GET` vrací seznam uživatelů a funkce `POST` vytváří nového uživatele.

2. Skupiny tras s více layouty

Můžete kombinovat skupiny tras s layouty a vytvářet tak různé layouty pro různé části vaší aplikace. To je užitečné pro scénáře, kdy chcete mít jinou hlavičku nebo postranní panel pro různé části vašeho webu.

Příklad:

app/
  (marketing)/
    layout.js  // Marketingový layout
    about/
      page.js
    contact/
      page.js
  (admin)/
    layout.js  // Administrátorský layout
    dashboard/
      page.js

V tomto příkladu budou stránky `about` a `contact` používat `marketingový` layout, zatímco stránka `dashboard` bude používat `administrátorský` layout.

3. Middleware

Middleware vám umožňuje spouštět kód předtím, než je požadavek zpracován vaší aplikací. To je užitečné pro úkoly jako je autentizace, autorizace, logování a přesměrování uživatelů na základě jejich polohy nebo zařízení.

Middleware je definován v souboru s názvem `middleware.js` (nebo `middleware.ts`) v kořenovém adresáři vašeho projektu.

Příklad:

// middleware.js
import { NextResponse } from 'next/server'

export function middleware(request) {
  // Zkontrolujte, zda je uživatel ověřen
  const isAuthenticated = false; // Nahraďte vaší logikou ověřování

  if (!isAuthenticated && request.nextUrl.pathname.startsWith('/admin')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

// Více se dozvíte níže v sekci "Matching Paths"
export const config = {
  matcher: '/admin/:path*',
}

Tento příklad definuje middleware, který kontroluje, zda je uživatel ověřen, než mu povolí přístup k jakékoli trase pod `/admin`. Pokud uživatel není ověřen, je přesměrován na stránku `/login`.

Osvědčené postupy pro souborové směrování

Chcete-li co nejlépe využít systém souborového směrování v App Routeru, zvažte následující osvědčené postupy:

Příklady internacionalizace s Next.js App Routerem

Next.js App Router zjednodušuje internacionalizaci (i18n) prostřednictvím souborového směrování. Zde je návod, jak můžete efektivně implementovat i18n:

1. Směrování pomocí podadresářů

Organizujte své trasy na základě lokalizace pomocí podadresářů. Například:

app/
  [locale]/
    page.tsx         // Domovská stránka pro danou lokalizaci
    about/
      page.tsx     // Stránka "O nás" pro danou lokalizaci
// app/[locale]/page.tsx
import { getTranslations } from './dictionaries';

export default async function HomePage({ params: { locale } }) {
  const t = await getTranslations(locale);
  return (<h1>{t.home.title}</h1>);
}

// dictionaries.js
const dictionaries = {
  en: () => import('./dictionaries/en.json').then((module) => module.default),
  es: () => import('./dictionaries/es.json').then((module) => module.default),
};

export const getTranslations = async (locale) => {
  try {
    return dictionaries[locale]() ?? dictionaries.en();
  } catch (error) {
    console.error(`Nepodařilo se načíst překlady pro lokalizaci ${locale}`, error);
    return dictionaries.en();
  }
};

V tomto nastavení dynamický segment trasy `[locale]` zpracovává různé lokalizace (např. `/en`, `/es`). Překlady jsou načítány dynamicky na základě lokalizace.

2. Směrování pomocí domén

Pro pokročilejší přístup můžete použít různé domény nebo subdomény pro každou lokalizaci. To často vyžaduje dodatečnou konfiguraci u vašeho hostingového poskytovatele.

3. Middleware pro detekci lokalizace

Použijte middleware k automatické detekci preferované lokalizace uživatele a k jeho odpovídajícímu přesměrování.

// middleware.js
import { NextResponse } from 'next/server';
import { match } from '@formatjs/intl-localematcher';
import Negotiator from 'negotiator';

let locales = ['en', 'es', 'fr'];

function getLocale(request) {
  const negotiatorHeaders = {};
  request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));
  let languages = new Negotiator({ headers: negotiatorHeaders }).languages();

  try {
      return match(languages, locales, 'en'); // Použít "en" jako výchozí lokalizaci
  } catch (error) {
      console.error("Chyba při párování lokalizace:", error);
      return 'en'; // Záložní řešení na angličtinu, pokud párování selže
  }
}

export function middleware(request) {
  const pathname = request.nextUrl.pathname;
  const pathnameIsMissingLocale = locales.every(
    (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
  );

  if (pathnameIsMissingLocale) {
    const locale = getLocale(request);

    return NextResponse.redirect(
      new URL(
        `/${locale}${pathname.startsWith('/') ? '' : '/'}${pathname}`,
        request.url
      )
    );
  }
}

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

Tento middleware kontroluje, zda požadovaná cesta má prefix lokalizace. Pokud ne, detekuje preferovanou lokalizaci uživatele pomocí hlavičky `Accept-Language` a přesměruje ho na příslušnou cestu specifickou pro danou lokalizaci. Knihovny jako `@formatjs/intl-localematcher` a `negotiator` se používají pro vyjednávání lokalizace.

Next.js App Router a globální přístupnost

Tvorba globálně přístupných webových aplikací vyžaduje pečlivé zvážení principů přístupnosti (a11y). Next.js App Router poskytuje pevný základ pro budování přístupných zážitků, ale je nezbytné implementovat osvědčené postupy, aby vaše aplikace byla použitelná pro všechny, bez ohledu na jejich schopnosti.

Klíčové aspekty přístupnosti

  1. Sémantické HTML: Používejte sémantické HTML prvky (např. `<article>`, `<nav>`, `<aside>`, `<main>`) ke strukturování vašeho obsahu. To dává význam asistenčním technologiím a pomáhá uživatelům snadněji navigovat na vašem webu.
  2. Atributy ARIA: Používejte atributy ARIA (Accessible Rich Internet Applications) k vylepšení přístupnosti vlastních komponent a widgetů. Atributy ARIA poskytují asistenčním technologiím dodatečné informace o roli, stavu a vlastnostech prvků.
  3. Klávesnicová navigace: Zajistěte, aby všechny interaktivní prvky byly přístupné pomocí klávesnice. Uživatelé by měli být schopni procházet vaší aplikací pomocí klávesy `Tab` a interagovat s prvky pomocí kláves `Enter` nebo `Space`.
  4. Barevný kontrast: Používejte dostatečný barevný kontrast mezi textem a pozadím, aby byla zajištěna čitelnost pro uživatele se zrakovým postižením. Směrnice pro přístupnost webového obsahu (WCAG) doporučují kontrastní poměr alespoň 4.5:1 pro normální text a 3:1 pro velký text.
  5. Alternativní text obrázků: Poskytněte popisný alternativní text pro všechny obrázky. Alternativní text poskytuje textovou alternativu pro obrázky, kterou mohou číst čtečky obrazovky.
  6. Popisky formulářů: Propojte popisky formulářů s odpovídajícími vstupními poli pomocí prvku `<label>`. To uživatelům jasně ukazuje, jaké informace se v každém poli očekávají.
  7. Testování pomocí čtečky obrazovky: Otestujte svou aplikaci pomocí čtečky obrazovky, abyste se ujistili, že je přístupná pro uživatele se zrakovým postižením. Mezi populární čtečky obrazovky patří NVDA, JAWS a VoiceOver.

Implementace přístupnosti v Next.js App Routeru

  1. Používejte komponentu Next.js Link: Používejte komponentu `<Link>` pro navigaci. Poskytuje vestavěné funkce přístupnosti, jako je přednačítání a správa fokusu.
  2. Správa fokusu: Při navigaci mezi stránkami nebo otevírání modálních oken zajistěte správnou správu fokusu. Fokus by měl být nastaven na nejlogičtější prvek na nové stránce nebo v modálním okně.
  3. Přístupné vlastní komponenty: Při vytváření vlastních komponent zajistěte jejich přístupnost dodržováním výše uvedených principů. Používejte sémantické HTML, atributy ARIA a klávesnicovou navigaci, aby vaše komponenty byly použitelné pro všechny.
  4. Linting a testování: Používejte nástroje pro linting, jako je ESLint s pluginy pro přístupnost, k identifikaci potenciálních problémů s přístupností ve vašem kódu. Také používejte automatizované testovací nástroje k testování vaší aplikace na porušení přístupnosti.

Závěr

Systém souborového směrování v Next.js App Routeru nabízí výkonný a intuitivní způsob, jak strukturovat a navigovat ve vašich aplikacích. Porozuměním základním konceptům a osvědčeným postupům uvedeným v tomto průvodci můžete vytvářet robustní, škálovatelné a udržovatelné aplikace v Next.js. Experimentujte s různými funkcemi App Routeru a objevte, jak může zjednodušit váš vývojový proces a zlepšit uživatelský zážitek.