Komponenty serwerowe w Next.js
W tej części omówię, czym są komponenty serwerowe, jakie mają supermoce i dlaczego uważam, że Fullstack w Next.js jest najfajnieszym sposobem pisania kodu.
W trakcie twojej przygody w Solvro, bardzo możliwym jest to, że musiał*ś zaciągnąć jakieś dane z zewnętrznego API. Prawdopodobnie wtedy dopisał*ś na górze pliku "use server"
. Jednak w komponencie React’owym jest to zbędne. Next.js default’uje się do komponentów serwerowych dla ułatwienia pracy, chyba że dopiszesz mu "use client"
.
Na czym polegają komponenty serwerowe?
Komponenty serwerowe to takie, które są renderowane po stronie serwera. Dzięki temu możemy wykonywać operacje, takie jak pobieranie danych z zewnętrznego API, czy dostęp do bazy danych. To drugie pozwala nam całkowicie wyeliminować potrzebę korzystania z backendu, bo możemy po prostu napisać funkcję serwerową, która zaciąga dane z bazy, przetwarza je i zwraca na przykład w postaci zwykłego obiektu (JSON’a), ale o tym później.
Jak uzywać "use server"
?
Funkcja serwerowa
Funkcja serwerowa to swego rodzaju backend, który nie wymaga ani osobnego backendu, ani nie jest wywoływany na endpoint’cie. Zamiast tego, jest to zwykła funkcja, którą możemy wywołać w komponencie serwerowym. Funkcja serwerowa zwraca dane, które możemy przekazać do komponentu klienckiego. Jedyne co trzeba zrobić, to utworzyć plik z rozszerzeniem .ts poza folderem /app
(najlepiej w folderze /actions
) i oznaczyć go dyrektywą "use server"
. Przykładowa funkcja z jednego z moich projektów:
"use server"
import { prisma } from '@/prisma/prisma';
export async function getBalance(user: string) { const dbQuery = await prisma.user.findFirst({ where: { id: user }, select: { balance: true } })
if(!dbQuery) { return 0 }
return dbQuery.balance})
Trochę się dzieje w tej funkcji, więc pochylmy się chwilę nad nią. Na początku importujemy naszego klienta Prisma, który pozwala nam na dostęp do bazy danych, ale o nim opowiem dokładniej w dalszej części. W funkcji getBalance
zaciągamy z bazy danych saldo użytkownika, a jeżeli go nie ma, zwracamy 0. Na końcu zwracamy saldo użytkownika.
W tym przypadku funkcja zwraca jedną wartość w postaci number
. Jednakże, funkcja serwerowa może zwracać również obiekt, tablicę, czy nawet null
lub undefined
, co raczej nie jest zalecane. Wszystko zależy od tego, co dana funkcja ma robić. Wszystko fajnie, tylko co dalej możemy zrobić z naszą funkcją serwerową?
Komponent serwerowy
Komponent serwerowy ma między innymi możliwość wywołania funkcji serwerowej i przekazania jej wyniku do komponentu klienckiego, czy też uruchamiania asynchronicznych funkcji. Przykład komponentu serwerowego renderującego wynik działania funkcji serwerowej:
import { getBalance } from "@/actions/getBalance";
export async function Page() { const user = await auth(); const balance = await getBalance(user.id); return <div>Saldo użytkownika: {balance}</div>;}
W tym przykładzie wywołujemy funkcję getBalance
, podajemy jej odpowiedni argument, w tym przypadku uuid użytkownika, którego saldo chcemy zaciągnąć i przypisujemy wynik do zmiennej balance
. Następnie renderujemy wynik w komponencie. Z racji, że jesteśmy wewnątrz komponentu serwerowego, możemy używać funkcji asynchronicznych, co jest niemożliwe* w komponencie klienckim.
_* niemożliwe, chyba że użyjemy hooka useEffect
potraktowanego czarną magią widoczną poniżej:
const [balance, setBalance] = useState(0);const { user } = useSession();
async function getTransactionSummaries() { if (!user) return; try { const result = await getBalance(user.id); setBalance(result); } catch (error) { console.error(error); // handle np jakis toast }}
useEffect(() => { void getTransactionSummaries();}, []);