Learn Nuxt Icon

Nuxt Rendering Modes - Everything you need to know

Since its inception, Nuxt has boasted the concept of being a 'universal' Vue-based web framework. In this article we'll take a look at what that means and the options available to us.

What is a rendering mode in Nuxt?

At its surface, the phrase 'rendering mode' seems a bit more complicated than it is. It's not some sort of graphics, 2D or 3D configuration. Instead it's the way your app code is built and delivered to your users.

Think about it like this:

Your application pages exist in 4 environments:

  1. On your machine at the time of coding / writing.
  2. On a server at the time that you deploy it.
  3. On your users' machine at the time that they request it.
  4. In a cache either on a CDN or within your users' browsers.

With these 4 options, we create a web of possibilities on how we want to deliver the content to the user. We'll look at each of the rendering mode options in the context of these 4 environments and why we might use them.

Server-Side Rendering (SSR) in Nuxt

SSR is a rendering mode that you can think of your code acting more like an 'engine'. When you are developing the application / site - you're writing the logic that you expect the engine to render at the time that the user's request hits the server. This means that you will need a server or function that is continuously listening for requests.

Once that request hits your server, for example to GET /home, your built code will look at the path, method, headers and body of your request and find the page within pages/home.vue and 'render' the HTML, CSS and JS that needs to be sent back to the user's browser.

SSR at build time

When developing, the following script in your package.json will build your SSR app:

package.json


{
  "scripts": {
    ...
    "build": "nuxt build"
    ...
  }
}

At this point, Nuxt will generate the HTTP handlers for your deployment target. It looks at your server/api and pages/ to determine what routes need to be listening. In a Node environment this will get built into the .output/server/index.mjs file.

SSR at server time

The platform that is hosting your code should start this server with the node .output/server/index.mjs command (in Node environments). As soon as a request hits this server - Nuxt will generate the HTML, CSS and JS (as far as possible). It looks at the request context and your page's <script> content to figure out what requests and data fetching needs to occur to generate the HTML response.

SSR effects on your user environment

The response that is received on your user's machine will be a server-generated text/html response. This means that the content within the response body is not necessarily a static HTML file that you have stored on your server's physical disk.

When the server-side HTML is rendered within your user's browser - the 'client' side code will take over, Vue will look at the data that needs to be reactive, event listeners, lifecycle hooks (more on this in an upcoming article) and make your app interactive within your user's browser. This process is called hydration.

SSR and caching

Like any HTTP request, SSR content can be cached within your user's browser using the Cache-Control header. Which means that while the max-age time within the header has not expired, the browser does not need to re-request your server to generate the HTML, CSS and JS again. This helps keep resource usage on your server low.

Client-Side Rendering (CSR) in Nuxt

For a long time CSR was all the hype. Single Page Applications (SPA's) leveraged this paradigm. With CSR you have the option of making it full static (we'll discuss this soon) or still use a server to respond with the bare-bones HTML file i.e. there is no code being generated on the server side at the time of the request.

Vue.js itself uses a CSR paradigm. The code you write and build generates all the necessary JavaScript that needs to fire within your user's browser, which will dynamically create the HTML elements, add event listeners etc.

The downside here is that this JS needs to execute every time the page loads within the browser the first time and typically will take more time / memory than just reading and displaying pre-generated HTML and hydrating what needs to be.

The benefit of CSR is that once that initial JS finishes execution, the app has everything it needs within the browser - which tends to make navigations and interactions feel snappy and native.

CSR at build time

To use CSR with Nuxt, you need to configure it:

nuxt.config.ts


defineNuxtConfig({
  ssr: false,
})

This is essentially the same as just building a Vue application, but you get the niceties of Nuxt's file based routes, optimizations and more. With CSR you don't need to think about wether the page is currently executing 'on the server' or within the user's browser, which makes development a bit less complicated.

It is also recommended that when you use CSR to create a file in the path ./app/spa-loading-template.html. This is content that your Nuxt app will show while the user's browser is busy hydrating the empty HTML that your server sent.

CSR on your server

As mentioned, CSR mode still can run within a server on your hosting platform, but it does not generate HTML on the fly when a request is received. It just sends the empty HTML file with the necessary JS and Vue to execute on your user's browser.

CSR in your user's environment

Your user's browser receives the bare-bones HTML file with all the linked JS and CSS etc. then starts hydrating the app. This means that if your app is being requested by a machine (like Google's Bot), any content that is supposed to be generated in the browser won't be seen. That is a downside for e.g. blog apps, because you have less control over SEO.

CSR and caching

When you cache the CSR app's assets, it is only caching the base HTML, JS and CSS. It does not remember what was generated when the content arrived the first time. This means your code will have to re-execute every time the user loads the app. Caching does not help with the heavy JS operations within the browser, it just avoids having to re-request the initial file.

