Have you heard of React Server Component? It’s a newer type of component rendering that has been making a wave in the React community in the past year.
The significant aspect of React Server Component is its server-only rendering. This means it doesn’t necessitate client-side rendering, eliminating the need for hydration. Hydration refers to the process of rendering JavaScript components on the client side. As it involves loading and executing code, hydration can significantly impact the first-load performance of a Server Side Rendered (SSR) webpage.
React Server Component is all about reducing the need for hydration.
Nuxt.js now has something similar called Server-Only Component (or just Server Component). As of Nuxt v3.11, it’s still an experimental feature. But we can try it out and see what it’s all about.
Server-Only Rendering
As it sounds, “Server-only Component” means it only runs and renders on the server, never on the client. It will only send the rendered HTML to the client. The source code of that component is not needed on the client side.
To use this feature, there’s no need to change your code. Just add the extension .server before the .vue extension.
(you can clone this sample app here, it’s from my course Real World Nuxt 3)
You can add the extension to any component or page component that you want to render only on the server.
Pros and Cons
The good thing is that the page will now load faster without an additional hydration step on the front end.
Here’s a performance comparison on loading the page.
Before:
After:
(ignore Idle and Total, they are not relevant to the page’s performance)
Look at the Scripting part (the yellow part), that’s where the hydration takes place. Since there’s no hydration with server-only rendering, there’s less Scripting work needed to be done while loading the page.
Additionally, the source code of the page component will only run on the server, meaning all related dependencies will also remain there. This includes any sensitive logic, like fetch calls to an API server that should be kept private. As long as these are not used in other regular components, they too will stay on the server.
But the tradeoff is that a server-only component can’t be interactive because they are no longer Vue components on the client side. They are just a bunch of HTML at that point.
In practice, we would have to mix and match different types of components to get the desired results.
Mixing Server-Only Components and SSR
Once server-only components are no longer in the experimental stage, an intuitive method should be available to mark a nested component as interactive, tentatively via the nuxt-client attribute. This would allow you to render a page as server-only, while also having the ability to nest interactive components within the page, rendered with classic SSR. This is a top-down approach.
Alternatively, the page can be rendered using traditional Server Side Rendering (SSR), where you can selectively choose which nested components to render in server-only mode. Simply add the .server extension to the components you want to render as server-only. This is the bottom-up approach.
The top-down approach is great for pages with mostly static content, but where there’s a bit of interactive UI.
The bottom-up approach is more suitable when the page frame is highly interactive, but contains a large section of static content. For instance, the main body of a blog article usually displays a significant amount of static content.
As of version 3.11.1, this feature is still under development, so the final developer experience (DX) remains uncertain.
However, the server-only component feature is built on the <NuxtIsland>
component. The DX of using <NuxtIsland>
is more defined, so we’ll discuss that next.
Nuxt Island
Instead of making an entire component server-only, we can render a specific component in the template server-only by using the <NuxtIsland>
component.
For example, with this code (from the sample project):
/pages/posts/[post].vue
<template>
<main>
<template v-if="post">
<h1>
{{ post.title }}
<CategoryLink :category="post.category" />
</h1>
<RenderMarkdown :source="post.content" />
</template>
</main>
</template>
The code is rendering some markdown content using a component called RenderMarkdown
.
We can render it in the server-only mode through <NuxtIsland>
:
/pages/posts/[post].vue
<template>
<main>
<template v-if="post">
<h1>
{{ post.title }}
<CategoryLink :category="post.category" />
</h1>
<NuxtIsland
name="RenderMarkdown"
:props="{ source: post.content }"
/>
</template>
</main>
</template>
RenderMarkdown
has to have the .island.vue extension for this to work.
This approach is more flexible because the rendering of a component is more related to the context in which that component is used rather than the component itself.
In this example, the prop post.content
doesn’t get changed like a state does, so rendering it in server-only would improve performance without sacrificing the functionality of the page
However, there might be another scenario where we use RenderMarkdown
and the prop passed to it is supposed to be reactive. In such cases, rendering RenderMarkdown
server-only may not be optimal. The reactivity of the prop would trigger the server-only component to render again on the server over the network, and have its HTML sent to the frontend, also over the network. This could actually result in degraded performance.
So with <NuxtIsland>
, you have the freedom to choose how a component should be rendered when you’re using it, not when you’re creating it.
Looking Ahead
The Nuxt team has mentioned that v3.11 is likely the last minor update before the next major version. Hopefully, we’ll see server-only component as a stable feature soon in Nuxt v4.
Until then, you can still optimize the performance of your site by utilizing the various rendering modes currently available in Nuxt.js. You can check out the Real World Nuxt 3 course on Vue Mastery to learn all about that.