Day 1

 

👋

Welcome!

@danielkelly_io

@danielkellyio

Alabama, USA

Daniel Kelly

Lead Instructor @ Vue School

Full Stack developer (10 years)

Husband and Father

presentation Icons from fontawesome.com

Nuxt 2 Nuxt 3

Workshop

Intro

What's New in Nuxt 3

 

1

Vue

Vue

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

Vue

3

Vue

3

Increased Performance

  • ~55% faster overall
  • updates up to 33% faster
  • memory usage ⬇ 54%

TypeScript Support

  • Improved DX
  • Zero-config
  • Less fragile apps

Composition API

  • Better logic reuse
  • Flexible code organization
  • Better type inference

v3

Vue

3

Composition API

  • Better logic reuse
  • Flexible code organization
  • Better type inference

v3

Game Changer

Vue

3

Composition API

  • Better logic reuse
  • Flexible code organization
  • Better type inference

v3

You can write your own reusable composables to extract logic

Vue

3

Composition API

  • Better logic reuse
  • Flexible code organization
  • Better type inference

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

Universal Rendering

Better Performance

No waiting for browser to

run JS app and render

Improved SEO

Web crawlers directly index

the page's content

Universal Rendering

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

Universal Rendering

Static Site Generation

Cheap, fast, secure hosting

on the Jamstack

New and Improved

💪

Runs Anywhere

Not just Node anymore.

Deno, workers, +

Built for Fast

Cold Starts

Great for serving from

Lambda or Edge Functions

Universal Rendering

Hybrid Rendering

Support multiple

rendering modes on a

single app

v3

Thanks to Nitro

⚗️

https://nitro.unjs.io/

Framework agnostic server engine

Thanks to Nitro

⚗️

Tons of features!

Thanks to Nitro

⚗️

Tons of features!

Thanks to Nitro

⚗️

internally uses h3 for mapping requests to handlers

Nuxt

Nitro

h3

Thanks to Nitro

⚗️

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

  • ssr: false - admin dashboards, SaaS apps, etc when SEO isn't important
     
  • static: true - marketing pages, blog posts, etc
     
  • swr: true - any page with dynamic content that changes semi-often

Hybrid Rendering

v3

Zero-Config Deploys

Or minimal config deploys

v3

Tooling

Webpack

Bundling and

development server

with HMR

CLI

Start dev server, build for production, ssg

v2

Tooling

Vite

Lightning fast

development server

with HMR

Nuxi CLI

Everything from Nuxt CLI

plus: file scaffolding, updates,

cleanup, and more!

v3

Tooling

Nuxt Devtools

set of visual tools to

help you better understand

your app

more on this in a bit

v3

Tooling

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

Tooling

npx nuxi add plugin MyPlugin
// @/plugins/MyPlugin.ts
export default defineNuxtPlugin((nuxtApp) => {})

Nuxi CLI

 

Questions?

 

🙋

 

Exercise

 

1

 

Coffee Break

 

☕️

15 mins

File Structure

2

And how syntax has changed

File Structure

Conventions

Predictable file structure

that's easy to navigate

Functionality Based

on Directory

Auto imported components,

file-based routing, etc

v2

File Structure

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

/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)

/pages

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)

/pages

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

/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

/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

/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

/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

/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"

/components

<LazyMountainsList v-if="show" />
  • useful if component is not always needed
  • delay loading component until the right moment
  • optimize bundle size

combine directory structure to assemble component name

/components

(Duplicate segments are removed)

 <BaseFooCustomButton />

extract common UI or code patterns into reusable layouts

/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

/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

/middleware

  • anonymous middleware
  • named middleware
  • global middleware

run code before navigating to a route

/middleware

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

/middleware

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

/middleware

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

/plugins

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

/plugins

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

/plugins

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

/plugins

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

/plugins

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

/plugins

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

File Structure Misc.

v2

Static Assets

v3

Static Assets

Other miscellaneous changes made to the file structure

File Structure Misc.

v2

State Management

const useX = () => useState('x')

State Management

v3

(Talk more about this later)

Other miscellaneous changes made to the file structure

File Structure Misc.

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

File Structure Misc.

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>

 

Questions?

 

🙋

 

Exercise

 

2

 

Lunch Break

 

🍔

40 mins

 

 Data Fetching and Server API Routes

 

3

Data Fetching

<script>
  export default {
    async asyncData({ params, $http }) {
      const post = await $http.$get(`...`)
      return { post }
    }
  }
</script>

v2

Page Level Data Fetching

(blocks route navigation)

Data Fetching