Static Rendering in Nuxt

Nuxt also allows you to write the code for your app and build it into static files then deploy. To enable static rendering, simply use the generate script instead of the build script. Nuxt will crawl all your <nuxt-link> elements too and smartly generate those pages. The downside is that this mode requires you to manually rebuild your site and redeploy to get fresh content live.

Static Nuxt websites at build time

When you use generate to build your app. All the code is executed on your machine to generate the HTML, CSS and JS for each page. This rendering mode does not require a running server for you to deploy. You can simply upload all the files in the .output/public folder to a static file server.

Static Nuxt websites on the server

As mentioned you don't need a constant HTTP listener for static sites, you only need a running static file server. When a request arrives to your static file server, it simply looks for the path of the request and responds with a physical file at that path.

Static Nuxt websites in your user's environment

Once the static file server sends the files to your user, the hydration process will still take place to make your app / site interactive. If you are building a Single Page App experience, you can use the CSR config in combination with the Nuxt Generate command:

nuxt.config.ts


defineNuxtConfig({
  ssr: false,
})

Static Nuxt websites and caching

Caching is beneficial for Nuxt sites because you're always receiving static files. However, you might not want to indefinitely cache content on your user's browser. For instance, if you edit a page, rebuild and redeploy - you would want your user's browser to receive this new updated content when they request the page again.

Hybrid Rendering in Nuxt

In the next post we'll take a look at Route Rules, they're necessary if you want to leverage some of the hybrid rendering concepts.

Incremental Static Regeneration (ISR) in Nuxt

Within route rules you can specify a glob pattern of routes that need to use ISR as follows:

nuxt.config.ts


export default defineNuxtConfig({
  routeRules: {
    // all routes (by default) will be revalidated every 60 seconds, in the background
    '/**': { isr: 60 },
    // this page will be generated on demand and then cached permanently
    '/static': { isr: true },
  }
})

ISR is a concept that leverages the combination of SRR, static and caching. When you first deploy your site - the content will be generated and sent to the user on request. The 60 value in the code above adds necessary headers to your requests that tells the user's browser to cache the response contents for that amount of seconds. Once that time lapses - the browser will make a request to the server again, which regenerates the content and caches it again.

On certain platforms like Vercel this caching does not only occur within the user's browser, but also on the platform's edge network. The edge network is basically just a bunch of small servers all around the world that can keep copies of your initial response (according to the 60s). This means that your content is closer to your users.

Stale-while-revalidate (SWR) in Nuxt

SWR is much like ISR, except it always serves the latest cached content and always revalidates in the background. Upon the next request to the server the user will receive the latest rebuilt content. SWR does not provide the CDN caching on Vercel like ISR.

Why use ISR / SWR?

Firstly, the cache times with ISR / SWR are usually quite short. Your user should not see stale content for too long. The benefit of ISR comes with for example ecommerce pages with 100,000+ products. Generating these at build time would take multiple minutes / hours. Also if they were full SSR only then your server would have to regenerate that content on EVERY request, which is inefficient.

With ISR, you can leverage the cache to reduce load on your server and also avoid having to pregenerate thousands of pages. While the user is busy navigating they will still get a good / speedy experience and might only get a bit of a slower response every so often if they request a page that has not been cached on the CDN or their browser yet.

What should you choose

It's all dependent on what type of app you're building. Blogs, e-commerce sites, content pages etc. benefit from being pre-generated because of SEO and because the pages' content does not change that often. If you're fine with building these sites manually and redeploying (or using a CI environment to do it for you) then using Static mode (nuxt generate) is fine.

If you have a site that needs SEO and has content that changes relatively regularly, like a frequently updated blog, or help docs, you might benefit more from an SSR experience. This is also good for your user's browser performance, but adds strain to your server.

If you have a site with thousands of pages, you might consider leveraging SWR/ISR to reduce the need to build thousands of pages and keep the load on your server down. You just need to adjust the cache lifetime based on how frequently your content updates.

CSR is a good option for something like a small SaaS / Single Page App. Where you get an app-like experience. Clicking links and jumping around in the app does not cause a new request to the server, instead everything is replaced client-side. This causes some more strain on the loading time of your app, but with a loading template your users might understand it once they experience the snapiness post-load.

Full static / no hydration

Nuxt has recently come out with more concepts like island or server components and experimentalNoScripts which are concepts that try to reduce JS delivered to your user even more. For example. If you have a product page with almost no interactivity. Is it really necessary to load the Vue library and execute hydration. This development is great and you can read more about it on Daniel's blog.

Route Rules

For a large project, usually there are sections that are static content, sections that are app-like, sections that generate tons of pages. This is where Route Rules come in, you can build a full hybrid experience within one app. In the next article, we'll look at these in more detail with an example app.