About me
Alexander Lichter
Nuxt.js Core Maintainer
@TheAlexLichter
Web Dev Consultant
@TheAlexLichter
Status Quo
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
Goal of the Vue 2.x
Composition API Plugin
@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.
Composition API in Nuxt
@TheAlexLichter
- Already works fine with the CAPI plugin
- Can be enabled via Nuxt plugin
Problems
- Does not cover Nuxt functionalities
- What is with SSR?
Nuxt.js
Composition API
@TheAlexLichter
- Superset of @vue/composition-api
- Composables for nuxt-specific functionalities, e.g.
- Data fetching
- Accessing the context
- Setting meta tags
- Improved TypeScript support for plugins, middleware and more
- Under nuxt-community umbrella
- Currently maintained by @danielcroe
Goals
@TheAlexLichter
Similar to the @vue/composition-api package
- Provide a way to use CAPI with Nuxt + Vue 2
- Get feedback for future composables
- Make future migration to Nuxt 3 easier
Notice
@TheAlexLichter
The API of Nuxt-specific composables will
likely change before Nuxt 3 is released.
Talking about Nuxt 3...
@TheAlexLichter
Definition Helpers
@TheAlexLichter
- Improve DX (TS & Intellisense)
- Not strictly related to CAPI
- No-Op-Functions
Definition Helpers
Examples
@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
})
Common composables
@TheAlexLichter
fetch in Nuxt.js
@TheAlexLichter
import axios from 'axios'
export default {
data() {
return {
mountains: []
}
},
async fetch() {
this.mountains = await axios.get('https://api.nuxtjs.dev/mountains')
}
}
useFetch
@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 }
}
})
useFetch
@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 }
}
})
The Context
@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 }
}
})
useContext
@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')
}
})
useAsync
@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
useAsync
@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
useAsync
@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
useAsync
@TheAlexLichter
Not the exact equivalent of asyncData
Similarities
- Both are blocking on the server-side
- Both can be executed on server- and client-side
- No way to "refetch" data nor built-in state check like fetch
Differences
- useAsync is non-blocking on client-side while asyncData is blocking
- Data from useAsync is not merged with data
useStatic
@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 }
},
})
- factory function
- Parameter
- keyBase (unique across project)
useStatic
@TheAlexLichter
during generation
- the factory function (1) will be executed
- result will be saved as JSON named by the parameter (2) and keyBase (3)
- when slug would be sternburg, the file would be saved under /static/beers-sternburg.json in your dist folder
const beer = useStatic(
slug => axios.get(`https://api.nuxtjs.dev/beers/${slug}`), // 1
slug, // 2
'beers' // 3
)
useStatic
@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)
useStatic
@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
onGlobalSetup
@TheAlexLichter
- Useful for global injects "the Vue way" (provide/inject)
- Great place for global, layout-independent, composables
- Mostly used in Nuxt.js plugins
import { provide,onGlobalSetup, } from "@nuxtjs/composition-api";
import { DefaultApolloClient } from "@vue/apollo-composable";
export default defineNuxtPlugin(({ app }) => {
onGlobalSetup(() => {
provide(DefaultApolloClient, app.apolloProvider?.defaultClient);
});
});
useMeta
@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!'
}
})
Example
Canonical Links
@TheAlexLichter
- Importan for SEO
- Avoid duplicate content
- Define the "preferred URL"
@TheAlexLichter
import { useContext, computed } from '@nuxtjs/composition-api'
export function useCurrentUrl () {
const { route, $config } = useContext()
const { baseUrl } = $config
return computed(() => baseUrl + route.value.path)
}
useCurrentUrl
@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 })
}
useCanonical
@TheAlexLichter
import { onGlobalSetup, useMeta } from '@nuxtjs/composition-api'
import { useCanonical } from '@/composables/useCanonical'
export default function () {
onGlobalSetup(() => {
const { link } = useMeta()
useCanonical(link)
})
}
DONE!
New refs!
@TheAlexLichter
ssrRef
@TheAlexLichter
reactive reference with SSR in mind
- State from the server needs to be transferred from the server to the client (on SSR)
- ssrRef will do that automatically via window.__NUXT__
- Also accepts setter function
import { ssrRef } from '@nuxtjs/composition-api'
const setFrom = ssrRef('')
setFrom.value = process.server ? 'server' : 'client'
const expensive = ssrRef(someExpensiveFunction)
There are more!
@TheAlexLichter
- shallowSsrRef
- shallowRef + ssrRef
- reqRef
- ref that resets itself per request
- reqSsrRef
- reqRef + ssrRef
Check out our docs
Gotchas
@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
Gotchas
@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
Gotchas
@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
Summary
@TheAlexLichter
- Nuxt + Composition API is a thing
- It provides nuxt-specific functions
- And gives lots of space to build your own composables
- Give it a try!
Questions
@TheAlexLichter
Q&A after the
next slide!
Thank you!
@TheAlexLichter