🔥 (#161) v-once, global properties, and callOnce in Nuxt

Hey!

April has been a quieter month for me, which is nice after the busyness of the first part of this year.

I'm going to be giving a talk at Vue.JS Live on April 25th, so make sure you don't miss it!

You can check it out here: https://vuejslive.com/

Also, don't forget to check out the latest Deja Vue podcast episode here.

As always, here are your tips.

— Michael

null

🔥 Handle Client-side Errors in Nuxt

Proper error handling is a crucial part of any production app, even if it isn’t the most exciting thing.

Nuxt 3 comes with the NuxtErrorBoundary component which makes handling client-side errors a breeze:

<NuxtErrorBoundary>
<!-- Put components in here -->
</NuxtErrorBoundary>

Use the error named slot and the error object to show an error message to the user:

<template #error="{ error }">
<div>
<p>Oops, it looks like the video player broke :/</p>
<p>{{ error.message }}</p>
</div>
</template>

If we pass the error object to another method, we can more easily manipulate it in order to resolve our error:

<NuxtErrorBoundary>
<ImageViewer />
<template #error="{ error }">
<div>
<p>Oops, the image viewer isn't working properly!</p>
<p>{{ error.message }}</p>
<!-- Pass the error to another method to recover -->
<p><button @click="recoverFromError(error)">Try again</button></p>
</div>
</template>
</NuxtErrorBoundary>

Use a recoverFromError function to set the value of the error ref to null and re-render the default slot:

const recoverFromError = (error) => {
// Try to resolve the error here
error.value = null;
}

In some cases, a more drastic action might be needed, like navigating to a safe page using the navigateTo utility:

const recoverFromError = async (error) => {
// Navigate away first, otherwise we'll get stuck in a loop
await navigateTo('/');
error.value = null;
};

You can place NuxtErrorBoundary components around distinct chunks of functionality, like each widget in an admin dashboard, to contain and handle errors in specific ways.

This is better than simply having a generic error message that’s shown no matter what the error is. By isolating errors with NuxtErrorBoundary we can show more useful error messages and provide better ways of recovering!

Learn more from the docs: https://nuxt.com/docs/api/components/nuxt-error-boundary

🔥 v-once

If you've got large chunks of static or mostly static content, you can tell Vue to (mostly) ignore it using the v-once directive:

<template>
<!-- These elements never change -->
<div v-once>
<h1 class="text-center">Bananas for sale</h1>
<p>
Come get this wonderful fruit!
</p>
<p>
Our bananas are always the same price — ${{ banana.price }} each!
</p>
<div class="rounded p-4 bg-yellow-200 text-black">
<h2>
Number of bananas in stock: as many as you need
</h2>
<p>
That's right, we never run out of bananas!
</p>
</div>
<p>
Some people might say that we're... bananas about bananas!
</p>
</div>
</template>

This can be a helpful performance optimization if you need it.

The v-once directive tells Vue to evaluate it once and never update it again. After the initial update it's treated as fully static content.

Here are the docs for v-once.

🔥 Global Properties

It's possible to add global properties to your Vue app in both Vue 2 and Vue 3:

// Vue 3
const app = createApp({});
app.config.globalProperties.$myGlobal = 'globalpropertiesftw';
// Vue 2
Vue.prototype.$myGlobal = 'globalpropertiesftw';

I would recommend prefixing any global properties with a $.

This helps prevent naming conflicts with other variables, and it's a standard convention that makes it easy to spot when a value is global.

This global property can be accessed directly off of any component when using the Options API:

computed: {
getGlobalProperty() {
return this.$myGlobal;
},
},

Why can't this be used with the composition API?

Because the composition API is designed to be context-free and has no access to this.

Instead, you can create a simple composable to access your globals:

<script setup>
import useGlobals from './useGlobals';
const { $myGlobal } = useGlobals();
</script>
// useGlobals.js
export default () => ({
$myGlobal: 'globalpropertiesftw',
});

📜 3 Ways to Create Inline Composables

Composables are great, except that it seems we always need to create a new file for them.

In this article I explore some ways we can create inline composables — no need to create new files all over the place!

Check it out here: 3 Ways to Create Inline Composables

đź’¬ Principles of Programmer Productivity

"What one programmer can do in one month, two programmers can do in two months." — Fred Brooks

đź§  Spaced-repetition: How to use callOnce in Nuxt

The best way to commit something to long-term memory is to periodically review it, gradually increasing the time between reviews 👨‍🔬

Actually remembering these tips is much more useful than just a quick distraction, so here's a tip from a couple weeks ago to jog your memory.

If you need to run a piece of code only once, there’s a Nuxt composable for that (since 3.9):

<script setup>
await callOnce(async () => {
// This will only be run one time, even with SSR
});
</script>

Using callOnce ensures that your code is only executed one time — either on the server during SSR or on the client when the user navigates to a new page.

It also has a key similar to useFetch or useAsyncData, to make sure that it can keep track of what’s been executed and what hasn’t:

<script setup>
['one', 'two', 'three'].forEach(item => {
// Run once for each item
callOnce(item, async () => {
// Do something with the item
});
});
</script>

By default Nuxt will use the file and line number to automatically generate a unique key, but this won’t work in all cases.



p.s. I also have four products/courses: Clean Components Toolkit, Vue Tips Collection 2, Mastering Nuxt 3, and Reusable Components