How NuxtErrorBoundary Works

Learn how the NuxtErrorBoundary component in Nuxt 3 works under the hood to gracefully handle client-side errors. This deep dive explores its powerful features, source code, and practical use cases to help you write more resilient Vue apps.

Michael Thiessen
Nuxt 3

The Mastering Nuxt FullStack Unleashed Course is here!

Get notified when we release new tutorials, lessons, and other expert Nuxt content.

Click here to view course

One of my favourite components in Nuxt is the NuxtErrorBoundary.

Not because I use it a lot, but because it’s a very useful component that does a lot, but with very few lines of code.

In this article, I want to explore how it works, and maybe by the end you’ll love this component as much as I do.

What NuxtErrorBoundary does

Normally, when you have an error on the client-side, it will bubble up and up until something catches it. If nothing catches it, your entire app crashes and becomes unusable.

We use NuxtErrorBoundary to wrap specific parts of our component tree so that, if there’s an error inside of it, this component will catch it:

<template>
  <NuxtErrorBoundary>
    <template #error="{ error, clearError }">
        <p>Oops, something bad happened!</p>
        <p>{{ error }}</p>
        <button @click="clearError">Reset</button>
    </template>
  
    <ComponentWithErrors />
  </NuxtErrorBoundary>
</template>

We get a few main things from this component:

  • #error named slot that is shown when there is an error
  • error which is passed into the slot
  • clearError which resets the error and tries to re-render the default slot, also passed into the #error slot
  • @error event when it has captured an event

In Nuxt 3.17, the NuxtErrorBoundary component was re-written to be a Single File Component. One of the benefits of this is that we can now access these things through useTemplateRef:

<script setup>
const errorBoundary = useTemplateRef('errorBoundary')

function handleClearError() {
  if (!errorBoundary.value?.error) return
  
  errorBoundary.value?.clearError()
}
</script>

<template>
  <NuxtErrorBoundary ref="errorBoundary">
    <!-- ... -->
  </NuxtErrorBoundary>
</template>

Investigating the source code

We can take a look at the source code directly to see how it does all of this. In fact, it’s short enough that I’ll just paste it here (though it may have changed since I wrote this article):

<template>
  <slot
    v-if="error"
    v-bind="{ error, clearError }"
    name="error"
  />

  <slot
    v-else
    name="default"
  />
</template>

<script setup lang="ts">
import { onErrorCaptured, shallowRef } from 'vue'
import { useNuxtApp } from '../nuxt'
import { onNuxtReady } from '../composables/ready'

defineOptions({
  name: 'NuxtErrorBoundary',
  inheritAttrs: false,
})

const emit = defineEmits<{
  error: [error: Error]
}>()

defineSlots<{
  error(props: { error: Error, clearError: () => void }): any
  default(): any
}>()

const error = shallowRef<Error | null>(null)

function clearError () {
  error.value = null
}

if (import.meta.client) {
  const nuxtApp = useNuxtApp()

  function handleError (...args: Parameters<Parameters<typeof onErrorCaptured<Error>>[0]>) {
    const [err, instance, info] = args

    emit('error', err)

    nuxtApp.hooks.callHook('vue:error', err, instance, info)

    error.value = err
  }

  onErrorCaptured((err, instance, info) => {
    if (!nuxtApp.isHydrating) {
      handleError(err, instance, info)
    } else {
      onNuxtReady(() => handleError(err, instance, info))
    }

    return false
  })
}

defineExpose({ error, clearError })
</script>

Taking it from top to bottom, we have a few different sections:

  • Template definition
  • All the defines: defineOptions / defineEmits / defineSlots
  • Error ref and clearError definitions
  • A big block only executed on the client (this is the key part)
  • A lonely defineExpose at the very bottom

The most important part is the client-side block, so we’ll investigate that first.

Handling errors only on the client

This block is wrapped in if (import.meta.client) so that it will only execute on the client, never on the server:

if (import.meta.client) {
  const nuxtApp = useNuxtApp()

  function handleError (...args: Parameters<Parameters<typeof onErrorCaptured<Error>>[0]>) {
    const [err, instance, info] = args

    emit('error', err)

    nuxtApp.hooks.callHook('vue:error', err, instance, info)

    error.value = err
  }

  onErrorCaptured((err, instance, info) => {
    if (!nuxtApp.isHydrating) {
      handleError(err, instance, info)
    } else {
      onNuxtReady(() => handleError(err, instance, info))
    }

    return false
  })
}

First, we grab the current nuxt app instance with useNuxtApp so we have that handy.

Then, we define a handleError function. It takes in a bunch of arguments (err, instance, info), and then does three things:

  1. Emits the error as an error event
  2. Calls the vue:error Nuxt hook, which is used on both the server and client (you can read more about Nuxt’s hooks here)
  3. Sets the value of the component’s error ref (defined above, not in the snippet shown here)

So, this function notifies everyone else of the error it just captured (through the emit and hook) and then saves the error so it can be rendered.

Next, we see that we have the onErrorCaptured lifecycle method from Vue. This is what hooks into Vue’s error system, and then calls handleError if something happens. It uses the onNuxtReady composable to make sure that we only call handleError once the Nuxt app has fully finished initializing.

Rendering errors and a fallback slot

Once we’ve captured an error, the component switches from rendering the default slot to showing the named error slot:

<template>
  <slot
    v-if="error"
    v-bind="{ error, clearError }"
    name="error"
  />

  <slot
    v-else
    name="default"
  />
</template>

It passes the error ref and clearError function into the slot, so we can access them from there if we need to.

The clearError function is pretty simple:

function clearError () {
  error.value = null
}

All we’re doing here is setting the error ref back to null. Although it might be tempting to do this in your own code to reset errors, it’s safer to call clearError to protect yourself from any future changes to this code.

We can also see that because of the v-if, as soon as the error ref is set back to null, it will re-render whatever is in the default slot. This means that you need to “fix” whatever caused the error before you clear the error, otherwise you’ll find yourself in an infinite loop of errors.

The final details

Here’s the rest of the component that we haven’t yet covered:

import { onErrorCaptured, shallowRef } from 'vue'
import { useNuxtApp } from '../nuxt'
import { onNuxtReady } from '../composables/ready'

defineOptions({
  name: 'NuxtErrorBoundary',
  inheritAttrs: false,
})

const emit = defineEmits<{
  error: [error: Error]
}>()

defineSlots<{
  error(props: { error: Error, clearError: () => void }): any
  default(): any
}>()

// if (import.meta.client) { ... }

defineExpose({ error, clearError })
</script>

We define our emits and our slots for proper typing, and we’re also using defineOptions so that we can set inheritAttrs to false. This prevents any attributes set on the NuxtErrorBoundary component from falling through to the component inside.

Lastly, because the Composition API defaults to keeping everything inside of our component private, we need to use defineExpose so that we can access the error ref and clearError function from outside of the component. We need this so we can access them using useTemplateRef.

Wrapping Up

Now you know how the NuxtErrorBoundary component works!

It’s a short little component, but it does a lot and uses a ton of different features from Vue and Nuxt, which makes it really interesting to go through.

If you haven’t yet, go check out the source on GitHub and read it for yourself, it’s a great way to learn more about Vue and Nuxt!

Michael Thiessen
Michael is a passionate full time Vue.js and Nuxt.js educator. His weekly newsletter is sent to over 11,000 Vue developers, and he has written over a hundred articles for his blog and VueSchool.

Follow MasteringNuxt on