Loading...

Nuxt 3 Hybrid Rendering: Pre-Render Dynamic Routes (SSG)

Last update: 8/31/2024
Title image of the port: Nuxt 3 Hybrid Rendering: Pre-Render Dynamic Routes (SSG)

Introduction

Nuxt 3 offers various rendering modes, such as client-side, universal or hybrid-rendering. For SEO and performance optimization, you might want to pre-render routes during the build process, known as Static Site Generation (SSG). In Nuxt 3, a SSG-like application can be generated by utilizing the universal rendering mode to generate HTML pages while preserving the benefits of a client-side rendered application. This is even possible for dynamic routes (sometimes called dynamic pages). Based on a single page layout, they can be used to create a set of pages populated with content fetched from an API.

If you want to render some routes from your pages directory client-side (e.g., pages/index.vue) but pre-render dynamic routes (e.g., pages/articles/[slug].vue) during the build process (SSG), this can be achieved with the hybrid-rendering mode but requires additional Nuxt configuration.

In This Guide

This guide demonstrates two ways to configure the Nuxt 3 hybrid-rendering mode to pre-render dynamic routes, similar to a static site generator (SSG), while rendering other routes client-side. Specifically, this guide focuses on the Nuxt 3 configuration for pre-rendering dynamic routes. For illustration purposes, it is assumed, that the content for the dynamic routes is fetched from a Strapi 4 headless CMS. Nevertheless, the methods can be applied to similar scenarios.

Setup

  • Nuxt 3.8.2
  • Vite 4.5.0
  • nitro 2.8.0
  • node 20.14.0
  • Strapi 4.15.5
  • Nuxt Strapi Module 1.10.0

Prerequisites

  • Basic understanding of how to use the Nuxt 3 configuration file nuxt.config.ts.
  • Basic understanding of Nuxt 3’s file-based routing.
  • An API endpoint being set up. In this case an API endpoint of a Strapi 4 headless CMS, and the Nuxt 3 Strapi module is used.

Static Site Generation in Nuxt 3

In Nuxt 3, static site generation (SSG) can be achieved with the universal rendering mode. This might be little different to SSG in the context of a static site generator such as Jekyll, but it produces HTML files containing the relevant content important to SEO and application performance, because the HTML is immediately available to the client (the browser). These files can be uploaded to any web server, such as Apache HTTP Server, without running a separate process for the application server-side. The easiest way to generate a static Nuxt 3 application is by executing the npm run generate command. This pre-renders dynamic routes and generates the corresponding HTML files which can then be uploaded to a static hosting service, but it does so for all routes of the application.

To combine pre-rendered and client-side rendered routes, the hybrid-rendering mode offers additional features. However, in contrast to npm run generate, with npm run build, Nitro, the underlying server engine, must be configured to recognize the dynamic routes. Then, with npm run build Nuxt produces HTML files only for the routes defined in nuxt.config.ts.

The configuration depends on the use case. In some cases using the routeRules option in nuxt.config.ts is sufficient (refer to the documentation). However, this might not be enough for dynamic routes whose content is fetched from an API. For example, if you have a dynamic route pages/articles/[slug].vue for different articles of an online shop. In this case, the nitro option from the Nuxt configuration or the nitro:config lifecycle hook might be more suitable.

Method 1: Pre-Rendering Dynamic Routes Using the Nitro Lifecycle Hook

This method consists of two steps and hooks into the lifecycle of the Nitro server engine and extends the list of routes to be pre-rendered during the build process (see the discussion on GitHub). First, it is necessary to fetch the available paths of the dynamic routes from an API (here, a Strapi 4 headless CMS). Second, the list of paths (the routes) is then added in the nitro:config lifecycle hook to instruct Nitro to pre-render them during the build process.

First, add the following async function at the beginning of nuxt.config.ts:

nuxt.config.ts
const getArticlesRoutes = async () => {
  const url = "https://www.example.com";
  const response = await fetch(url + "/api/articles?fields[0]=slug");
  const articles = await response.json();
  return articles.data.map((article) => `/articles/${article.attributes.slug}`);
};

export default defineNuxtConfig({});

This function fetches the slugs of all articles from the Strapi 4 API (line 3), extracts the slug for each article, and returns an array of paths (line 5). The schema can be converted to similar scenarios and is not limited to the Strapi 4 CMS as long as the function returns an array with the necessary paths in the form ["/articles/product-1", "/articles/product-2"].

In the second step, hook into the build process, before initializing Nitro with the nitro:config hook. Therefore, add lines 10 to 16 to the nuxt.config.ts:

nuxt.config.ts
const getArticlesRoutes = async () => {
  const url = "https://www.example.com";
  const response = await fetch(url + "/api/articles?fields[0]=slug");
  const articles = await response.json();
  return articles.data.map((article) => `/articles/${article.attributes.slug}`);
};

export default defineNuxtConfig({
  hooks: {
    async "nitro:config"(nitroConfig) {
      if (nitroConfig.dev && !process.argv?.includes("generate")) { return }
      const routes = await getArticlesRoutes();
      nitroConfig.prerender?.routes?.push(...routes);
    }
  },
});

Explanation:

  • async "nitro:config"(nitroConfig) address the lifecycle hook, to apply the subsequent configuration. The async keyword is necessary because the asynchronous getArticleRoutes() function will be called.
  • if (nitroConfig.dev && !process.argv?.includes("generate")) { return } ensures to limit the configuration to npm run build, since npm run generate already pre-renders all routes. The Nitro configuration object that is given to the function has an attribute that contains the information whether the server is in development mode (nitroConfig.dev). To check if the npm run generate command was executed, the argv interface of the Node.js process module can be used to check if it includes a substring generate (process.argv?.includes("generate")). Only if the process is in the build mode, the function is continued. Otherwise return exits the lifecycle hook.
  • await getArticlesRoutes() calls the asynchronous function to fetch the paths as explained above and assigns it to the routes constant.
  • nitroConfig.prerender?.routes?.push(...routes) adds the fetched routes to the array of routes to be pre-rendered in the Nitro configuration.

Running the build process with npm run build now generates HTML documents for all dynamic routes defined in nitroConfig.prerender.routes;. After the build process, every pre-rendered dynamic route resides in a separate folder in the .output/public/articles/ directory.

Method 2: Enabling Nitro's Dynamic Route Crawling for Pre-Rendering

Instead of specifying which dynamic routes to pre-render, this method configures Nitro to crawl the routes itself. This is done by setting the nitro option in nuxt.config.ts. The following configuration enables the pre-rendering of dynamic routes:

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    prerender: {
      crawlLinks: true,
      routes: ["/"],
      ignore: ["/api", "/feedback"]
    }
  },
}

Explanation:

  • crawlLinks: true instructs Nitro to crawl all links it can find.
  • routes: ["/"] defines the starting point for Nitro to begin crawling. In this example, the home page is the starting point. Nitro will crawl and pre-render all linked pages, including dynamic routes.
  • ignore: ["/api", "/feedback"] tells Nitro to ignore these pages for pre-rendering. Alternatively, you can specify them with the routeRules option, e.g., routeRules: { "/api": { prerender: false }, … }

For more information, please refer to the Nitro documentation

Running the build process with npm run build now generates HTML documents for dynamic routes that Nitro can crawl, except those defined in ignore.

Summary

Nuxt 3's hybrid-rendering mode can be leveraged to pre-render dynamic routes while maintaining client-side rendering for other routes. This can be achieved using either the nitro:config lifecycle hook of the Nitro server engine or the nitro.prerender option available in the Nuxt 3 configuration file. The lifecycle hook specifies which routes to pre-render, while the nitro.prerender option instructs Nitro to crawl available routes based on the document structure.

Report a problem (E-Mail)