Alexander Lichter
Web Engineering Consultant • Founder • Nuxt team • Speaker
Developer Group Leipzig - 01. Juni 2021
Alexander Lichter
Nuxt.js Core Maintainer
@TheAlexLichter
Web Dev Consultant
@TheAlexLichter
Vue 3 + CAPI out for half a year now
but the ecosystem is still catching up
Vue 2 + CAPI is a viable option
More and more libraries provide composables, e.g. vue-apollo or vue-i18n
Plans to move CAPI inside the Vue 2 core
@TheAlexLichter
[...] primary goal is to provide a way to experiment with the API and to collect feedback.
When you migrate to Vue 3, just replacing @vue/composition-api to vue and your code should just work.
@TheAlexLichter
Problems
@TheAlexLichter
@TheAlexLichter
Similar to the @vue/composition-api package
@TheAlexLichter
The API of nuxt-specific composables will
likely change before Nuxt 3 is released.
@TheAlexLichter
@TheAlexLichter
@TheAlexLichter
import axios from 'axios'
export default {
data() {
return {
mountains: []
}
},
async fetch() {
this.mountains = await axios.get('https://api.nuxtjs.dev/mountains')
}
}
@TheAlexLichter
works like the fetch method of Nuxt.js
import { defineComponent, ref, useFetch } from '@nuxtjs/composition-api'
import axios from 'axios'
export default defineComponent({
setup() {
const mountains = ref([])
useFetch(async () => {
mountains.value = await axios.get('https://api.nuxtjs.dev/mountains')
})
return { mountains }
}
})
@TheAlexLichter
fetch and fetchState
import { defineComponent, ref, useFetch } from '@nuxtjs/composition-api'
import axios from 'axios'
export default defineComponent({
setup() {
const mountains = ref([])
// Use fetch to re-fetch data
// Use fetchState to check if error, pending or all good
// Both exist in the template already ($fetch, $fetchState)
const { fetch, fetchState } = useFetch(async () => {
mountains.value = await axios.get('https://api.nuxtjs.dev/mountains')
})
return { mountains }
}
})
@TheAlexLichter
Access the route, store and more!
export default {
async asyncData(ctx) {
const { slug } = ctx.params
const mountain = await ctx.$axios.$get('https://api.nuxtjs.dev/mountains/' + slug)
context.store.dispatch('initializeMountains')
return { mountain }
}
})
@TheAlexLichter
Similar to Vue3 router composables
import { computed, defineComponent, useRoute } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const route = useRoute()
const id = computed(() => route.value.params.id)
}
})
import { defineComponent, useRouter } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const router = useRouter()
router.push('/')
}
})
@TheAlexLichter
Similar to Vuex composable
import { defineComponent, useStore } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const store = useStore()
}
})
@TheAlexLichter
...with types
import { defineComponent, useStore } from '@nuxtjs/composition-api'
export interface State {
counter: number
}
export const key: InjectionKey<Store<State>> = Symbol()
export default defineComponent({
setup() {
const store = useStore(key)
// or
const store = useStore<State>()
// Now, `store.state.counter` will be typed as a number
}
})
@TheAlexLichter
All other belongings, e.g. modules!
import { defineComponent, useContext, slug } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
// There is the Nuxt.js context
// You might know it from asyncData or middleware
// It contains your module, the store and more
// But better use useRoute(r), useStore and so on if possible
const context = useContext()
context.$http.$get('https://api.nuxtjs.dev/tips')
}
})
@TheAlexLichter
import { defineComponent, useAsync, useContext } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const { $http } = useContext()
// Returns a null ref that is populated when the call resolves
const posts = useAsync(() => $http.$get('https://api.nuxtjs.dev/mountains'))
return { posts }
}
})
Another way to fetch data
@TheAlexLichter
const posts = useAsync(() => $http.$get('...'))
Node.js
Server
1. Request
4. HTML + JS
including useAsync response
API
2. useAsync call
3. Response
Initial request (SSR)
For the initial request, useAsync is only called on server-side
@TheAlexLichter
const posts = useAsync(() => $http.$get('...'))
1. posts is null useAsync called on navigation
API
2. posts has now a value (if the call succeeded)
Client-side navigation (CSR)
For further requests, useAsync is called on client-side. The navigation won't be blocked
@TheAlexLichter
Not the exact equivalent of asyncData
@TheAlexLichter
for expensive functions + SSG
export default defineComponent({
setup() {
const { params } = useRoute()
const slug = computed(() => params.value.slug)
const beer = useStatic(
slug => axios.get(`https://api.nuxtjs.dev/beers/${slug.value}`), // 1
slug, // 2
'beers' // 3
)
return { beer }
},
})
@TheAlexLichter
during generation
const beer = useStatic(
slug => axios.get(`https://api.nuxtjs.dev/beers/${slug}`), // 1
slug, // 2
'beers' // 3
)
@TheAlexLichter
when your generated site is live
const beer = useStatic(
slug => axios.get(`https://api.nuxtjs.dev/beers/${slug}`),
slug,
'beers'
)
CDN
1. Request
2. HTML + JS
including the inline JSON result
Initial request (retrieving static file)
@TheAlexLichter
when your generated site is live
const beer = useStatic(
slug => axios.get(`https://api.nuxtjs.dev/beers/${slug}`),
slug,
'beers'
)
Client-side navigation (CSR)
1. Get JSON from /static/ folder
JSON
2. Return and cache content if request succeeded
If it failed, run the factory function on client-side
@TheAlexLichter
import { provide,onGlobalSetup, } from "@nuxtjs/composition-api";
import { DefaultApolloClient } from "@vue/apollo-composable";
export default defineNuxtPlugin(({ app }) => {
onGlobalSetup(() => {
provide(DefaultApolloClient, app.apolloProvider?.defaultClient);
});
});
@TheAlexLichter
manipulate head data easily
import { defineComponent, ref, useMeta } from '@nuxtjs/composition-api'
// component must be defined via defineComponent
export default defineComponent({
head: {} // has to exist to work
setup() {
// Setting some meta data
useMeta({ title: 'My awesome site' })
// Get refs for meta data
const { title } = useMeta()
title.value = 'New title'
// Computed Meta
const message = ref('')
useMeta(() => ({ title: message.value }))
message.value = 'Changed!'
}
})
Canonical Links
@TheAlexLichter
@TheAlexLichter
import { useContext, computed, useRoute } from '@nuxtjs/composition-api'
export function useCurrentUrl () {
const { $config } = useContext()
const route = useRoute()
const { baseUrl } = $config
return computed(() => baseUrl + route.value.path)
}
@TheAlexLichter
import { useCurrentUrl } from '@/composables/useCurrentUrl'
import { watch, computed } from '@nuxtjs/composition-api'
export function useCanonical (link) {
const currentUrl = useCurrentUrl()
const urlWithSlash = computed(() => {
const url = currentUrl.value
const suffix = url.endsWith('/') ? '' : '/'
return url + suffix
})
const canonicalLinkIndex = computed(() => link.value.findIndex(l => l.rel === 'canonical'))
const removeLinkIfExists = () => {
const doesLinkExist = canonicalLinkIndex.value !== -1
if (!doesLinkExist) {
return
}
link.value.splice(canonicalLinkIndex.value, 1)
}
watch(currentUrl, () => {
removeLinkIfExists()
link.value.push({ rel: 'canonical', href: urlWithSlash.value })
}, { immediate: true })
}
@TheAlexLichter
import { onGlobalSetup, useMeta } from '@nuxtjs/composition-api'
import { useCanonical } from '@/composables/useCanonical'
export default function () {
onGlobalSetup(() => {
const { link } = useMeta()
useCanonical(link)
})
}
@TheAlexLichter
@TheAlexLichter
reactive reference with SSR in mind
import { ssrRef } from '@nuxtjs/composition-api'
const setFrom = ssrRef('')
setFrom.value = process.server ? 'server' : 'client'
const expensive = ssrRef(someExpensiveFunction)
@TheAlexLichter
Check out our docs
@TheAlexLichter
Some helpers are using keys under the hood for serialization:
shallowSsrRef, ssrPromise, ssrRef, useAsync
When using them in global composables, ensure that they use a unique key
@TheAlexLichter
function useGetFancyResult() {
// The key for ssrRef is injected automatically
// But it is NOT UNIQUE per function call by default
const result = ssrRef('')
// ...
return result
}
const a = useGetFancyResult()
const b = useGetFancyResult()
b.value = 'oh oh!'
// Now, a and b will have the same value
// Because they use the SAME KEY internally
function useGetFancyResult() {
// The key for ssrRef is injected automatically
// But it is NOT UNIQUE per function call by default
const result = ssrRef('', 'ajsiodasjiod') // BABEL
// ...
return result
}
const a = useGetFancyResult()
const b = useGetFancyResult()
b.value = 'oh oh!'
// Now, a and b will have the same value
// Because they use the SAME KEY internally
@TheAlexLichter
function useGetFancyResult(key) {
// The key for ssrRef is injected automatically
// But it is NOT UNIQUE per function call by default
const result = ssrRef('', key)
// ...
return result
}
const a = useGetFancyResult('a')
const b = useGetFancyResult('b')
b.value = 'no problem!'
// Now, a and b will NOT have the same value
// Because the keys are DIFFERENT
@TheAlexLichter
@TheAlexLichter
Q&A after the
next slide!
@TheAlexLichter
By Alexander Lichter
Vue 3 and the Composition API are out for a bit now. And while the ecosystem is catching up, more and more libraries provide support for the Composition API through composables. But what is about Server-Side Rendering with the composition API, especially when using Nuxt.js? I got you covered! In this talk, you will learn about the @nuxtjs/composition-api package that provides you a couple of nifty composables as well as SSR support in conjunction with the Composition API itself. Together we look into the new additions of the packages as well as several use cases and examples of how to use the Composition API with Nuxt.js.
Web Engineering Consultant • Founder • Nuxt team • Speaker