<script>
  export default {
    async fetch() {
      this.posts = await this.$http.$get('...')
    },
  }
</script>

v2

Component Level Data Fetching

provides shortcuts for rendering loading states

Data Fetching

v3

useAsyncData

useFetch

&

useAsyncData

  • works in pages, components, and plugins
  • used to fetch data that resolves async

useAsyncData

<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

useAsyncData

<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

useAsyncData

<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!

return from useAsyncData

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

useAsyncData options

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
})

useFetch

<script setup>
const { data } = await useAsyncData(() => $fetch('/api/count'))
</script>

<template>
  Page visits: {{ data }}
</template>

Calling fetch in useAsyncData is very common

useFetch

<script setup>
const { data } = await useFetch('/api/count')
</script>

<template>
  Page visits: {{ data }}
</template>

useFetch is a shorthand

useFetch

<script setup>
const { data, refresh, error, pending } = await useFetch('/api/count')
</script>

<template>
  Page visits: {{ data }}
</template>

exposes the same data and functions

useFetch

<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

useFetch

<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

useFetch

<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

Lazy

Versions

useLazyAsyncData

useLazyFetch

&

useLazyAsyncData

useLazyFetch

  • does not block navigation
  • can show loading indicator on page where data is fetched
  • still renders server side on direct page visit
  • must handle null data or check pending

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>

Where do I fetch data from?

  • Can be an external API (like https://dummyjson.com)
  • Server API Routes

Server API Routes

Create a new Server API Route

npx nuxi add api HelloWorld

Server API Routes

Produces this file in /server/api/HelloWorld.ts

export default defineEventHandler((event) => {
  return 'Hello HelloWorld'
})

Server API Routes

Function that returns the response body

export default defineEventHandler((event) => {
  return 'Hello HelloWorld'
})

Return a string

Server API Routes

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

Server API Routes

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

Server API Routes

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

Server API Routes

Then consume with useFetch

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

Server API Routes

Route shows up in autocomplete options thanks to TS

Server API Routes

Plus the data is automatically typed based on the return from the API route

Server API Routes

Like pages you can use dynamic params

/server/api/posts/[id].ts

getRouterParams(event)

Server API Routes

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

 

Questions?

 

🙋

 

Exercise

 

3

 

Coffee Break

 

☕️

15 mins

 

Nuxt Devtools

 

4

End of Day everyone is getting a little tired

Saved devtools for last because it will

rev you up!

Nuxt Devtools

Nuxt Devtools

npx nuxi devtools enable

Nuxt Devtools

Open devtoosl here

Nuxt Devtools

Welcome message on first install

Nuxt Devtools

Overview of project

Nuxt Devtools

List of all routes

  • Click to visit route
  • and can also open file in IDE

Nuxt Devtools

List all Components

Nuxt Devtools

List all Components

Nuxt Devtools

Components Graph to Visualize Deps and Types

Nuxt Devtools

Inspect components on page

Nuxt Devtools

View built in composables and those from libraries

Grayed out are available but not used

Solid white are used

Nuxt Devtools

View installed modules

Docs

Github

File System

Nuxt Devtools

Installed plugins and where they're from

Nuxt Devtools

Runtime and App Configs

We'll talk more about these later

Nuxt Devtools

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

Nuxt Devtools

Monitor the time spent in each hook

Help debug performance issues

Nuxt Devtools

Monitor the time spent in each hook

Help debug performance issues

Nuxt Devtools

Virtual files generated by Nuxt to support conventions

Nuxt Devtools

Inspect transformation steps of Vite

Nuxt Devtools

Inspect transformation steps of Vite

Nuxt Devtools

Modules can install their own extensions

Nuxt Devtools

Modules can install their own extensions

 

Questions?

 

🙋

 

Exercise

 

4

 

Q&A

 

 

Day 2

 

👋

Welcome Back!

 

Global State Management

 

5

Global State Management

Why?

  • Share state across components
  • Avoid prop drilling
  • Even share state across pages (usually for performance)

Global State Management

How?

In Nuxt 2, the answer was Vuex

Global State Management

What about in

Nuxt 3?

🙋

💬

Answers in the Chat, please

Global State Management

Pinia

Many people say

Global State Management

Several Options

  • Pinia
  • useState (built in composable)
  • useNuxtData (kinda) (built in composable)
  • Vuex (good if migrating)

Pinia

  • Devtools support
    • A timeline to track actions, mutations
    • Stores appear in components where they are used
    • Time travel and easier debugging
  • Hot module replacement
    • Modify your stores without reloading your page
    • Keep any existing state while developing

Pinia

  • Plugins: extend Pinia features with plugins
  • Proper TypeScript support or autocompletion for JS users
  • Server Side Rendering Support

How does Pinia work?

2 options

Pinia

// 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

Pinia

// 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

Pinia

// 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

Pinia

// 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

Vuex

// 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

useState()

Built in composable for global state management

  • SSR-friendly shared state across components
  • preserved after server-side rendering (during hydration)
  • shared across all components using a unique key
  • only work in setup or lifecycle hooks
    (you can use in composable that's called in setup)
  • must be serializable to JSON

useState()

Anatomy of useState

const count = useState('count', ()=> 0)

unique key
(to id this piece of state)

Callback function

returns the initial value

Reactive ref

useState()

Can be defined in component

// CountButton.vue
<script setup>
const count = useState('count', () => 0)
</script>

<template>
  <div>
    <button @click="count++">
      {{ count }}
    </button>
  </div>
</template>

useState()

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

useState()

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();

useState()

Similar to this in client-side only environment

// composables/useCount.ts

const count = ref(0);
export function useCount() {
  return {
    count,
  };
}

NOT SSR friendly

useState()

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

useState()

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:

useNuxtData()

Retrieve cached data from useAsyncData/useFetch calls

  • not strictly a state management solution
  • but can replace state management in some cases
  • For example, sharing the data of a resource across pages

useNuxtData()

Retrieve cached data from useAsyncData/useFetch calls

/products

List of Products

/products/1

Single Product Page

in the background

useNuxtData()

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

useNuxtData()

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);
      }),
  }
);

