Next.js 15 Fullstack (App Router + Server Actions) · Lekcja

Warunkowe renderowanie slotów dla pulpitów i kart

Steruj sekcjami pulpitu i widokami kart, mapując segmenty tras na równoległe sloty.

Lekcja 3 z 413 kroki

Warunkowe renderowanie slotów dla pulpitów i kart to bezpłatna lekcja Next.js 15 Fullstack (App Router + Server Actions) na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Next.js 15 Fullstack (App Router + Server Actions), a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Next.js 15 Fullstack (App Router + Server Actions) zawiera 4 lekcji w sumie.

Czym są trasy równoległe?

Next.js 15 wprowadza trasy równoległe — mechanizm układu, który pozwala jednocześnie renderować wiele stron w tym samym układzie, z których każda znajduje się we własnym nazwanym slocie.

Jest to idealne rozwiązanie dla pulpitów, na których niezależne sekcje, takie jak panel analityczny, kanał zespołu i pasek boczny powiadomień, mają wczytywać się równolegle i niezależnie.

  • Sloty definiuje się za pomocą konwencji folderów @slotName wewnątrz segmentu układu
  • Każdy slot jest przekazywany jako właściwość do nadrzędnego pliku layout.tsx
  • Sloty renderują się równolegle — jeden może przesyłać zawartość strumieniowo, podczas gdy inny jest już w pełni gotowy

Sloty można traktować jak nazwane otwory w układzie, które router jednocześnie wypełnia różnymi poddrzewami tras.

Struktura folderów dla slotów równoległych

Aby utworzyć równoległe sloty, umieść foldery @slotName bezpośrednio wewnątrz folderu segmentu trasy. Każdy folder slotu działa jak własne, małe drzewo tras.

Typowy dashboard może wyglądać tak:

  • app/dashboard/layout.tsx — odbiera wszystkie propsy slotów
  • app/dashboard/@analytics/page.tsx — slot analityczny
  • app/dashboard/@team/page.tsx — slot kanału zespołu
  • app/dashboard/@notifications/page.tsx — slot powiadomień

Foldery slotów nie wpływają na adres URL. Adres pozostaje /dashboard, a wszystkie trzy sloty są renderowane jednocześnie.

// File: app/dashboard/layout.tsx
// TypeScript interface showing slot props

interface DashboardLayoutProps {
  children: React.ReactNode;
  analytics: React.ReactNode;
  team: React.ReactNode;
  notifications: React.ReactNode;
}

export default function DashboardLayout({
  children,
  analytics,
  team,
  notifications,
}: DashboardLayoutProps) {
  return (
    <div className="dashboard-grid">
      <main className="col-span-2">{children}</main>
      <aside className="analytics-panel">{analytics}</aside>
      <aside className="team-panel">{team}</aside>
      <aside className="notifications-panel">{notifications}</aside>
    </div>
  );
}

Plik domyślny: obsługa niedopasowanych slotów

Gdy przechodzisz do adresu URL, który nie pasuje do podtrasy slotu, Next.js musi wiedzieć, co wyrenderować w tym slocie. Bez wartości zastępczej cały layout zakończyłby działanie błędem 404.

Rozwiązaniem jest plik default.tsx znajdujący się w każdym folderze slotu. Działa on jako widok zastępczy, gdy slot nie ma aktywnego dopasowania dla bieżącego adresu URL.

  • Umieść plik default.tsx w każdym folderze @slotName
  • Może zwracać null, aby niczego nie renderować, albo szkielet/wartość zastępczą
  • Dzięki temu pozostałe sloty działają nawet wtedy, gdy jeden z nich nie ma aktywnej podtrasy
// File: app/dashboard/@notifications/default.tsx
// Renders nothing when this slot has no active route match

export default function NotificationsDefault() {
  return null;
}

// File: app/dashboard/@analytics/default.tsx
// Could render a skeleton instead
export default function AnalyticsDefault() {
  return (
    <div className="animate-pulse bg-gray-100 rounded-lg h-48" />
  );
}

