Development

Budowanie nowoczesnych aplikacji w Next.js 15

cyberwolf.studio
Development/11 min read/January 18, 2025
Danylo Kabanov
Danylo Kabanov
Chief Technology Officer

#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.

tsx
1// Server Component — pobiera dane bez wysyłania JS do klienta
2async 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 })
8
9 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}
17
18// Client Component — tylko interaktywne elementy
19"use client"
20
21function AddToCartButton({ productId }: { productId: string }) {
22 const [pending, startTransition] = useTransition()
23
24 return (
25 <button
26 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:

PoziomMechanizmZastosowanie
Request memoizationReact.cache()Deduplikacja zapytań w jednym renderze
Data cachefetch z revalidateAPI zewnętrzne, dane zmieniające się okresowo
Full Route CacheStatic/Dynamic renderingStrony bez personalizacji
Router CacheClient-sideNawigacja między stronami
typescript
1// Rewalidacja czasowa — dane odświeżane co 60 sekund
2async function getProducts() {
3 const res = await fetch("https://api.example.com/products", {
4 next: { revalidate: 60 },
5 })
6 return res.json()
7}
8
9// Rewalidacja na żądanie — po aktualizacji w CMS
10import { revalidateTag } from "next/cache"
11
12async 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:

typescript
1// app/actions/contact.ts
2"use server"
3
4import { z } from "zod"
5
6const contactSchema = z.object({
7 name: z.string().min(2),
8 email: z.string().email(),
9 message: z.string().min(10).max(1000),
10})
11
12export 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 })
18
19 if (!result.success) {
20 return { error: "Nieprawidłowe dane formularza" }
21 }
22
23 await db.contacts.create({ data: result.data })
24 await sendNotificationEmail(result.data)
25
26 return { success: true }
27}

##Optymalizacja wydajności

Nasze standardowe techniki optymalizacji dla każdego projektu Next.js:

###Obrazy

tsx
1import Image from "next/image"
2
3// Automatyczna optymalizacja — WebP/AVIF, lazy loading, responsive
4<Image
5 src="/hero.jpg"
6 alt="Hero"
7 width={1200}
8 height={600}
9 priority // Tylko dla above-the-fold
10 sizes="(max-width: 768px) 100vw, 50vw"
11/>

###Czcionki

typescript
1import { Inter } from "next/font/google"
2
3// Czcionka hostowana lokalnie — zero layout shift
4const inter = Inter({
5 subsets: ["latin", "latin-ext"],
6 display: "swap",
7 variable: "--font-inter",
8})

###Dynamiczny import

tsx
1import dynamic from "next/dynamic"
2
3// Ciężkie komponenty ładowane leniwie
4const Chart = dynamic(() => import("@/components/chart"), {
5 loading: () => <ChartSkeleton />,
6 ssr: false, // Wyłączamy SSR dla komponentów zależnych od window
7})

##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.

Next.jsReactWeb DevelopmentPerformance