Przejdź do głównej zawartości

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();
}, []);