Skip to content

Commit

Permalink
docs: migrate docs for v2 (#460)
Browse files Browse the repository at this point in the history
* 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
harlan-zw authored Jan 30, 2025
1 parent 399b927 commit ee4a6e2
Show file tree
Hide file tree
Showing 212 changed files with 5,752 additions and 12,456 deletions.
2 changes: 0 additions & 2 deletions docs/.npmrc

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Install Vue Unhead
title: Installing Unhead with Vue
description: Learn how to start using Unhead with Vue.
navigation:
title: 'Installation'
Expand Down Expand Up @@ -160,5 +160,6 @@ export default {
Your Vue app is now setup for head management, congrats! 🎉

Try next:

1. Optional: [Setup SSR](/setup/ssr/installation)
2. Explore the [Composables](/usage/composables/use-head)
12 changes: 12 additions & 0 deletions docs/0.angular/3.troubleshooting.md
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.
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
---
title: How Vue Unhead Works
description: Learn how to use reactivity with Vue Unhead.
navigation:
title: How it works
title: Vue Reactivity
description: Master Vue and Unhead by following the best practices.
---

## How Reactivity Works

When using any of the provided composables, such as `useHead`, full reactivity is provided out of the box.

All values are reactive and support:

- ref
- computed getter
- computed (not recommended)
Expand Down Expand Up @@ -37,3 +38,27 @@ It's recommended to avoid using `computed`, as you will have simpler and more pe
When using reactivity in SSR, only at the time of rendering the SSR tags will be refs be resolved.

When using reactivity in CSR, any ref changes will trigger a request to update the DOM.

## Avoid Watch and useHead

Avoid wrapping `useHead` in a watcher.

```ts
// bad
watch((title) => {
useHead({
title,
})
})
```

This is because each call of `useHead` will add a new entry, which has its own side effects.

Instead, use a computed getter.

```ts
// good
useHead({
title: () => title
})
```
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
---
title: Components
title: <Head> Component
description: Use the <Head> component to manage your head tags.
navigation:
title: 'Components'
title: '<Head> Component'
---

The Unhead Vue package exports a `<Head>` component that can be used to manage your head tags.
The Unhead Vue package exports a `<Head>`{lang="html"} component that can be used to manage your head tags.

While it's recommended to use the `useHead` composable as it offers a more flexible API with full TypeScript support,
the `<Head>` component may make more sense for your project.
the `<Head>`{lang="html"} component may make more sense for your project.

The component will takes any child elements that you would normally put in your actual `<head>` and renders them
The component will takes any child elements that you would normally put in your actual `<head>`{lang="html"} and renders them
with Unhead.

```vue
Expand All @@ -24,3 +24,4 @@ import { Head } from '@unhead/vue/components'
<meta name="description" content="My awesome site description">
</Head>
</template>
```
243 changes: 243 additions & 0 deletions docs/0.angular/guides/2.managing-context.md
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.
Loading

0 comments on commit ee4a6e2

Please sign in to comment.