Pre-rendering

Pre-render pages at build time for maximum performance and avoid CPU usage on the server.

When using NuxtHub, you need to build your application with the nuxt build in order to keep your server when deploying your application.

However, you can also pre-render your pages at build time to improve performance and avoid CPU usage on the server.

This is possible thanks to Nuxt's Hybrid Rendering to allow different caching rules per route.

Route Rules

Globally

You can define route rules in your nuxt.config.ts to specify how each route should be rendered:

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true }
  }
})

When running nuxt build, Nuxt will pre-render the / route and save the index.html file in the output directory.

When deploying with NuxtHub on Cloudflare Pages, it will serve the pre-rendered HTML file directly from the edge for maximum performance and avoid CPU usage on the server.

Page Level

It is also possible to define route rules at the page level using the defineRouteRules util in the page component:

pages/index.vue
<script setup lang="ts">
defineRouteRules({
  prerender: true
})
</script>

<template>
  <h1>Hello world!</h1>
</template>

This is equivalent of our example above in the nuxt.config.ts file.

Notes:

  • A rule defined in ~/pages/foo/bar.vue will be applied to /foo/bar requests.
  • A rule in ~/pages/foo/[id].vue will be applied to /foo/** requests.
As this is an experimental feature, you need to enable it in your nuxt.config.ts files with experimental: { inlineRouteRules: true }.

Dynamic Pre-rendering

In some cases, you may want to Nuxt to pre-render other pages when pre-rendering a specific page.

This is useful when pre-rendering all the blog posts when pre-rendering the blog index page. To achieve this, we can use the prerenderRoutes util in the page component:

pages/blog/index.vue
<script setup lang="ts">
// Pre-render the /blog page
defineRouteRules({ prerender: true })

const { data: posts } = await useFetch('/api/posts')

// Tell Nuxt to pre-render all blog posts
prerenderRoutes(posts.value.map(post => `/blog/${post.slug}`))
</script>

It is important to tell Nuxt to pre-render the /blog using the defineRouteRules, we can also do it globally in the nuxt.config.ts file.

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // If not using `defineRouteRules` in the page component
    '/blog': { prerender: true }
  }
})

Using a Nuxt Module

You can also use a local Nuxt module to pre-render dynamic pages, which is particularly useful if you don't have a single root page (such as /blog) but still need to pre-render specific routes, such as /page-1, /parent/page-2, and so on.

modules/prerender-routes.ts
import { defineNuxtModule, addPrerenderRoutes } from '@nuxt/kit'

export default defineNuxtModule({
  meta: {
    name: 'nuxt-prerender-routes',
  },
  async setup() {
    const pages = await getDynamicPages()
    addPrerenderRoutes(pages)
  },
})

async function getDynamicPages(): string[] {
  // Replace this function with the logic for retrieving the slugs for your pages.
  return ['/page-1', '/parent/page-2']
}

Pre-render All Pages

To have the same behavior as nuxt generate while keeping the server part, you can pre-render all pages by configuring the nitro.prerender option in the nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    prerender: {
      // Pre-render the homepage
      routes: ['/'],
      // Then crawl all the links on the page
      crawlLinks: true
    }
  }
})

When running nuxt build, Nuxt will pre-render all pages and save the index.html file in the dist/ directory.

Learn more about Nuxt prerendering.

Cloudflare 100 routes limit

NuxtHub will generate a dist/_routes.json for Cloudflare Pages, but it has a limit of 100 excluded routes (used for static assets).

As each pre-rendered page will be added to the exclude list, we recommend to add your known pre-rendered pattern in the nitro.cloudflare.pages.routes.exclude option:

nuxt.config.ts
export default defineNuxtConfig({
  // ...
  nitro: {
    cloudflare: {
      pages: {
        routes: {
          exclude: [
            // we know that all docs and blog pages are pre-rendered
            '/docs/*',
            '/blog/*'
          ]
        }
      }
    }
  }
})

Caveats

If you are using authentication in your application such as nuxt-auth-utils, you need to remember that the authentication state will not be available during the pre-rendering process.

For example, if you have a header component that either display the logged in user or a login button, you need to wrap the logic inside the <AuthState> component to display a placeholder while the page is being pre-rendered.

components/AppHeader.vue
<script setup lang="ts">
const { loggedIn, user } = useUserSession()

const links = [
  { label: 'Docs', to: '/docs' },
  { label: 'Blog', to: '/blog' }
]
</script>

<template>
  <UHeader title="ACME" :links="links">
    <template #right>
      <AuthState>
        <UButton v-if="loggedIn" to="/profile">{{ user.name }}</UButton>
        <UButton v-else to="/login">Login</UButton>
        <template #placeholder>
          <!-- Display loading state -->
          <UButton :loading="true" />
        </template>
      </AuthState>
  </template>
</template>