Alexander Lichter
Web Engineering Consultant • Founder • Nuxt team • Speaker
Alexander Lichter
Nuxt.js Core Maintainer
@TheAlexLichter
Web Dev Consultant
@TheAlexLichter
Vue 3 + CAPI out for 2 months
but the ecosystem still has to catch up
Vue 2 + CAPI is a viable option now
More and more libraries provide composables, e.g. vue-apollo or vue-i18n
@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 { defineNuxtPlugin } from '@nuxtjs/composition-api'
export default defineNuxtPlugin((context) => {
// Plugin logic
})
import { defineNuxtMiddleware } from '@nuxtjs/composition-api'
export default defineNuxtMiddleware((ctx) => {
// Middleware logic
})
import { defineNuxtModule } from '@nuxtjs/composition-api'
export default defineNuxtModule<{ myOption: boolean }>(
(moduleOptions) => {
// module logic
})
@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
Access the route, store and more!
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 store, route, params and more
const context = useContext()
context.store.dispatch('someAction')
const { params } = context
const slug = computed(() => params.value.slug)
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 } = useContext()
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 } from '@nuxtjs/composition-api'
export function useCurrentUrl () {
const { route, $config } = useContext()
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