#Budowanie nowoczesnych aplikacji w Next.js 15
Next.js ewoluował z prostego frameworka do renderowania React po stronie serwera w pełnoprawną platformę do budowania aplikacji webowych. W CyberWolf.Studio jest to nasz framework pierwszego wyboru — i mamy ku temu solidne powody.
##Dlaczego Next.js?
Po latach pracy z różnymi technologiami, Next.js konsekwentnie zapewnia najlepszy balans między wydajnością, Developer Experience i skalowalnością:
- Server Components — mniejszy bundle JS, szybsze ładowanie
- App Router — intuicyjny routing oparty na strukturze katalogów
- Streaming SSR — progresywne ładowanie bez blokowania UI
- Built-in optymalizacje — obrazy, czcionki, skrypty
##Server Components w praktyce
Server Components to fundamentalna zmiana w sposobie myślenia o architekturze React. Kluczowa zasada: renderuj na serwerze wszystko, co nie wymaga interaktywności.
1// Server Component — pobiera dane bez wysyłania JS do klienta2async function ProductList({ categoryId }: { categoryId: string }) {3 const products = await db.products.findMany({4 where: { categoryId, status: "active" },5 orderBy: { createdAt: "desc" },6 take: 20,7 })89 return (10 <div className="grid grid-cols-3 gap-6">11 {products.map((product) => (12 <ProductCard key={product.id} product={product} />13 ))}14 </div>15 )16}1718// Client Component — tylko interaktywne elementy19"use client"2021function AddToCartButton({ productId }: { productId: string }) {22 const [pending, startTransition] = useTransition()2324 return (25 <button26 onClick={() => startTransition(() => addToCart(productId))}27 disabled={pending}28 >29 {pending ? "Dodawanie..." : "Dodaj do koszyka"}30 </button>31 )32}
###Zasada: minimalizuj granicę klient-serwer
Zamiast oznaczać cały komponent jako "use client", wydzielamy tylko interaktywne fragmenty:
Page (Server)
├── Header (Server)
│ └── SearchBar (Client) — interaktywny input
├── ProductList (Server) — dane z bazy
│ └── ProductCard (Server)
│ └── AddToCartButton (Client) — interakcja
└── Footer (Server)
##Strategie cache'owania
Next.js oferuje kilka poziomów cache'owania. Stosujemy je selektywnie:
| Poziom | Mechanizm | Zastosowanie |
|---|---|---|
| Request memoization | React.cache() | Deduplikacja zapytań w jednym renderze |
| Data cache | fetch z revalidate | API zewnętrzne, dane zmieniające się okresowo |
| Full Route Cache | Static/Dynamic rendering | Strony bez personalizacji |
| Router Cache | Client-side | Nawigacja między stronami |
1// Rewalidacja czasowa — dane odświeżane co 60 sekund2async function getProducts() {3 const res = await fetch("https://api.example.com/products", {4 next: { revalidate: 60 },5 })6 return res.json()7}89// Rewalidacja na żądanie — po aktualizacji w CMS10import { revalidateTag } from "next/cache"1112async function updateProduct(id: string, data: ProductData) {13 await db.products.update({ where: { id }, data })14 revalidateTag("products")15}
##Obsługa formularzy z Server Actions
Server Actions eliminują potrzebę tworzenia osobnych endpointów API dla operacji CRUD:
1// app/actions/contact.ts2"use server"34import { z } from "zod"56const contactSchema = z.object({7 name: z.string().min(2),8 email: z.string().email(),9 message: z.string().min(10).max(1000),10})1112export async function submitContact(formData: FormData) {13 const result = contactSchema.safeParse({14 name: formData.get("name"),15 email: formData.get("email"),16 message: formData.get("message"),17 })1819 if (!result.success) {20 return { error: "Nieprawidłowe dane formularza" }21 }2223 await db.contacts.create({ data: result.data })24 await sendNotificationEmail(result.data)2526 return { success: true }27}
##Optymalizacja wydajności
Nasze standardowe techniki optymalizacji dla każdego projektu Next.js:
###Obrazy
1import Image from "next/image"23// Automatyczna optymalizacja — WebP/AVIF, lazy loading, responsive4<Image5 src="/hero.jpg"6 alt="Hero"7 width={1200}8 height={600}9 priority // Tylko dla above-the-fold10 sizes="(max-width: 768px) 100vw, 50vw"11/>
###Czcionki
1import { Inter } from "next/font/google"23// Czcionka hostowana lokalnie — zero layout shift4const inter = Inter({5 subsets: ["latin", "latin-ext"],6 display: "swap",7 variable: "--font-inter",8})
###Dynamiczny import
1import dynamic from "next/dynamic"23// Ciężkie komponenty ładowane leniwie4const Chart = dynamic(() => import("@/components/chart"), {5 loading: () => <ChartSkeleton />,6 ssr: false, // Wyłączamy SSR dla komponentów zależnych od window7})
##Podsumowanie
Next.js to potężne narzędzie, ale jego skuteczność zależy od umiejętnego wykorzystania dostępnych mechanizmów. Kluczem jest zrozumienie, kiedy renderować na serwerze, kiedy na kliencie i jak efektywnie zarządzać cache'em.
Szukasz zespołu do budowy aplikacji webowej? Napisz do nas — chętnie omówimy Twój projekt.