useNuxtData()

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

useNuxtData()

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.
  }
})

 

Questions?

 

🙋

 

Exercise

 

5

 

Coffee Break

 

☕️

 

Best Practices

 

6

Best Practices

What are they and why?

💪

Common patterns to follow for building Nuxt applications

  • performance
  • code maintainability
  • scalability
  • often used to describe preference (this is not correct usage)

Best Practices | Performance

Overview

💪

  • Nuxt Image - optimize image size/format
  • Async components
  • Smart 3rd party deps
  • Eliminating unneeded reactivity
  • Virtual lists

Best Practices | Performance

Nuxt Image

💪

Best Practices | Performance

Nuxt Image

💪

Images are low hanging fruit for performance optimization.

Nuxt Image makes it easy to get a boost!

Best Practices | Performance

Nuxt Image

💪

// image exists at @/public/myImage.jpg
<img src="/myImage.jpg"/>

Best Practices | Performance

Nuxt Image

💪

// image exists at @/public/myImage.jpg
<NuxtImg src="/myImage.jpg"/>

Best Practices | Performance

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"
/>

Best Practices | Performance

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"
/>

Best Practices | Performance

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

Best Practices | Performance

Nuxt Image - $img utility

💪

const $img = useImage()
const imgUrl = $img(src, modifiers, options)

Best Practices | Performance

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

Best Practices | Performance

Nuxt Image - FAQ

💪

  • Does it work without Cloudinary? Yes (21+ providers)
  • Does it work on external images? Yes
  • Where do I store local images? /public

Best Practices | Performance

Async components

💪

Best Practices | Performance

Async components

💪

divide the app into smaller chunks and only load a component from the server when it's needed

Best Practices | Performance

Async components

💪

const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

Best Practices | Performance

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

Best Practices | Performance

Async components

💪

<script setup>
const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)
</script>

<template>
  <AsyncComponent>
</template>  

no need to make async if always renders

Best Practices | Performance

Smart 3rd party deps

💪

3rd party deps add hidden weight that often goes uninvestigated

Best Practices | Performance

Smart 3rd party deps

💪

find the cost of adding an npm package to your bundle

Best Practices | Performance

Smart 3rd party deps

💪

Let's take a look at Lodash on bundlephobia

Best Practices | Performance

Smart 3rd party deps

💪

Webpack Bundle Analyzer

Best Practices | Performance

Smart 3rd party deps

💪

Rollup Plugin Visualizer

(for Vite)

Best Practices | Performance

Smart 3rd party deps

💪

npx nuxi analyze

Best Practices | Performance

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

Best Practices | Performance

Eliminating unneeded reactivity

💪

<script setup>
// will never change
const perPage = ref(10);
</script>

<template>
{{ perPage }}
</template>

Best Practices | Performance

Eliminating unneeded reactivity

💪

<script setup>
// will never change
const perPage = 10;
</script>

<template>
{{ perPage }}
</template>

Best Practices | Performance

Eliminating unneeded reactivity

💪

Some HTML never needs to be hydrated at all

Best Practices | Performance

Eliminating unneeded reactivity

💪