Warunkowe renderowanie slotów za pomocą Server Components

Komponent layoutu otrzymuje wszystkie sloty jako propsy — oznacza to, że może Pan/Pani zastosować logikę warunkową bezpośrednio w layoucie, aby pokazywać lub ukrywać sloty na podstawie danych po stronie serwera, ról użytkowników albo flag funkcji.

Ponieważ layout.tsx jest domyślnie Server Component, można wykonywać asynchroniczne pobieranie danych bezpośrednio w nim, aby podjąć tę decyzję.

  • Pobrać rolę bieżącego użytkownika z bazy danych lub sesji
  • Warunkowo dołączać albo wykluczać propsy slotów z renderowanego wyniku
  • Nie jest wymagany JavaScript po stronie klienta do sterowania widocznością
// File: app/dashboard/layout.tsx
import { getCurrentUser } from '@/lib/auth';

interface DashboardLayoutProps {
  children: React.ReactNode;
  analytics: React.ReactNode;
  team: React.ReactNode;
}

export default async function DashboardLayout({
  children,
  analytics,
  team,
}: DashboardLayoutProps) {
  const user = await getCurrentUser();
  const isAdmin = user?.role === 'admin';

  return (
    <div className="dashboard-grid">
      <main>{children}</main>
      {isAdmin && <aside>{analytics}</aside>}
      <aside>{team}</aside>
    </div>
  );
}

Budowanie dashboardu z kartami za pomocą równoległych tras

Równoległe trasy są szczególnie przydatne w przypadku interfejsów z kartami. Zamiast używać stanu po stronie klienta do śledzenia aktywnej karty, można przypisać każdą kartę do segmentu URL, dzięki czemu stan karty staje się częścią adresu URL.

Takie podejście zapewnia:

  • Adresy URL, które można udostępniać — użytkownicy mogą dodać konkretną kartę do zakładek
  • Nawigację między kartami za pomocą przycisków wstecz/dalej przeglądarki
  • Niezależne stany ładowania dla każdej karty dzięki Suspense
  • Treść kart renderowaną na serwerze, bez kosztu hydratacji logiki kart

Pasek kart staje się zestawem komponentów <Link> wskazujących różne podtrasy w obrębie slotu.

// File: app/dashboard/@tabs/layout.tsx
// Shared tab navigation rendered inside the @tabs slot

import Link from 'next/link';

interface TabsLayoutProps {
  children: React.ReactNode;
}

export default function TabsLayout({ children }: TabsLayoutProps) {
  return (
    <div>
      <nav className="flex gap-2 border-b mb-4">
        <Link
          href="/dashboard/overview"
          className="tab-link"
        >
          Overview
        </Link>
        <Link
          href="/dashboard/revenue"
          className="tab-link"
        >
          Revenue
        </Link>
        <Link
          href="/dashboard/users"
          className="tab-link"
        >
          Users
        </Link>
      </nav>
      <div className="tab-content">{children}</div>
    </div>
  );
}

Mapowanie segmentów tras na strony kart

Każda karta odpowiada plikowi page.tsx zagnieżdżonemu w slocie @tabs. Segment URL automatycznie staje się selektorem aktywnej karty — nie jest potrzebny stan w JavaScript.

Dla dashboardu pod adresem /dashboard ze slotem @tabs:

  • app/dashboard/@tabs/overview/page.tsx — aktywna pod adresem /dashboard/overview
  • app/dashboard/@tabs/revenue/page.tsx — aktywna pod adresem /dashboard/revenue
  • app/dashboard/@tabs/users/page.tsx — aktywna pod adresem /dashboard/users

