-
-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* docs: v2 * docs: v2 * chore: bump * chore: bump Nuxt SEO 2.1 * chore: docs progress * chore: progress commit * chore: progress commit * chore: sync lock * chore: missing dep
- Loading branch information
Showing
212 changed files
with
5,752 additions
and
12,456 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
--- | ||
title: Troubleshooting | ||
description: Learn how Unhead works under the hood. | ||
--- | ||
|
||
Unhead manages the `<head>`{lang="html"} of your site with support for SSR (Server-Side Rendering) and CSR (Client-Side Rendering). It's split into multiple packages to improve modularization and flexibility, allowing developers to choose and use only the components they actually need. | ||
|
||
The core package is framework-agnostic and should work in any setup. | ||
|
||
Framework packages are provided to improve the DX of using Unhead with the framework. | ||
|
||
Optional packages exist to add extra functionality and optimisations to Unhead. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
--- | ||
title: Understanding Async Context and useHead() | ||
description: Manage head tags with useHead() in Vue across async operations, component lifecycles, and server-side rendering. | ||
navigation: | ||
title: 'Async Context' | ||
--- | ||
|
||
## Introduction | ||
|
||
This injection pattern is fundamental to how Vue manages component state and dependencies in the Composition API. | ||
|
||
When we call `useHead()`{lang="ts"}, behind the scenes, it's calling the Vue [inject](https://vuejs.org/api/composition-api-dependency-injection.html#inject) function to get the Unhead instance | ||
attached to the Vue instance. | ||
|
||
```ts | ||
import { inject } from 'vue' | ||
|
||
function useHead(input) { | ||
const head = inject(HEAD_KEY) | ||
head.push(input) | ||
} | ||
|
||
function injectHead() { | ||
return inject(HEAD_KEY) | ||
} | ||
``` | ||
|
||
The `inject()`{lang="ts"} function keeps track of your Vue component instance, however, after async operations within lifecycle hooks or nested functions, Vue loses track of this context. | ||
|
||
```vue | ||
<script setup lang="ts"> | ||
import { useHead } from '@unhead/vue' | ||
import { onMounted } from 'vue' | ||
onMounted(async () => { | ||
await someAsyncOperation() | ||
// This will throw an error | ||
useHead({ | ||
title: 'My Title' | ||
}) | ||
}) | ||
</script> | ||
``` | ||
|
||
When trying to inject once Vue has lost the context, you'll receive an error from Unhead: | ||
|
||
> useHead() was called without provide context. | ||
We'll look at how we can prevent this error and ensure our head updates work correctly across async operations. | ||
|
||
## Use Top Level Await | ||
|
||
Vue's script setup handles async operations through [compile-time transforms](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md#top-level-await) that preserve the component instance context. As explained in Anthony's [article on async composition](https://antfu.me/posts/async-with-composition-api), this makes most async operations work seamlessly. | ||
|
||
At the top level of script setup, context is automatically preserved: | ||
|
||
```vue | ||
<script setup lang="ts"> | ||
import { useHead } from '@unhead/vue' | ||
// The compiler transforms this to preserve context | ||
await someAsyncOperation() | ||
useHead({ | ||
title: 'My Title' | ||
}) | ||
</script> | ||
``` | ||
|
||
This is the simplest and most effective way to handle async operations in Vue. | ||
|
||
## Using `effectScope()`{lang="ts"} | ||
|
||
The `effectScope()`{lang="ts"} API allows you to re-run a code block using the same component context, making it ideal for solving | ||
the async context loss issue. | ||
|
||
```vue | ||
<script setup lang="ts"> | ||
import { effectScope, onMounted } from 'vue' | ||
import { useHead } from '@unhead/vue' | ||
// Create an effect scope before any async operations | ||
const scope = effectScope() | ||
onMounted(async () => { | ||
const data = await fetchData() | ||
// Run all effects within this scope | ||
scope.run(() => { | ||
// All composables inside here will be properly cleaned up | ||
useHead({ | ||
title: data.title, | ||
meta: [ | ||
{ | ||
name: 'description', | ||
content: data.description | ||
} | ||
] | ||
}) | ||
}) | ||
}) | ||
</script> | ||
``` | ||
|
||
## Using `injectHead()`{lang="ts"} | ||
|
||
The `injectHead()` function lets us grab a reference to Unhead's instance before any async operations occur. | ||
|
||
Here's how to use it effectively: | ||
|
||
```vue | ||
<script setup lang="ts"> | ||
import { onMounted, injectHead } from '@unhead/vue' | ||
// Store the head instance at setup time | ||
const head = injectHead() | ||
// For simple updates, we don't need to pass the head instance | ||
useHead({ | ||
title: 'My Site' | ||
}) | ||
// Inside async functions, we must pass the head instance | ||
async function updatePageHead(id: string) { | ||
const data = await fetchPage(id) | ||
// Pass head as an option to maintain context | ||
useHead({ | ||
title: data.title, | ||
meta: [ | ||
{ | ||
name: 'description', | ||
content: data.description | ||
} | ||
] | ||
}, { head }) // The head instance ensures the update works | ||
} | ||
// The same applies for lifecycle hooks | ||
onMounted(async () => { | ||
const analyticsData = await loadAnalytics() | ||
useHead({ | ||
script: [ | ||
{ | ||
// Analytics script injection after load | ||
children: analyticsData.scriptContent | ||
} | ||
] | ||
}, { head }) | ||
}) | ||
</script> | ||
``` | ||
|
||
The key idea is that `injectHead()`{lang="ts"} should be called at the top level of your component, before any async operations. This ensures you have a stable reference to use throughout your component's lifecycle. | ||
|
||
## Using Reactive State | ||
|
||
A more elegant way to handle async head updates is to combine reactive state with useHead. This approach lets you define your head configuration once and have it automatically update as your data changes: | ||
|
||
```vue | ||
<script setup lang="ts"> | ||
import { ref, computed } from 'vue' | ||
// Initialize your reactive state | ||
const page = ref({ | ||
title: 'Loading...', | ||
description: '', | ||
image: '/placeholder.jpg' | ||
}) | ||
// Define head once with computed properties | ||
useHead({ | ||
// Title will automatically update when page.value.title changes | ||
title: computed(() => page.value.title), | ||
meta: [ | ||
{ | ||
name: 'description', | ||
content: computed(() => page.value.description) | ||
}, | ||
{ | ||
property: 'og:image', | ||
content: computed(() => page.value.image) | ||
} | ||
] | ||
}) | ||
// Your async operations just update the reactive state | ||
async function loadPage(id: string) { | ||
const data = await fetchPage(id) | ||
// Head updates automatically when we update the ref | ||
page.value = { | ||
title: data.title, | ||
description: data.description, | ||
image: data.image | ||
} | ||
} | ||
// Works great with watchers too | ||
watch(route, async () => { | ||
await loadPage(route.params.id) | ||
}) | ||
</script> | ||
``` | ||
|
||
### Pinia Store | ||
|
||
You can also use this pattern with more complex state management: | ||
|
||
```vue | ||
<script setup lang="ts"> | ||
import { storeToRefs } from 'pinia' | ||
import { usePageStore } from '@/stores/page' | ||
const store = usePageStore() | ||
// Destructure with storeToRefs to maintain reactivity | ||
const { title, description } = storeToRefs(store) | ||
useHead({ | ||
title, // Reactive store state automatically works | ||
meta: [ | ||
{ | ||
name: 'description', | ||
content: description | ||
} | ||
] | ||
}) | ||
// Now your store actions can update the head | ||
await store.fetchPage(id) | ||
</script> | ||
``` | ||
|
||
This reactive state pattern aligns well with Vue's composition API design and often results in cleaner, more maintainable code than manually updating the head after each async operation. | ||
|
||
## Async Context in Nuxt | ||
|
||
When using Nuxt, you don't need to worry about managing async context for head updates. This is because Nuxt attaches Unhead directly to the Nuxt application instance which is globally accessible regardless of the async operation. | ||
|
||
This decision allows you to use `useHead()`{lang="ts"} anywhere, including plugins, middleware and layouts without worrying about context. | ||
|
||
## Learn More | ||
|
||
The Vue team's solution for async context through script setup transforms is quite elegant. You can read more about the technical implementation in the [Script Setup RFC](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md#top-level-await). For a deeper understanding of how async context evolved in Vue's Composition API, check out Anthony Fu's [detailed exploration](https://antfu.me/posts/async-with-composition-api) of the topic. |
Oops, something went wrong.