Prerendering
Learn how to serve dynamic content on prerendered and statically generated pages.
If you prerender your Nuxt site for performance, you might worry that you can no longer run A/B tests, tailor content to each visitor, or update it from your CMS. You can. This guide explains why prerendering and dynamic content seem to be at odds, and shows the two ways to combine them so you can pick the right one for each page.
Static vs. dynamic content
Prerendering compiles your pages into static HTML at build time. That is great for performance, but it also means the HTML is the same for everyone the moment it is generated.
Dynamic content, on the other hand, is decided at request time, whether that is an A/B test variant, content tailored to the visitor, or the latest content from your CMS. A fully prerendered page is frozen by the time the visitor sees it, so on its own it cannot adapt on the fly.
The good news is that you do not have to choose between the two for your whole site. Nuxt supports hybrid rendering, which lets you set a different rendering rule per route, and the SDK ships both server-side and client-side composables. So you keep prerendering everywhere it makes sense and change the approach only for the pages that need dynamic content.
Choosing an approach
There are two ways to serve dynamic content on a page without giving up prerendering across the rest of your site.
| Server-side rendering | Client-side rendering | |
|---|---|---|
| Final content on first load | ||
| No content flicker | ||
| Visible to crawlers | ||
| Served as a static file | ||
| Runs without a server |
For most cases we recommend rendering these pages on the server. The experience is smoother, there is no content flicker, and the content is visible to search engines. Reach for client-side rendering when a page must remain a fully static file, for example when it is served from a CDN with no server at all.
Render on the server
For the pages that need dynamic content, opt out of prerendering with a route rule. They will be rendered on the server per request, so the right content is decided server-side and is already in the first HTML response.
export default defineNuxtConfig({ routeRules: { // Render these pages on the server. '/': {prerender: false}, '/pricing': {prerender: false},
// Prerender everything else for maximum performance. '/**': {prerender: true}, },});On those pages, use the content as usual. The default useContent composable and <Slot> component resolve the content on the server, with no extra setup:
<script setup>const {data} = await useContent('home-hero');</script>
<template> <div v-if="data"> <strong>{{ data.content.title }}</strong> <p>{{ data.content.subtitle }}</p> </div></template>The rest of your site stays prerendered, so only the pages that need it are rendered on the server. If you want to cache those responses, you can combine the route rule with caching options like swr or isr.
Render on the client
When a page must stay prerendered, keep it in the prerendered set and load the content in the browser. The page ships static, and the content updates right after it appears.
export default defineNuxtConfig({ routeRules: { // Keep the page static; the content loads in the browser. '/': {prerender: true}, },});These composables fetch in the browser and always need an initial value, which is rendered into the static HTML and shown to visitors and crawlers until the content loads. Without it, the build fails with a missing slot content error.
<script setup>import {useContent} from '@croct/plug-nuxt/csr';
const {data} = useContent('home-hero', { initial: { title: 'Welcome to Croct!', subtitle: 'The easiest way to personalize your application.', },});</script>
<template> <div v-if="data"> <strong>{{ data.title }}</strong> <p>{{ data.subtitle }}</p> </div></template>Use useContent and <Slot> from @croct/plug-nuxt/csr, not the default ones, which run at build time and would freeze the content for every visitor.