Qwik vs Next.js - porównanie prędkości
Szybkość strony ma znaczenie. Im szybsza strona internetowa tym lepsze UX, lepsze SEO i większy zysk. Badania przeprowadzone przez Rekuten 24 pokazują, że optymalizacja Core Web Vitals prowadzi do:
- 53.37% przychodu na jednego odwiedzającego.
- 33.13% we współczynniku konwersji.
- 15.20% w średniej wartości zamówienia.
- 9.99% średnio spędzonego czasu.
- Zmniejszenie współczynnika wyjść o 35,12%.
Nowoczesne frameworki i biblioteki frontendowe poruszają kwestię szybkości i pomagają programistom w dostarczaniu użytkownikom lepszych doświadczeń. Omówimy dwa z nowoczesnych frameworków: Qwik oraz Next.js. Przyjrzymy się eksperymentalnemu benchmarkowi i sprawdzimy, jak każdy z frameworków osiąga optymalną wydajność.
No to do dzieła!
W eksperymencie udział biorą
- qwik v0.16
- Next.js v13
- React v18
Spójrzmy na wynik
Zbudowałem dwie identyczne aplikacje z Qwik i Next.js i zmierzyłem wydajność. Demo wygląda tak:
Demo znajdziesz na GitHubie. Zapraszamy do zapoznania się z repozytorium i przetestowania✨
Aplikacja pozwala użytkownikom na wprowadzanie podpowiedzi do DALL-E, generatora obrazów AI i wyświetla wygenerowane obrazy na stronie. Wyświetla również najnowszy feed na Twitterze dotyczący DALL-E.
Kluczowe funkcje to:
- Renderowanie aplikacji po stronie serwera.
- Pobieranie wyników obrazu po stronie klienta za pomocą REST API od DALL-E.
- Pobieranie feedu Twittera po stronie serwera za pomocą REST API Twittera.
Oto Core Web Vitals przedstawione przez Lighthouse dla Qwik:
Porównanie do Web Vitals Next.js:
Możemy zaobserwować następujące zjawiska:
- Wskaźnik prędkości Qwik jest o 0,5 s szybszy niż Next.js.
- Time to interactive w Qwik jest o 0,3 s szybszy niż Next.js.
- Next.js to 0 ms całkowitego czasu blokowania, w porównaniu do 160 ms w przypadku Qwik.
- Next.js ma nieco wyższy ogólny wynik wydajności strony o 5 punktów.
- Największe wyrenderowanie treści (LCP) w Next.js jest o 0,8 s szybsze niż w przypadku Qwik.
Przyjrzyjmy się bliżej, jak skonfigurowana jest aplikacja w każdym z frameworków.
Renderowanie po stronie serwera w Qwik
Struktura komponentów:
// qwik/src/routes/index.tsx
export const onGet: RequestHandler<TwitterResponse> = async () => {
const data = await fetchTweets();
return data;
}
export default component$(() => {
const tweets = useEndpoint<TwitterResponse>();
return (
<Layout>
<DallePromptAndResult />
<Resource
value={tweets}
onResolved={(result) => (
<SSRTwitterCarousel data={result} />
)}
/>
<StaticPromptRecommendation />
</Layout>
)
})
Kiedy serwer Qwik otrzymuje żądanie strony, rozpoczyna proces renderowania po stronie serwera. Funkcja „useEndPoint” wywołuje funkcję „onGet” na serwerze i pobiera feed Twittera. Komponent „Resource” wstrzyma renderowanie, dopóki dane z Twittera nie zostaną rozwiązane lub odrzucone. Po zakończeniu renderowania, serwer odpowiada klientowi za pomocą wyrenderowanego HTML.
W profilu wydajności możemy zaobserwować zachowanie renderowania po stronie serwera:
Czas realizacji odpowiedzi dla klienta jest blokowany przez pobieranie danych po stronie serwera i renderowanie komponentów.
Next.js SSR i Streaming
Struktura komponentów:
// next/app/page.tsx
export default async function Page() {
const tweets = await fetchTweets();
return (
<Layout>
<DallePromptAndResult />
<Suspense fallback={<Skeleton />}>
<SSRTwitterCarousel data={tweets} />
</Suspense>
<StaticPromptRecommendation />
</Layout>
)
}
Komponent „Page” jest komponentem serwerowym. Wygląda tak samo jak zwykły komponent, ale obsługuje async/await.
Kiedy serwer Next.js otrzymuje żądanie strony, rozpoczyna proces renderowania i strumieniuje wynik renderowania do klienta, dzięki czemu klient może stopniowo renderować i wyświetlać UI użytkownikom. Gdy serwer pobiera feedy Twittera, React renderuje placeholder suspense, aby wskazać stan oczekujący. Po rozwiązaniu lub odrzuceniu danych, React ujawnia granicę suspensu i wyświetla ostateczny UI.
Można zauważyć, że klient rozpoczyna renderowanie, podczas gdy serwer przesyła strumieniowo. Skraca to czas oczekiwania na rozpoczęcie oglądania strony przez użytkowników.
Wznowienie vs Komponent serwera React
Qwik obejmuje model mentalny „Get HTML and render”.
Wznowienie (resumability) to innowacja w Qwik. Pozwala na renderowanie aplikacji na serwerze i wznawia resztę renderowania na kliencie.
Framework wygląda następująco:
Po otrzymaniu żądania, serwer Qwik rozpoczyna proces renderowania i generuje
- serializowany stan aplikacji,
- serializowaną obsługę zdarzeń,
- komponenty HTML,
- oraz fragmenty kodu komponentów.
Następnie serwer odpowiada klientowi za pomocą HTML strony i serializowanego stanu.
Na kliencie, przeglądarka przetwarza krytyczną ścieżkę renderowania i wyświetla UI. Qwik deserializuje stan po załadowaniu strony HTML. Stan zawiera wszystkie lokalne stany każdego komponentu. Leniwie ładowane komponenty mogą być dynamicznie importowane bez znajomości stanu nadrzędnego, ponieważ mogą odwoływać się do deserializowanego stanu dla swoich lokalnych stanów, gdy są renderowane na kliencie.
Qwik serializuje również obsługę zdarzeń.
W generowanym przez serwer HTML, obsługa zdarzeń jest przywoływana jako dynamiczne fragmenty JavaScriptu. Kiedy użytkownicy wchodzą w interakcję z elementem interaktywnym, Qwik używa referencji do dynamicznego pobrania fragmentu z serwera i odpalenia zdarzenia za pomocą obsługi zdarzeń wewnątrz fragmentu.
Z drugiej jednak strony Next.js ma inne podejście do renderowania po stronie serwera.
Next.js i komponent serwera React promuje model mentalny “Render while fetching” (renderuj podczas pobierania).
Next.js 13 wprowadza eksperymentalnie komponent serwerowy React. Komponent serwerowy to specjalny typ komponentu, który może być renderowany tylko po stronie serwera. W połączeniu z Suspense i Streamingiem, framework jest w stanie stopniowo renderować interaktywne UI, jednocześnie unikając długich żądań danych blokujących renderowanie strony.
Framework wygląda następująco:
Kiedy serwer Next.js otrzymuje żądanie, deleguje Reacta do obsługi renderowania. Po stronie serwera React renderuje drzewo komponentów do opisu UI w formacie JSON zamiast natywnego HTML. Opis UI zawiera opis całego drzewa komponentów. W przypadku komponentów klienta, React opisuje je jako komponenty dla klienta do obsługi za pomocą serializowanych propsów. W przypadku komponentów serwera, React renderuje je do natywnych HTML-ów i umieszcza w opisie UI.
Po stronie klienta React odbiera i uzgadnia opis UI w strumieniu odpowiedzi, aby stopniowo renderować UI. Kiedy React zauważy granicę suspensu, renderuje placeholder suspensu, dopóki dane oczekujące nie zostaną rozwiązane i ujawnią granicę suspensu.
Ponieważ React otrzymuje opis UI zamiast natywnego HTML po stronie klienta, musi skonstruować drzewo komponentów, wyrenderować UI i dołączyć obsługę zdarzeń po stronie klienta. Jest to tak zwana hydratacja.
Podsumowanie
Koncepcja frameworku Qwik polegająca na serializacji stanów i obsłudze zdarzeń jest bardzo innowacyjna. Klient jest w stanie wyrenderować stronę przy użyciu tylko HTML i minimalnej ilości JavaScriptu. Zmniejsza ilość JavaScriptu, którą klient musi pobrać przy pierwszym ładowaniu i na bieżąco wykorzystuje dynamiczny import do pobierania obsługi zdarzeń i komponentów. Możemy wyraźnie zaobserwować korzyści z benchmarku i profilu wydajności.
Jednak połączenie komponentu serwera React, suspensu i streamingu ma głęboki wpływ na doświadczenie użytkownika. Użytkownicy są w stanie zobaczyć i wejść w interakcję z zawartością strony bez czekania na zakończenie pobierania danych po stronie serwera.
Wynik eksperymentu nie jest dla mnie rozstrzygający. Oba frameworki wypadły dobrze w benchmarkach. Różnice w First Content Paint, Largest Content Paint i Cumulative Layout Shift są prawdopodobnie wynikiem różnych obrazów Twittera.
Oryginał tekstu w języku angielskim przeczytasz tutaj.