Adres URL nadrzędnego layout.tsx pozostaje taki sam (/dashboard/*), a tylko slot @tabs aktualizuje swoją zawartość.

// File: app/dashboard/@tabs/revenue/page.tsx
import { getRevenueData } from '@/lib/data';

export default async function RevenueTab() {
  const data = await getRevenueData();

  return (
    <section>
      <h2 className="text-xl font-semibold mb-4">Revenue Overview</h2>
      <dl className="grid grid-cols-3 gap-4">
        <div>
          <dt className="text-sm text-gray-500">Monthly Recurring</dt>
          <dd className="text-2xl font-bold">
            ${data.mrr.toLocaleString()}
          </dd>
        </div>
        <div>
          <dt className="text-sm text-gray-500">Annual Run Rate</dt>
          <dd className="text-2xl font-bold">
            ${data.arr.toLocaleString()}
          </dd>
        </div>
        <div>
          <dt className="text-sm text-gray-500">Churn Rate</dt>
          <dd className="text-2xl font-bold">{data.churnRate}%</dd>
        </div>
      </dl>
    </section>
  );
}

Wyróżnianie aktywnej karty za pomocą usePathname

Aby wizualnie wskazać aktywną kartę, należy porównać bieżący adres URL z właściwością href każdej karty. Hook usePathname() z next/navigation udostępnia bieżącą ścieżkę po stronie klienta.

Ponieważ pasek kart musi być interaktywny (odczytuje bieżący adres URL), musi być Client Component. Można pozostawić tylko pasek nawigacji jako Client Component, a całą treść kart zachować jako Server Components.

  • Oznaczyć plik nawigacji kart dyrektywą 'use client'
  • Użyć usePathname() do wykrywania aktywnego segmentu
  • Zastosować klasy warunkowe na podstawie dopasowania
'use client';
// File: app/dashboard/@tabs/_components/TabNav.tsx

import Link from 'next/link';
import { usePathname } from 'next/navigation';

const tabs = [
  { href: '/dashboard/overview', label: 'Overview' },
  { href: '/dashboard/revenue', label: 'Revenue' },
  { href: '/dashboard/users', label: 'Users' },
];

export function TabNav() {
  const pathname = usePathname();

  return (
    <nav className="flex gap-1 border-b">
      {tabs.map((tab) => {
        const isActive = pathname === tab.href;
        return (
          <Link
            key={tab.href}
            href={tab.href}
            className={`px-4 py-2 text-sm font-medium ${
              isActive
                ? 'border-b-2 border-blue-600 text-blue-600'
                : 'text-gray-500 hover:text-gray-700'
            }`}
          >
            {tab.label}
          </Link>
        );
      })}
    </nav>
  );
}

Używanie Suspense do niezależnego ładowania slotów

Jedną z największych zalet równoległych slotów jest to, że każdy slot jest przesyłany strumieniowo niezależnie. Poszczególne sloty można opakować w granice <Suspense>, aby wolny slot nie blokował renderowania szybkiego.

Eliminuje to typowy problem dashboardów, w którym jedno wolne wywołanie API zamraża całą stronę.

  • Owinąć każdy slot we własny komponent <Suspense> w layoucie
  • Każdy slot wyświetla własny szkielet ładowania podczas pobierania danych
  • Szybkie sloty pojawiają się natychmiast, a wolne są przesyłane strumieniowo po przygotowaniu
  • Nie jest potrzebny plik loading.tsx — Suspense zapewnia szczegółową kontrolę
// File: app/dashboard/layout.tsx
import { Suspense } from 'react';
import { AnalyticsSkeleton } from '@/components/skeletons';

interface DashboardLayoutProps {
  children: React.ReactNode;
  analytics: React.ReactNode;
  team: React.ReactNode;
  notifications: React.ReactNode;
}

export default function DashboardLayout({
  children,
  analytics,
  team,
  notifications,
}: DashboardLayoutProps) {
  return (
    <div className="grid grid-cols-3 gap-4">
      <main className="col-span-2">
        <Suspense fallback={<p>Loading main...</p>}>
          {children}
        </Suspense>
      </main>
      <div className="space-y-4">
        <Suspense fallback={<AnalyticsSkeleton />}>
          {analytics}
        </Suspense>
        <Suspense fallback={<p>Loading team...</p>}>
          {team}
        </Suspense>
        <Suspense fallback={null}>
          {notifications}
        </Suspense>
      </div>
    </div>
  );
}

Warunkowe sloty na podstawie parametrów zapytania

Czasami potrzebna jest kontrola widoczności slotu za pomocą parametrów zapytania, a nie segmentów ścieżki — na przykład przełączanie panelu szczegółów za pomocą ?panel=details.

W Server Components można uzyskać dostęp do parametrów zapytania za pomocą propu searchParams w plikach page.tsx albo użyć useSearchParams() w Client Components.

  • Przekazać searchParams z page.tsx do layoutu albo użyć Server Action
  • W layoucie odbierać parametry zapytania za pośrednictwem warstwy danych strony, a nie bezpośrednio (layouty nie otrzymują searchParams)
  • Czystszy wzorzec: użyć własnego page.tsx slotu do odczytu searchParams i warunkowego renderowania zawartości
// File: app/dashboard/@details/page.tsx
// Conditionally renders based on ?panel=details

interface DetailsPageProps {
  searchParams: Promise<{ panel?: string }>;
}

export default async function DetailsPanel({
  searchParams,
}: DetailsPageProps) {
  const { panel } = await searchParams;

  if (panel !== 'details') {
    return null;
  }

  return (
    <aside className="border-l pl-4">
      <h3 className="font-semibold">Details Panel</h3>
      <p className="text-sm text-gray-600">
        Expanded details visible when ?panel=details is present.
      </p>
    </aside>
  );
}

Łączenie równoległych tras z Server Actions do obsługi stanu kart

Server Actions naturalnie współpracują z kartami opartymi na równoległych trasach. Można użyć Server Action do bezpośredniej modyfikacji danych na stronie karty, a następnie polegać na Next.js, który ponownie zweryfikuje dane i wyrenderuje ponownie tylko dotknięty slot — nie całą stronę.

Zapewnia to płynny UX: użytkownik przesyła formularz w karcie, akcja jest wykonywana na serwerze, slot ponownie przesyła strumieniowo świeże dane, a wszystkie pozostałe sloty pozostają bez zmian.

  • Zdefiniować Server Action za pomocą 'use server' w osobnym pliku albo w kodzie inline
  • Wywołać revalidatePath lub revalidateTag, aby unieważnić pamięć podręczną danych
  • Ponownie pobierany i renderowany jest tylko odpowiedni slot
// File: app/dashboard/@tabs/users/actions.ts
'use server';

import { revalidatePath } from 'next/cache';
import { db } from '@/lib/db';

export async function deactivateUser(userId: string): Promise<void> {
  await db.user.update({
    where: { id: userId },
    data: { isActive: false },
  });

  // Only the users tab data is invalidated
  revalidatePath('/dashboard/users');
}

// File: app/dashboard/@tabs/users/page.tsx (partial)
// import { deactivateUser } from './actions';
//
// <form action={deactivateUser.bind(null, user.id)}>
//   <button type="submit">Deactivate</button>
// </form>

Miękka nawigacja i zachowywanie stanu slotów

Podczas przechodzenia między kartami lub sekcjami dashboardu za pomocą Next.js <Link> router wykonuje miękką nawigację. Oznacza to, że:

  • Ponownie pobierane są tylko sloty, których segment trasy uległ zmianie
  • Sloty z niezmienionymi trasami zachowują istniejący wyrenderowany wynik i stan Reacta
  • Pozycja przewijania w niezmienionych slotach zostaje zachowana

To kluczowa przewaga nad tradycyjnymi implementacjami kart wykorzystującymi stan po stronie klienta — otrzymuje się nawigację sterowaną adresem URL bez kosztu ponownego renderowania całego drzewa strony przy każdym przełączeniu karty.

Twarda nawigacja (pełne przeładowanie strony) resetuje wszystkie sloty, dlatego w dashboardzie z równoległymi trasami należy zawsze używać <Link> do przechodzenia między kartami.

Sprawdzenie wiedzy: niedopasowane sloty

Ma Pan/Pani layout dashboardu z trzema równoległymi slotami: @analytics, @team i @notifications. Użytkownik przechodzi do /dashboard/settings, które jest zdefiniowane w children, ale nie ma odpowiadającej podtrasy w żadnym z trzech folderów slotów.

Co dzieje się z trzema slotami i jaki plik zapobiega błędowi 404 dla całego layoutu?

Podsumowanie: warunkowe renderowanie slotów dla dashboardów i kart

W tej lekcji nauczył(a) się Pan/Pani sterować sekcjami dashboardu i widokami z kartami za pomocą Parallel Routes w Next.js 15.

Najważniejsze informacje:

  • Równoległe sloty korzystają z konwencji folderów @slotName i są przekazywane jako propsy do nadrzędnego layout.tsx
  • Plik default.tsx w każdym folderze slotu jest wymagany do obsługi niedopasowanych adresów URL i zapobiegania błędom 404
  • Layout jest Server Component, więc można uzależnić renderowanie slotów od asynchronicznego sprawdzania ról, flag funkcji lub dowolnych danych po stronie serwera
  • Interfejsy z kartami mapują każdą kartę na podsegment URL w obrębie slotu, zapewniając udostępnialne, możliwe do dodania do zakładek karty renderowane na serwerze, bez narzutu stanu po stronie klienta
  • Każdy slot należy umieścić we własnej granicy <Suspense>, aby wolne sloty nie blokowały przesyłania strumieniowego szybkich slotów
  • Server Actions na stronach kart mogą wywoływać revalidatePath, aby odświeżać tylko dotknięty slot, pozostawiając wszystkie pozostałe bez zmian
  • Miękka nawigacja za pomocą <Link> ponownie pobiera tylko zmienione sloty i zachowuje stan Reacta w slotach, które się nie zmieniły

Parallel Routes z warunkową logiką slotów zapewniają kompozytowalną architekturę sterowaną adresem URL dla złożonych dashboardów — bez użycia choćby jednej zmiennej stanu po stronie klienta do zarządzania kartami.

Bezpłatny start

Ucz się TypeScript dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
22
Lekcje
88

Często zadawane pytania

Czy lekcja „Warunkowe renderowanie slotów dla pulpitów i kart” jest bezpłatna?

Tak — pełny tekst „Warunkowe renderowanie slotów dla pulpitów i kart” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Next.js 15 Fullstack (App Router + Server Actions), przejdź na CoddyKit PRO. Kurs Next.js 15 Fullstack (App Router + Server Actions) zawiera 4 lekcji w sumie.

Co nauczysz się w „Warunkowe renderowanie slotów dla pulpitów i kart”?

Steruj sekcjami pulpitu i widokami kart, mapując segmenty tras na równoległe sloty. Ćwiczysz Next.js 15 Fullstack (App Router + Server Actions) z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Next.js 15 Fullstack (App Router + Server Actions)?

Nie wymagamy żadnego doświadczenia. Next.js 15 Fullstack (App Router + Server Actions) w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Warunkowe renderowanie slotów dla pulpitów i kart”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Next.js 15 Fullstack (App Router + Server Actions)?

Tak. Każda lekcja Next.js 15 Fullstack (App Router + Server Actions) zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Równoległe trasy z nazwanymi slotami i domyślnymi segmentami
  2. Trasy przechwytujące dla współdzielonych widoków modalnych
  3. Warunkowe renderowanie slotów dla pulpitów i kart
  4. Budowanie przepływu modalu zdjęć z miękką nawigacją
← Powrót do Next.js 15 Fullstack (App Router + Server Actions)