Understanding Nuxt Rendering Types: CSR, SSR, and SSG
When building web applications with Nuxt.js, understanding different rendering strategies is crucial for optimizing performance and user experience. In this article, we'll explore three main rendering types: Client-Side Rendering (CSR), Server-Side Rendering (SSR), and Static Site Generation (SSG).
Rendering Concepts
Client-Side Rendering (CSR)
Client-Side Rendering means the page is initially rendered in the browser, and data is fetched asynchronously after the page loads. This approach is useful when you need dynamic content that changes frequently or when you want to reduce server load.
Here's a simple CSR example:
What happens:
- When a user navigates to this page, Nuxt sends a minimal HTML shell to the browser
- The browser immediately renders the static content (heading and paragraph)
- The JavaScript executes in the browser, creating a new
Date()object - Vue's reactivity system updates the template, displaying the current date/time
- The entire process happens client-side - no server processing is involved
- The date shown will be the exact moment the JavaScript executes in the user's browser
Key characteristics:
- Page renders immediately in the browser
- Data fetching happens asynchronously on the client
- No server-side data fetching
- Good for highly interactive applications
Server-Side Rendering (SSR)
Server-Side Rendering executes data requests on the server before sending the page to the client. This ensures that users receive a fully rendered page with data already populated.
What happens:
- When a user requests this page, the request hits the Nuxt server
- The server executes the
useAsyncData()function before rendering - The server waits for the 5-second timeout to complete (simulating an API call)
- Once the date is resolved, the server renders the complete HTML with the date already populated
- The fully rendered HTML (including the date) is sent to the client
- The user sees the page with data immediately - no loading state needed
- The console.log runs on the server, so it appears in server logs, not the browser console
- Each page request triggers a new server-side execution, so the date will be different for each request
Key characteristics:
- Data is fetched on the server before rendering
- Fully rendered HTML is sent to the client
- Better for SEO and initial page load
- Requires a Node.js server
Static Site Generation (SSG)
Static Site Generation pre-renders pages at build time. During development, it behaves like SSR, but in production, all pages are generated as static HTML files during the build process.
What happens:
- During the build process (
npm run build), Nuxt executes this page's code - The
useAsyncData()function runs on the build server, waiting for the 5-second timeout - Once the date is resolved, Nuxt renders the complete HTML with the date baked in
- This static HTML file is saved to disk (e.g.,
dist/ssg.html) - In production, when users visit this page, they receive the pre-rendered HTML instantly
- The date shown will be the date from when the build ran, not when the user visits
- In development mode, this behaves like SSR - executing on each request for hot-reload
- No server is needed in production - the static HTML can be served from a CDN
Key characteristics:
- Pages are generated at build time
- No server required in production (can be served from CDN)
- Fastest possible load times
- Best for content that doesn't change frequently
Comparison Table
| Feature | CSR | SSR | SSG |
|---|---|---|---|
| Data Fetching | Client-side | Server-side | Build-time |
| Initial Load | Fast (empty) | Slower (waits for data) | Fastest (pre-rendered) |
| SEO | Poor | Excellent | Excellent |
| Server Required | No | Yes | No (after build) |
| Dynamic Content | Excellent | Good | Limited (Need ISR) |
Implementation: Rendering Product Lists
Let's see how these rendering strategies work in practice by implementing a product listing page.
Client-Side Rendering Implementation
With CSR, the product list is fetched after the page loads in the browser. Users will see a loading state while data is being fetched.
Product List Page:
What happens:
- The page HTML is sent to the browser with
productsinitialized asnull - Vue immediately renders the page, showing "Fetching data on client ..." because
products == nullis true - Once the component mounts in the browser, the
onMounted()hook fires - An HTTP request is made from the browser to
https://fakestoreapi.com/products - While waiting for the response, users see the loading message
- When the API responds,
products.valueis updated with the fetched data - Vue's reactivity system detects the change and re-renders the template
- The loading message disappears, and the product list is displayed
- All of this happens in the user's browser - the server only serves the initial HTML shell
Product Detail Page:
What happens:
- When navigating to
/product/csr/1, Nuxt sends the page HTML to the browser - The
routeobject contains the dynamicidparameter from the URL (e.g.,route.params.id = "1") - Initially,
productisnull, so "Fetching data on client ..." is displayed - The title and description show empty strings due to the
|| ""fallback - After the component mounts,
onMounted()executes in the browser - The route parameter is extracted and used to construct the API URL:
https://fakestoreapi.com/products/1 - The browser makes an HTTP request to fetch the specific product data
- Once the data arrives,
product.valueis updated, triggering a re-render - The loading message disappears, and the product details are displayed
- The optional chaining (
product?.title) safely accesses properties that might not exist yet
Static Site Generation Implementation
With SSG, the product list is fetched at build time, and all pages are pre-rendered as static HTML. This provides the fastest possible load times.
Product List Page:
What happens:
- During the build process, Nuxt executes this page's code before generating static files
- The
useAsyncData()function runs on the build server, making an HTTP request to the API - The API response (product list) is captured and stored
- Nuxt renders the complete HTML with all products already in the markup
- This pre-rendered HTML file is saved to the build output directory
- When users visit this page in production, they receive the static HTML instantly
- No API call happens in the browser - all data is already embedded in the HTML
- Users see the product list immediately with no loading state
- The products shown are from when the build ran, not live data
- If you need fresh data, you'd need to rebuild and redeploy the site
Product Detail Page:
What happens:
- During build, Nuxt needs to generate static pages for all possible product IDs
- Nuxt calls
generateStaticParams()(or similar) to determine which product IDs exist - For each product ID, Nuxt executes this page component with that specific
route.params.id - The
useAsyncData()function runs for each product, fetching data from the API - Each product's data is fetched and rendered into a separate static HTML file (e.g.,
product/ssg/1.html,product/ssg/2.html) - All these static files are generated during the build process
- When a user visits
/product/ssg/1, they receive the pre-rendered HTML for product #1 - The product data is already in the HTML - no client-side API call is needed
- Navigation between products is instant since all pages are pre-generated
- If a new product is added to the API, you must rebuild to include it in the static site
Key Differences in Implementation
Data Fetching
- CSR: Uses
onMounted()hook to fetch data after component mounts - SSG: Uses
useAsyncData()which executes at build time (or during SSR in development)
Loading States
- CSR: Requires explicit loading state handling (
v-if="products == null") - SSG: No loading state needed as data is available immediately
Performance
- CSR: Initial page load is fast, but data fetching adds delay
- SSG: Fastest possible load time as everything is pre-rendered
When to Use Each Strategy
Use CSR when:
- Building highly interactive applications
- Content changes frequently
- SEO is not a priority
- You want to reduce server load
Use SSR when:
- SEO is important
- You need dynamic content that changes per request
- You want fast initial page loads with fresh data
- You have a Node.js server available
Use SSG when:
- Content is relatively static
- You want the fastest possible load times
- You want to host on a CDN without a server
- Build-time data fetching is acceptable
Conclusion
Understanding the differences between CSR, SSR, and SSG is essential for building performant Nuxt.js applications. Each strategy has its place, and the best choice depends on your specific use case, performance requirements, and content update frequency.
By implementing product listing pages with both CSR and SSG, we can see the practical differences in how data is fetched and rendered. Choose the strategy that best fits your application's needs!