Page speed matters. Faster website results in better UX, better SEO, and more profit. The latest research done by Rekuten 24 shows that optimizing Core Web Vitals leads to:
53.37% in revenue per visitor.
33.13% in conversion rate.
15.20% in average order value.
9.99% in average time spent.
A 35.12% reduction in exit rate.
The modern frameworks and frontend libraries address speed and help developers ship better user experiences to the users. We’ll be discussing two of the modern frameworks: Qwik and Next.js. We’ll see an experiment benchmark and look into how each framework achieves optimal performance.
Let’s go.
I built two identical applications with Qwik and Next.js and measured the performances. The demo look like this:
You can find the demo on GitHub. Feel free to take a look at the repo and try it out✨
The application lets users enter prompts to DALL·E, an AI image generator, and displays the generated AI images in the page. It also displays the latest Twitter feed about DALL·E.
The key features are:
Here’s the core Web Vitals by Lighthouse for Qwik:
Comparing to Next.js’s Web Vitals:
We can observe the following:
Let’s take a closer look at how the application in each framework is set up.
The component structure:
// 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>
)
})
When the Qwik server receives a page request, it starts the rendering process on the server. The “useEndPoint” function invokes the “onGet” function on the server and fetches the Twitter feed. The “Resource” component will pause the rendering until the Twitter data is resolved or rejected. Once the rendering is completed, the server responds to the client with rendered HTML.
We can observe the server-side rendering behavior in the performance profile:
The lead time to respond to the client is blocked by the server side data fetching and component rendering.
The component structure:
// 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>
)
}
The “Page” component is a server component. It looks just like a normal component but it supports async/await.
When the Next.js server receives a page request, it starts the rendering process and streams the rendering result to the client so that the client can progressively render and display the UI to the users. While the server is fetching Twitter feeds, React renders the suspense placeholder to indicate the pending state. Once the data is resolved or rejected, React reveals the suspense boundary and displays the final UI.
You can see the client starts rendering while the server is streaming. It reduces the lead time for the users to start seeing the page.
Qwik embraces the “Get HTML and render” mental model.
Resumability is Qwik’s innovation. It allows an application to be rendered as much as possible on the server and resumes the rest of the rendering on the client.
The framework looks like this:
When receiving a request, the Qwik server starts the rendering process and generates
The server then responds to the client with the page HTML and the serialized state.
On the client, the browser processes the critical rendering path and displays the UI. Qwik deserializes the state after the page HTML is loaded. The state contains all the local states of each component. The lazy-loaded components are able to be dynamically imported independently without knowing the parent’s state because they can refer to the deserialized state for their local states when they are rendering on the client.
Qwik also serializes event handlers.
In the server-generated HTML, the event handlers are referenced as dynamic JavaScript chunks. When users interact with an interactive element, Qwik uses the reference to dynamically download the chunk from the server and fire the event with the event handler inside the chunk.
On the other hand, Next.js has a different approach to server-side rendering.
Next.js and React server component promotes the “Render while fetching” mental model.
Next.js 13 introduces React server component as an experimental feature. Server component is a special type of component that can only be rendered on the server. Combining with Suspense and Streaming, the framework is able to progressively render interactive UI while avoiding long data requests from blocking the page rendering.
The framework looks like this:
When Next.js server receives a request, it delegates React to handle rendering. On the server, React renders the component tree into a UI description in JSON instead of native HTML. The UI description describes the entire component tree. For the client components, React describes them as components for the client to handle with serialized props. For the server components, React renders them to native HTMLs and places them in the UI description.
On the client, React receives and reconciles the UI description in the response stream to progressively render the UI. When React sees a suspense boundary, it renders the suspense placeholder until the pending data is resolved and reveals the suspense boundary.
Because React receives a UI description instead of native HTML on the client, it needs to construct the component tree, render the UI, and attach event handlers on the client. It’s known as hydration.
Qwik’s concept of serializing states and event handlers is very innovative. The client is able to render the page with just HTML and minimal JavaScript. It reduces the amount of JavaScript the client needs to download for the initial load and leverages dynamic import to download event handlers and components on the fly. We can clearly observe the benefit from the benchmark and performance profile.
However, the combination of React server component, suspense, and streaming has a profound impact on user experience. Users are able to see and interact with the content of the page without waiting for server-side data fetching to complete.
The result of the experiment is not conclusive to me. Both frameworks performed well in the benchmarks. The differences in First Content Paint, Largest Content Paint, and Cumulative Layout Shift are likely a result of different Twitter images.
Want to Connect? This article was originally posted on__Daw-Chih’s website__.
Also Published Here