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