Welcome!
Lead Instructor @ Vue School
Full Stack developer (10 years)
Husband and Father
presentation Icons from fontawesome.com
Workshop
Dynamic/Reactive Frontends
<template>
<div>
<h1>Nuxt is Awesome!</h1>
<ul v-auto-animate>
<li
v-for="reason in soManyReasons"
:key="reason">
{{ reason }}
</li>
</ul>
</div>
</template>
Templating
v2
Increased Performance
TypeScript Support
Composition API
v3
Composition API
v3
Game Changer
Composition API
v3
You can write your own reusable composables to extract logic
Composition API
v3
Or reach for off the shelf composables
VueUse
pre-installed in our exercise project and we'll use it a bit later in an exercise
Better Performance
No waiting for browser to
run JS app and render
Improved SEO
Web crawlers directly index
the page's content
Static Site Generation
Cheap, fast, secure hosting
on the Jamstack
v2
Better Performance
No waiting for browser to
run JS app and render
Improved SEO
Web crawlers directly index
the page's content
Static Site Generation
Cheap, fast, secure hosting
on the Jamstack
Runs Anywhere
Not just Node anymore.
Deno, workers, +
Built for Fast
Cold Starts
Great for serving from
Lambda or Edge Functions
Hybrid Rendering
Support multiple
rendering modes on a
single app
v3
⚗️
https://nitro.unjs.io/
Framework agnostic server engine
⚗️
Tons of features!
⚗️
Tons of features!
⚗️
internally uses h3 for mapping requests to handlers
Nuxt
Nitro
h3
⚗️
Most exciting features to me include:
Hybrid Rendering
Universal Deploys
Hybrid Rendering
export default defineNuxtConfig({
routeRules: {
// Static page generated on-demand, revalidates in background
'/blog/**': { swr: true },
// Static page generated on-demand once
'/articles/**': { static: true },
// Set custom headers matching paths
'/_nuxt/**': { headers: { 'cache-control': 's-maxage=0' } },
// Render these routes with SPA
'/admin/**': { ssr: false },
// Add cors headers
'/api/v1/**': { cors: true },
// Add redirect headers
'/old-page': { redirect: '/new-page' },
'/old-page2': { redirect: { to: '/new-page', statusCode: 302 } }
}
})
v3
Hybrid Rendering
Hybrid Rendering
v3
Zero-Config Deploys
Or minimal config deploys
v3
Webpack
Bundling and
development server
with HMR
CLI
Start dev server, build for production, ssg
v2
Vite
Lightning fast
development server
with HMR
Nuxi CLI
Everything from Nuxt CLI
plus: file scaffolding, updates,
cleanup, and more!
v3
Nuxt Devtools
set of visual tools to
help you better understand
your app
more on this in a bit
v3
npx nuxi add page HelloWorld
// @/pages/HelloWorld.vue
<script lang="ts" setup></script>
<template>
<div>
Page: foo
</div>
</template>
<style scoped></style>
Nuxi CLI
v3
npx nuxi add plugin MyPlugin
// @/plugins/MyPlugin.ts
export default defineNuxtPlugin((nuxtApp) => {})
Nuxi CLI
🙋
15 mins
And how syntax has changed
Conventions
Predictable file structure
that's easy to navigate
Functionality Based
on Directory
Auto imported components,
file-based routing, etc
v2
Conventions
Predictable file structure
that's easy to navigate
Functionality Based
on Directory
Auto imported components,
file-based routing, etc
V3 Enhancments
Nuxt 3 adds conventions
and added functionality
v3
routes created based on structure (including dynamic routes)
/pages/edit-[id].vue
v2
/pages/_id.vue
Dynamic Params
/pages/[id].vue
v3
Dynamic Params
/pages/_.vue
Catch All
/pages/[...slug].vue
Catch All
<NuxtLink to="/123">Post 123</NuxtLink>
routes created based on structure (including dynamic routes)
v3
Page Meta
// MyPage.vue
definePageMeta({
middleware: ["auth"],
pageTransition: 'fade',
layout: 'blog',
})
useHead({
title: "Hello World"
})
v2
Page Meta
// MyPage.vue
export default {
middleware: 'auth',
transition: 'fade',
layout: 'blog',
head() {
return {
title: 'Hello World',
}
}
}
routes created based on structure (including dynamic routes)
v3
Get Current Route
// MyPage.vue
console.log(useRoute());
v2
Get Current Route
// MyPage.vue
export default {
created(){
console.log(this.$route)
}
}
predictable location to store Vue Single File components
v3
Auto Imports
// it's automatic!
v2
Auto Imports
// nuxt.config.js
export default {
components: true
}
🔥 so are composables and utils!
/composables
/utils
predictable location to store Vue Single File components
v3
Client Only
// MyPage.vue
<template>
<ClientOnly>
<MyComponent/>
</ClientOnly>
</template>
v2
Client Only
// MyPage.vue
<template>
<ClientOnly>
<MyComponent/>
</ClientOnly>
</template>
predictable location to store Vue Single File components
v2
Client Only
// MyPage.vue
<template>
<ClientOnly>
<MyComponent/>
</ClientOnly>
</template>
v3
Client Only
// MyPage.vue
<template>
<MyComponent/>
</template>
MyComponent.client.vue
MyComponent.server.vue
predictable location to store Vue Single File components
MyComponent.client.vue
v2
Client Only
// MyPage.vue
<template>
<ClientOnly>
<MyComponent/>
</ClientOnly>
</template>
v3
Client Only
// MyPage.vue
<template>
<MyComponent/>
</template>
// MyComponent.server.vue
<template>
Loading...
</template>
MyComponent.server.vue
MyComponent.client.vue
predictable location to store Vue Single File components
MyComponent.client.vue
v2
Client Only
// MyPage.vue
<template>
<ClientOnly>
<MyComponent/>
</ClientOnly>
</template>
v3
Client Only
// MyPage.vue
<template>
<MyComponent/>
</template>
// MyComponent.client.vue
<script setup>
$fetch("http://myapi.com")
</script>
MyComponent.client.vue
lazy load components by prefixing with "lazy"
<LazyMountainsList v-if="show" />
combine directory structure to assemble component name
(Duplicate segments are removed)
<BaseFooCustomButton />
extract common UI or code patterns into reusable layouts
v3
Render Page
// layouts/default.vue
<template>
<div>
<slot />
</div>
</template>
v2
Render Page
// layouts/default.vue
<template>
<Nuxt />
</template>
⚠️ must provide wrapper!
extract common UI or code patterns into reusable layouts
v3
Render Page
// ~/error.vue
<template>
<pre> {{error}} </pre>
</template>
<script setup>
defineProps({
error: Object
})
</script>
v2
Error Page
// layouts/error.vue
<template>
<pre> {{error}} </pre>
</template>
<script>
export default {
props: ['error'],
}
</script>
run code before navigating to a route
run code before navigating to a route
v2
Global Middleware
// nuxt.config.js
export default{
router: {
middleware: 'log',
},
}
~/middleware/log.js
v3
Global Middleware
~/middleware/log.global.js
run code before navigating to a route
v2
Middleware Definition
// middleware/log.js
export default function (context) {
console.log("hello")
}
v3
Middleware Definition
// middleware/log.js
export default defineNuxtRouteMiddleware((to, from) => {
console.log("hello")
})
run code before navigating to a route
v2
Middleware Definition
// middleware/log.js
export default function ({redirect}) {
return redirect("/login")
}
v3
Middleware Definition
// middleware/log.js
export default defineNuxtRouteMiddleware((to, from) => {
return navigateTo('/login')
})
provide global helpers, register Vue plugins and directives
v2
Register Plugins
// nuxt.config.js
export default {
plugins: ['~/plugins/tooltip.js']
}
v3
Register Plugins
🔥 auto registered!
provide global helpers, register Vue plugins and directives
v2
Register Vue Plugin
// ~/plugins/tooltip.js
import Vue from 'vue'
import VTooltip from 'v-tooltip'
Vue.use(VTooltip)
v3
Register Vue Plugin
// ~/plugins/tooltip.js
import VTooltip from 'v-tooltip'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(VTooltip)
})
provide global helpers, register Vue plugins and directives
v2
Register Vue Plugin
// ~/plugins/tooltip.client.js
import Vue from 'vue'
import VTooltip from 'v-tooltip'
Vue.use(VTooltip)
v3
Register Vue Plugin
// ~/plugins/tooltip.client.js
import VTooltip from 'v-tooltip'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(VTooltip)
})
provide global helpers, register Vue plugins and directives
v2
Global Helpers
// ~/plugins/tooltip.js
export default ({ app }, inject) => {
inject(
'hello',
msg => console.log(`Hello ${msg}!`)
)
}
v3
Global Helpers
// ~/plugins/tooltip.js
export default defineNuxtPlugin(() => {
return {
provide: {
hello: (msg: string) => `Hello ${msg}!`
}
}
})
provide global helpers, register Vue plugins and directives
v2
Global Helpers
// MyComponent.vue
<template>
<div>{{ $hello('world') }}</div>
</template>
v3
Global Helpers
// MyComponent.vue
<template>
<div>{{ $hello('world') }}</div>
</template>
provide global helpers, register Vue plugins and directives
v2
Global Helpers
// MyComponent.vue
<script>
export default {
created() {
this.$hello('mounted')
}
}
</script>
v3
Global Helpers
// MyComponent.vue
<script setup lang="ts">
const { $hello } = useNuxtApp()
</script>
Just use a composable 🤔
Other miscellaneous changes made to the file structure
v2
Static Assets
v3
Static Assets
Other miscellaneous changes made to the file structure
v2
State Management
(Talk more about this later)
Other miscellaneous changes made to the file structure
v2
Server Directory
🤷
v3
Server Directory
REST api routes
runs on every request (including api)
nitro plugins
(Talk more about this later)
Other miscellaneous changes made to the file structure
v2
app.vue
🤷
v3
app.vue
// app.vue
<template>
<NuxtLoadingIndicator />
// Can add your own app-wide custom stuff
<AppHeader/>
<NuxtLayout>
<NuxtPage/>
</NuxtLayout>
</template>
<style>
// good place to register global CSS
@import "@/assets/main.css";
</style>
🙋
40 mins
<script>
export default {
async asyncData({ params, $http }) {
const post = await $http.$get(`...`)
return { post }
}
}
</script>
v2
Page Level Data Fetching
(blocks route navigation)
<script>
export default {
async fetch() {
this.posts = await this.$http.$get('...')
},
}
</script>
v2
Component Level Data Fetching
provides shortcuts for rendering loading states
v3
useAsyncData
useFetch
&
<script setup>
const { data } = await useAsyncData(() => $fetch('/api/count'))
</script>
<template>
Page visits: {{ data }}
</template>
Can be anything that resolves async
Must await (works with suspense)
data is return of callback
smart built-in for http requests
<script setup>
const { data } = await useAsyncData('unique_key', () => $fetch('/api/count'))
</script>
<template>
Page visits: {{ data }}
</template>
Can also take a unique key as first param
(if not provided it's generated based on file name and line number
Key is for caching
<script setup>
const { data } = await useAsyncData('unique_key', () => $fetch('/api/count'))
</script>
<template>
Page visits: {{ data }}
</template>
Key is for caching
👀 more on this in a later section!
const { data, pending, refresh, error } = await useAsyncData(...)
a boolean indicating whether the data is still being fetched
a function that can be used to refresh the data
an error object if the data fetching failed
await useAsyncData(()=>{...}, {
// lazy = don't block navigation
lazy: false,
// set default value before function resolves
default: ()=>{},
// fetch the data SSR?
server: true,
// alter the return from function
transform: ()=> {},
// limit data in payload for performance
pick: [],
//watch reactive sources to auto-refresh
watch: [],
// false means only run handler on change of a watcher
immediate: true
})
<script setup>
const { data } = await useAsyncData(() => $fetch('/api/count'))
</script>
<template>
Page visits: {{ data }}
</template>
Calling fetch in useAsyncData is very common
<script setup>
const { data } = await useFetch('/api/count')
</script>
<template>
Page visits: {{ data }}
</template>
useFetch is a shorthand
<script setup>
const { data, refresh, error, pending } = await useFetch('/api/count')
</script>
<template>
Page visits: {{ data }}
</template>
exposes the same data and functions
<script setup>
const { data } = await useFetch('/api/count', {
// all the options that useAsyncData takes plus...
// set the request method
method: "GET",
// set request body
body: { foo: "bar" },
// set request headers
headers: [{ Authorization "Bearer 1232342" }],
// set baseURL
baseURL: "https://mysite.com"
})
</script>
<template>
Page visits: {{ data }}
</template>
takes useAsyncDataOptions plus more
<script setup>
const skip = ref(0);
const url = computed(()=> `/api/count?skip=${skip.value}`)
const { data } = await useFetch(url)
</script>
<template>
Page visits: {{ data }}
</template>
use reactive url to automatically resend the request whenever URL changes
<script setup>
const skip = ref(0);
const { data } = await useFetch(()=> `/api/count?skip=${skip.value}`)
</script>
<template>
Page visits: {{ data }}
</template>
or provide a callback function
Both Come in
Versions
useLazyAsyncData
useLazyFetch
&
useLazyAsyncData
useLazyFetch
useLazyAsyncData
useLazyFetch
<script setup>
const { data, pending } = await useLazyFetch("...");
</script>
<template>
<!-- or could be v-if="!data" -->
<div v-if="pending">loading</div>
<div v-else>
{{ data }}
</div>
</template>
Create a new Server API Route
npx nuxi add api HelloWorld
Produces this file in /server/api/HelloWorld.ts
export default defineEventHandler((event) => {
return 'Hello HelloWorld'
})
Function that returns the response body
export default defineEventHandler((event) => {
return 'Hello HelloWorld'
})
Return a string
Function that returns the response body
export default defineEventHandler(async (event) => {
// make requests to third-party services, APIs
// talk to your database
// etc
return 'Hello HelloWorld'
})
Can return a promise to perform async operations
Function that returns the response body
export default defineEventHandler((event) => {
return {
"foo": "bar",
"activated": true,
"nested":{
"you": "bet"
}
}
})
Or JavaScript data that is serializable to JSON
Use util functions from h3 to get request info
export default defineEventHandler((event) => {
const body = readBody(event);
const myCookie = getCookie(event, "myCookie");
const queryString = getQuery(event);
// etc
return {...}
})
Will always pass the event
Then consume with useFetch
const { data } = await useFetch('/api/HelloWorld')
Route shows up in autocomplete options thanks to TS
Plus the data is automatically typed based on the return from the API route
Like pages you can use dynamic params
/server/api/posts/[id].ts
getRouterParams(event)
Specify handler by request method
/server/api/posts/[id].get.ts
/server/api/posts/[id].post.ts
/server/api/posts/[id].put.ts
/server/api/posts/[id].delete.ts
🙋
15 mins
End of Day everyone is getting a little tired
Saved devtools for last because it will
rev you up!
npx nuxi devtools enable
Open devtoosl here
Welcome message on first install
Overview of project
List of all routes
List all Components
List all Components
Components Graph to Visualize Deps and Types
Inspect components on page
View built in composables and those from libraries
Grayed out are available but not used
Solid white are used
View installed modules
Docs
Github
File System
Installed plugins and where they're from
Runtime and App Configs
We'll talk more about these later
State created with useState and fetched with useFetch/useAsyncData
(More on using this cache and state later)
It's cached under this key
The data we fetched in the exercise
Monitor the time spent in each hook
Help debug performance issues
Monitor the time spent in each hook
Help debug performance issues
Virtual files generated by Nuxt to support conventions
Inspect transformation steps of Vite
Inspect transformation steps of Vite
Modules can install their own extensions
Modules can install their own extensions
🙋
Welcome Back!
Why?
How?
In Nuxt 2, the answer was Vuex
What about in
Nuxt 3?
🙋
💬
Answers in the Chat, please
Pinia
Many people say
✅
Several Options
How does Pinia work?
2 options
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
Option Store
Great if you familiar
with Vuex options
// stores/counter.js
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubled = computed(()=> count.value * 2)
function increment() {
count.value++
}
return { count, increment }
})
Setup Store
Great if you prefer composable style
// MyComponent.vue
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
counter.count++
// or using an action instead
counter.increment()
</script>
Accessing the state is easy and TypeSafe
// MyComponent.vue
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
counter.count++
// or using an action instead
counter.increment()
</script>
Accessing the state is easy and TypeSafe
// plugins/vuex.ts
import { createStore } from "vuex";
import * as rootStore from "@/store/index";
export default defineNuxtPlugin((nuxtApp) => {
const store = createStore(rootStore);
nuxtApp.vueApp.use(store);
});
Only choose to ease migration process
Built in composable for global state management
Anatomy of useState
const count = useState('count', ()=> 0)
unique key
(to id this piece of state)
Callback function
returns the initial value
Reactive ref
Can be defined in component
// CountButton.vue
<script setup>
const count = useState('count', () => 0)
</script>
<template>
<div>
<button @click="count++">
{{ count }}
</button>
</div>
</template>
And used in another component
// OtherComponent.vue
<script setup>
const count = useState('count', () => 0)
</script>
<template>
<div>
{{ count }}
</div>
</template>
Both instances of count refer to the same state
Can also use in a composable
// composables/useCount.ts
export function useCount() {
const count = useState("count", () => 0);
return {
count,
};
}
And count will be the same across all components
where the composable is used
const { count } = useCount();
Similar to this in client-side only environment
// composables/useCount.ts
const count = ref(0);
export function useCount() {
return {
count,
};
}
❌
NOT SSR friendly
Similar to this in client-side only environment
// composables/useCount.ts
export function useCount() {
const count = useState("count", () => 0);
return {
count,
};
}
So always use useState instead of ref outside of the composable function
Can also be the composable itself
// composables/useCount.ts
export const useCounter = () => useState('counter', () => 0)
// MyComponent.vue
const count = useCount();
and use it like so:
Retrieve cached data from useAsyncData/useFetch calls
Retrieve cached data from useAsyncData/useFetch calls
/products
List of Products
/products/1
Single Product Page
in the background
Retrieve cached data from useAsyncData/useFetch calls
// pages/products/index.vue
const { data, pending } = await useFetch(
() => `https://dummyjson.com/products?skip=${skip.value}&limit=${perPage}`,
{
key: "products",
}
);
Specify a key on the fetch
const { data, pending } = await useAsyncData(
"products",
() => ...,
);
or provide as first argument to useAsyncData
Retrieve cached data from useAsyncData/useFetch calls
// pages/products/[id].vue
const { data: cached } = useNuxtData("products");
const id = computed(() => useRoute().params.id);
const { data: product } = await useLazyFetch(
() => `https://dummyjson.com/products/${id.value}`,
{
key: `product-${id.value}`,
default: () =>
cached.value?.products.find((product) => {
return product.id === Number(id.value);
}),
}
);
Can also use useNuxtData for optimistic updates
// pages/todos/index.vue
const { data } = await useFetch('/api/todos', { key: 'todos' })
Imagine we fetched some todos from an API
Can also use useNuxtData for optimistic updates
const newTodo = ref('')
const previousTodos = ref([])
// Access to the cached value of useFetch in todos.vue
const { data: todos } = useNuxtData('todos')
const { data } = await useFetch('/api/addTodo', {
key: 'addTodo',
method: 'post',
body: {
todo: newTodo.value
},
onRequest () {
previousTodos.value = todos.value // Store the previously cached value to restore if fetch fails.
todos.value.push(newTodo.value) // Optimistically update the todos.
},
onRequestError () {
todos.value = previousTodos.value // Rollback the data if the request failed.
},
async onResponse () {
await refreshNuxtData('todos') // Invalidate todos in the background if the request succeeded.
}
})
🙋
What are they and why?
💪
Common patterns to follow for building Nuxt applications
Overview
💪
Nuxt Image
💪
Nuxt Image
💪
Images are low hanging fruit for performance optimization.
Nuxt Image makes it easy to get a boost!
Nuxt Image
💪
// image exists at @/public/myImage.jpg
<img src="/myImage.jpg"/>
Nuxt Image
💪
// image exists at @/public/myImage.jpg
<NuxtImg src="/myImage.jpg"/>
Nuxt Image - options for <NuxtImg>
💪
// image exists at @/public/myImage.jpg
<NuxtImg
src="/myImage.jpg"
// actual image is resized before sending to browser (also height)
width="200"
sizes="sm:100vw md:50vw lg:400px"
format="webp"
quality="80"
loading="lazy"
/>
Nuxt Image - options for <NuxtImg>
💪
// image exists at @/public/myImage.jpg
<NuxtImg
src="/myImage.jpg"
// actual image is resized before sending to browser (also height)
width="200"
sizes="sm:100vw md:50vw lg:400px"
quality="80"
loading="lazy"
format="webp"
/>
Nuxt Image - <NuxtPicture>
💪
// image exists at @/public/myImage.jpg
<NuxtPicture src="/myImage.jpg" />
Better option is NuxtPicture as it allows the browser to decide the best format
will auto serve webp to browsers that support it and jpg to those that do not
Nuxt Image - $img utility
💪
const $img = useImage()
const imgUrl = $img(src, modifiers, options)
Even use external images
💪
// nuxt.config.ts
export default defineNuxtConfig({
image: {
domains: ["i.dummyjson.com"],
},
})
<NuxtImg
src="https://i.dummyjson.com/data/products/1/1.jpg"
/>
But must add domain to allowlist
Nuxt Image - FAQ
💪
Async components
💪
Async components
💪
divide the app into smaller chunks and only load a component from the server when it's needed
Async components
💪
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
Async components
💪
<script setup>
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
</script>
<template>
<AsyncComponent v-if="some condition...">
</template>
will load a seperate JS file only when condition is true
Async components
💪
<script setup>
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
</script>
<template>
<AsyncComponent>
</template>
no need to make async if always renders
Smart 3rd party deps
💪
3rd party deps add hidden weight that often goes uninvestigated
Smart 3rd party deps
💪
find the cost of adding an npm package to your bundle
Smart 3rd party deps
💪
Let's take a look at Lodash on bundlephobia
Smart 3rd party deps
💪
Webpack Bundle Analyzer
Smart 3rd party deps
💪
Rollup Plugin Visualizer
(for Vite)
Smart 3rd party deps
💪
npx nuxi analyze
Eliminating unneeded reactivity
💪
Sometimes we define variables only for convenience. And they never change, so it's unnecessary for Vue to track it as a dependency
Eliminating unneeded reactivity
💪
<script setup>
// will never change
const perPage = ref(10);
</script>
<template>
{{ perPage }}
</template>
Eliminating unneeded reactivity
💪
<script setup>
// will never change
const perPage = 10;
</script>
<template>
{{ perPage }}
</template>
Eliminating unneeded reactivity
💪
Some HTML never needs to be hydrated at all
Eliminating unneeded reactivity
💪
Nuxt Server Components
Eliminating unneeded reactivity
💪
// Markdown.vue
<script setup lang="ts">
import MarkdownIt from "markdown-it";
const md = new MarkdownIt();
const props = defineProps<{
md: string;
}>();
var result = md.render(props.md);
</script>
<template>
<div v-html="result"></div>
</template>
Example
Markdown
Component
Eliminating unneeded reactivity
💪
Normally the component js and the markdown-it library are downloaded and run in browser
Eliminating unneeded reactivity
💪
Markdown.server.vue
append server to the component name to render ONLY on the server side with NO client side JS
Eliminating unneeded reactivity
💪
Eliminating unneeded reactivity
💪
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
componentIslands: true,
},
})
Virtual lists
💪
Sometimes you need to render VERY long lists which can make the page react sluggishly
Virtual lists
💪
Virutal lists only render the minimum number of DOM nodes necessary to show the items within a container
Virtual lists
💪
Overview
💪
TypeScript & ESLint
💪
TypeScript & ESLint
💪
No time to dive into details
Live Talk at Vue.js Amsertdam
Composables vs Mixins
💪
Composables vs Mixins
💪
No time to dive into details
Other Tips and Tricks
💪
🙋
40 mins
Error Handling
Sources of Errors
Error Handling
How do we handle these errors?
Error Handling
Vue's onErrorCaptured
onErrorCaptured((error, instance, info) => {
// do whatever with the error
});
Error Handling
Vue's onErrorCaptured
onErrorCaptured((error, instance, info) => {
alert("Sorry, we hit a snag. Please refresh and try again")
});
great for performing side affects whenever an error occurs
Error Handling
app level error handling
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.config.errorHandler = (error, context) => {
// ...
}
})
Error Handling
alternatively use nuxt hook `vue:error`
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('vue:error', (..._args) => {
console.log('vue:error')
})
})
Error Handling
Rendering an Error Page
// ~/error.vue
<script setup>
defineProps(["error"]);
</script>
<template>
Oh noes!
<pre>
{{ error }}
</pre>
</template>
👍 Great for show stopping errors
lives in root directory
accepts an error prop
Error Handling
Rendering an Error Page
// ~/error.vue
<script setup>
defineProps(["error"]);
</script>
<template>
Oh noes!
<button @click="clearError({ redirect: '/' })">
Go home
</button>
</template>
Call clear error with the redirect option to clear error and navigate away
Error Handling
The NuxtErrorBoundary Component
<template>
<!-- some content -->
<NuxtErrorBoundary @error="someErrorLogger">
<!-- You use the default slot to render your content -->
<template #error="{ error }">
You can display the error locally here.
<button @click="error = null">
This will clear the error.
</button>
</template>
</NuxtErrorBoundary>
</template>
Wrap portions of UI with NuxtErrorBoundary and if an error occurs anywhere within it, the error slot shows
Error Handling
Error related functions
🙋
Throughout the workshop you've seen:
✅ Juicy new Nuxt 3 features
✅ differences in the old & new styntaxes
🎉 Now let's migrate!
⚠️ But first....
a warning
Setting expectations
Nuxt 3 is a complete rewrite of Nuxt 2
There will be significant changes when migrating a Nuxt 2 app to Nuxt 3
You WILL run into errors and have to do some googling when errors surface
Not all modules are ready for Nuxt 3. You might have to create temporary work arounds
Setting expectations
Don't worry
There are some tools available
Official Upgrade Guide
Nuxt Cheat Sheet
Debbie O'Brien Blog Post
2 options for Migrating
Straight to Nuxt 3
Nuxt Bridge
Progressively migrate with this compatibility layer for Nuxt 2
Let's give it a try!
Let's give it a try!
In the last exercise we'll:
🙋
Resources
Composition API
Resources
Composition API
Resources
Nuxt 3
Resources
Pinia
🙏