Domů O mně Služby Portfolio Učím Blog Kontakt
← Zpět na učím

11 — Loading UI (loading.js / loading.tsx)

Výuka

Proč to existuje?

Když uživatel přechází na novou stránku nebo se načítají data ze serveru, může to chvíli trvat. Bez loading stavu by uživatel viděl prázdnou stránku nebo "zamrzlou" aplikaci — což vypadá, jako by se něco pokazilo.

Loading UI zobrazuje uživateli vizuální feedback (např. spinner, skeleton, progress bar), že se něco děje a aplikace funguje. To výrazně zlepšuje uživatelský zážitek.

Jak to funguje?

Next.js používá React Suspense pro automatické zobrazení loading stavů. Když vytvoříš soubor loading.js nebo loading.tsx ve složce, Next.js ho automaticky zobrazí, zatímco se načítá stránka nebo data.

Pravidlo:

  • Vytvoř loading.js ve složce → zobrazí se během načítání této stránky a všech podsložek
  • Loading komponenta obaluje page.js pomocí Suspense

Jak probíhá načítání:

  1. Uživatel klikne na odkaz → Next.js začne načítat novou stránku
  2. Zobrazí se loading.js → uživatel vidí, že se něco děje
  3. Stránka se načte → loading.js zmizí a zobrazí se skutečný obsah

K čemu to slouží?

  • Lepší UX — uživatel ví, že se stránka načítá
  • Profesionální dojem — aplikace působí responsivně
  • Automatické — nemusíš manuálně řídit zobrazení/skrytí loaderu
  • Per-route loading — každá sekce může mít vlastní loading UI

JavaScript

// app/blog/loading.js
export default function Loading() {
  return (
    <div style={{ padding: '2rem', textAlign: 'center' }}>
      <div className="spinner">Načítání...</div>
      <p>Připravujeme obsah pro vás</p>
    </div>
  )
}

// app/blog/page.js
export default async function BlogPage() {
  // Simulace načítání dat (např. z API)
  const posts = await fetch('https://api.example.com/posts').then(r => r.json())

  return (
    <div>
      <h1>Blog</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  )
}

Skeleton loading (pokročilé)

// app/products/loading.js
export default function Loading() {
  return (
    <div className="products-grid">
      {[1, 2, 3, 4].map(i => (
        <div key={i} className="skeleton-card">
          <div className="skeleton-image" />
          <div className="skeleton-title" />
          <div className="skeleton-text" />
        </div>
      ))}
    </div>
  )
}

TypeScript

// app/blog/loading.tsx
export default function Loading(): JSX.Element {
  return (
    <div style={{ padding: '2rem', textAlign: 'center' }}>
      <div className="spinner">Načítání...</div>
      <p>Připravujeme obsah pro vás</p>
    </div>
  )
}

// app/blog/page.tsx
interface Post {
  id: number
  title: string
  excerpt: string
}

export default async function BlogPage(): Promise<JSX.Element> {
  const posts: Post[] = await fetch('https://api.example.com/posts')
    .then(r => r.json())

  return (
    <div>
      <h1>Blog</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  )
}

Rozdíl JS vs TS

TypeScript přidává typy pro data (např. Post interface) a návratové typy (JSX.Element, Promise). To zajišťuje, že komponenty vracejí správný typ a data mají očekávanou strukturu.

Důležité poznámky

Kde umístit loading.js?

app/
├─ loading.js          # loading pro celou aplikaci (/)
├─ page.js
├─ blog/
  ├─ loading.js       # loading pro /blog a /blog/*
  └─ page.js
└─ dashboard/
   ├─ loading.js       # loading pro /dashboard a /dashboard/*
   └─ page.js

Vlastní spinner komponenta

// components/Spinner.js
export default function Spinner() {
  return (
    <div className="spinner-container">
      <div className="spinner" />
      <style jsx>{`
        .spinner {
          border: 4px solid #f3f3f3;
          border-top: 4px solid #3498db;
          border-radius: 50%;
          width: 40px;
          height: 40px;
          animation: spin 1s linear infinite;
        }
        @keyframes spin {
          0% { transform: rotate(0deg); }
          100% { transform: rotate(360deg); }
        }
      `}</style>
    </div>
  )
}

// app/loading.js
import Spinner from '@/components/Spinner'

export default function Loading() {
  return <Spinner />
}

Tip

  • Používej skeleton screens místo spinnerů — uživatel vidí aproximaci layoutu, což působí rychleji
  • Loading UI by měl odpovídat designu stránky — stejné barvy, fonty
  • Nezapomeň na accessibility — přidej aria-label="Načítání obsahu" pro screen readery
  • Pro velmi rychlé načítání můžeš přidat malé zpoždění, aby loading neblikl (např. min 200ms)

Kvíz

Kdy se zobrazí obsah souboru loading.js?

Next.js automaticky používá loading.js jako fallback během Suspense boundary. Když se načítá stránka nebo async komponenta, Next.js zobrazí loading UI, dokud se obsah nenačte. Nepotřebuješ psát žádný kód pro zobrazení/skrytí — je to automatické.

🎯 Závěrečný projekt

Po dokončení všech 8 dílů vytvoříte jednoduchou Todо aplikaci v čistém JavaScriptu. Naučíte se, jak aplikovat vše, co jste se naučili, na reálný projekt.

Zobrazit podrobnosti projektu →

Připraveni začít?

Zaregistrujte se a získejte přístup ke všem dílům tohoto seriálu

Kontaktujte mě

Informace o seriálu

Obtížnost

Délka

Cca 480 minut

Počet videí

8 videí + projekty

Certifikát

Po dokončení obdržíte certifikát


Lekce v této sekci


Struktura lekcí (souborový strom)

06. Typescript specifika
  • v přípravě
08. Moduly tridy
  • v přípravě
09. React zaklady
  • v přípravě
10. React hooks
  • v přípravě
12. Nextjs server
  • v přípravě
13. Databaze auth
  • v přípravě
14. Nextjs pokrocile
  • v přípravě