Nuxt Server Components

Best Practices | Performance

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

Best Practices | Performance

Eliminating unneeded reactivity

💪

Normally the component js and the markdown-it library are downloaded and run in browser

Best Practices | Performance

Eliminating unneeded reactivity

💪

Markdown.server.vue

append server to the component name to render ONLY on the server side with NO client side JS

Best Practices | Performance

Eliminating unneeded reactivity

💪

  • great for content that is mostly static
  • if prop value changes ajax request is made for new HTML
  • is currently experimental

Best Practices | Performance

Eliminating unneeded reactivity

💪

// nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    componentIslands: true,
  },
})

Best Practices | Performance

Virtual lists

💪

Sometimes you need to render VERY long lists which can make the page react sluggishly

Best Practices | Performance

Virtual lists

💪

Virutal lists only render the minimum number of DOM nodes necessary to show the items within a container

Best Practices | Performance

Virtual lists

💪

Best Practices | Maintainability/Scalability

Overview

💪

  • use TypeScript
  • use ESLint
  • prefer composables over mixins
  • Other tips and tricks

Best Practices | Maintainability/Scalability

TypeScript & ESLint

💪

  • helps you catch errors directly in your IDE
  • gives your IDE autocomplete super powers
    (even on component props and events)
  • makes refactors less risky and less stressful

Best Practices | Maintainability/Scalability

TypeScript & ESLint

💪

No time to dive into details

Live Talk at Vue.js Amsertdam

Best Practices | Maintainability/Scalability

Composables vs Mixins

💪

  • makes sources of data/methods more clear
  • prevents naming collisions
  • allows for private reactive data

Best Practices | Maintainability/Scalability

Composables vs Mixins

💪

No time to dive into details

Best Practices | Maintainability/Scalability

Other Tips and Tricks

💪

  • separate logic from presentation
  • use :key on loops
  • maintain a predictable naming conventions based on the Vue.js style guide
  • minimize direct DOM manipulation
    (use template refs otherwise)
  • validate props with TS or even runtime validations

 

Questions?

 

🙋

 

Exercise

 

6

 

Lunch Break

 

🍔

40 mins

 

Error Handling

 

7

Error Handling

  1. Errors during the Vue rendering lifecycle (SSR + SPA)
  2. Errors during API or Nitro server lifecycle
  3. Server and client startup errors (SSR + SPA)

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
});
  • component level error handling
  • catch errors from descendant component
  • return false to prevent propagation

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) => {
    // ...
  }
})
  • run a callback whenever any error happens throughout the app
  • great for services like Sentry, Rollbar, etc

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

  • useError() - get the error currently being handled
  • throw createError() - create a new error and throw it
    • on server - full-screen error page
    • on client - throws a non-fatal error
  • clearError() - clear the currently handled Nuxt error

 

Questions?

 

🙋

 

Exercise

 

7

 

Path to Migration

 

8

Migrate from 2 to 3

Throughout the workshop you've seen:

✅ Juicy new Nuxt 3 features

✅ differences in the old & new styntaxes

Migrate from 2 to 3

🎉 Now let's migrate!

Migrate from 2 to 3

⚠️ But first....
a warning

Setting expectations

Migrate from 2 to 3

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

Migrate from 2 to 3

Don't worry

There are some tools available

Official Upgrade Guide

Migrate from 2 to 3

Nuxt Cheat Sheet

Migrate from 2 to 3

Debbie O'Brien Blog Post

Migrate from 2 to 3

2 options for Migrating

Migrate from 2 to 3

Straight to Nuxt 3

  • Migrate all at once
  • Start new Nuxt 3 proeject, and move files one at a time
  • Progressively migrate
  • Upgrade directly in existing project
  • My experience hasn't been great

Nuxt Bridge

Progressively migrate with this compatibility layer for Nuxt 2

Nuxt Bridge

  • Nitro
  • Vite
  • Composition API
  • Script Setup
  • Auto Imports

Migrate from 2 to 3

  • You've been given the tools
  • You know the majority of  syntax changes

Let's give it a try!

Let's give it a try!

Migrate from 2 to 3

In the last exercise we'll:

  • Start with an existing Nuxt 2 project
  • Create a brand new Nuxt 3 project
  • Move files over 1 at a time from Nuxt 2 project until working

 

Questions?

 

🙋

 

Exercise

 

8

 

Q&A

 

Resources

Composition API

Resources

Composition API

Resources

Nuxt 3

Resources

Pinia

🙏

Thanks!

Nuxt 2->3 Workshop

By Daniel Kelly

Nuxt 2->3 Workshop

  • 858