Struktura projektu React / Next.js (App Router)

Frontend ZEUS używa Next.js 15 z App Routerem. Spójna struktura katalogów i jasna granica między komponentami serwerowymi a klienckimi to fundament, na którym budujemy resztę.

Struktura katalogów

app/
├── (dashboard)/
│   ├── layout.tsx
│   └── probes/
│       ├── page.tsx          # Server Component (domyślnie)
│       └── probe-table.tsx   # "use client" — interaktywny
├── api/
│   └── probes/route.ts       # Route Handler
├── layout.tsx
└── globals.css
components/                    # współdzielone, reużywalne UI
lib/                           # logika, klienci API, utils
hooks/                         # custom hooks (use client)
types/                         # współdzielone typy

Standard ProfessNet: katalog app/ to wyłącznie routing i kompozycja stron. Reużywalne UI mieszka w components/, logika w lib/. page.tsx nie zawiera ciężkiej logiki — woła funkcje z lib/.

Server vs Client Components

W App Routerze komponenty są domyślnie serwerowe. Renderują się na serwerze, nie trafiają do bundla i mogą czytać dane bezpośrednio.

// app/(dashboard)/probes/page.tsx — Server Component
import { getProbes } from "@/lib/probes";
import { ProbeTable } from "./probe-table";

export default async function ProbesPage() {
  const probes = await getProbes();        // fetch po stronie serwera
  return <ProbeTable probes={probes} />;
}

"use client" dodajemy tylko tam, gdzie potrzeba interaktywności — stanu, eventów, hooków przeglądarki.

// app/(dashboard)/probes/probe-table.tsx
"use client";

import { useState } from "react";
import type { Probe } from "@/types/probe";

export function ProbeTable({ probes }: { probes: Probe[] }) {
  const [filter, setFilter] = useState("");
  const shown = probes.filter((p) => p.host.includes(filter));
  return (
    <>
      <input value={filter} onChange={(e) => setFilter(e.target.value)} />
      {shown.map((p) => (
        <Row key={p.id} probe={p} />
      ))}
    </>
  );
}

Standard ProfessNet: "use client" zjeżdża jak najniżej w drzewie. Nie oznaczaj całej strony jako klienckiej tylko dla jednego przycisku — wydzielisz mały komponent kliencki i resztę zostawiasz na serwerze.

Konwencje nazewnictwa

ElementKonwencjaPrzykład
Komponent (plik)kebab-caseprobe-table.tsx
Komponent (eksport)PascalCaseProbeTable
Hookuse + camelCaseuseStudioData
Typ / interfejsPascalCaseProbe, ScanResult
Pliki specjalne Nextzarezerwowanepage, layout, loading, error

Importy z aliasem

Konfigurujemy alias @/ zamiast względnych ../../../.

{ "compilerOptions": { "paths": { "@/*": ["./*"] } } }
import { ProbeTable } from "@/components/probe-table";   // czytelnie

Wskazówka: pliki loading.tsx i error.tsx w segmencie dają automatycznie stany ładowania i błędu dla całej podstrony — wykorzystuj je zamiast ręcznych spinnerów wszędzie.


Domyślnie serwerowo, klient tylko tam, gdzie trzeba, logika w lib/ — to struktura, która skaluje się razem z konsolą ZEUS i utrzymuje mały bundel po stronie przeglądarki.