diff --git a/docs/content/1.getting-started/5.seo-meta.md b/docs/content/1.getting-started/5.seo-meta.md
index 6b4ab49e328..636a5b56feb 100644
--- a/docs/content/1.getting-started/5.seo-meta.md
+++ b/docs/content/1.getting-started/5.seo-meta.md
@@ -1,79 +1,76 @@
---
navigation.icon: uil:file-search-alt
-description: Nuxt provides good default values for meta tags, but you can override these if you need to.
+description: Improve your Nuxt app's SEO with powerful head config, composables and components.
---
# SEO and Meta
-Out-of-the-box, Nuxt provides good default values for `charset` and `viewport` meta tags, but you can override these if you need to, as well as customize other meta tags for your site in several different ways.
+Improve your Nuxt app's SEO with powerful head config, composables and components.
-:ReadMore{link="/api/configuration/nuxt-config#head"}
+## App Head
-## `useHead` Composable
+Providing an [app.head](/api/configuration/nuxt-config#head) property in your `nuxt.config.ts` allows you to customize the head for your entire app.
-Within your `setup` function, you can call `useHead` with an object of meta properties with keys corresponding to meta tags: `title`, `titleTemplate`, `base`, `script`, `noscript`, `style`, `meta` and `link`, as well as `htmlAttrs` and `bodyAttrs`. There are also two shorthand properties, `charset` and `viewport`, which set the corresponding meta tags. Alternatively, you can pass a function returning the object for reactive metadata.
-
-For example:
+::alert{type=info}
+This method does not allow you to provide reactive data, if you need global reactive data you can use `useHead` in `app.vue`.
+::
-```vue
-
-```
+Shortcuts are available to make configuration easier: `charset` and `viewport`. You can also provide any other key listed below in [Types](#types).
-::ReadMore{link="/api/composables/use-head"}
-::
+### Defaults
-## Title Templates
+Out-of-the-box, Nuxt provides sane defaults, which you can override if needed.
-You can use the `titleTemplate` option to provide a dynamic template for customizing the title of your site, for example, by adding the name of your site to the title of every page.
+- `charset`: `utf-8`
+- `viewport`: `width=device-width, initial-scale=1`
-The `titleTemplate` can either be a string, where `%s` is replaced with the title, or a function. If you want to use a function (for full control), then this cannot be set in your `nuxt.config`, and it is recommended instead to set it within your `app.vue` file, where it will apply to all pages on your site:
+### Example
-```vue [app.vue]
-
+ }
+})
```
-Now, if you set the title to `My Page` with `useHead` on another page of your site, the title would appear as 'My Page - Site Title' in the browser tab. You could also pass `null` to default to the site title.
+:ReadMore{link="/api/configuration/nuxt-config/#head"}
-## Body Meta Tags
+## Composable: `useHead`
-You can use the `body: true` option on the `link` and `script` meta tags to append them to the end of the `
` tag.
+The `useHead` composable function allows you to manage your head tags in a programmatic and reactive way, powered by [@vueuse/head](https://github.com/vueuse/head).
-For example:
+As with all composables, it can only be used with a components `setup` and lifecycle hooks.
-```vue
-
```
-## Meta Components
+::ReadMore{link="/api/composables/use-head"}
+::
+
+## Components
Nuxt provides ``, ``, `
+ ```
+
+ ```vue [Components]
+
+
+
+
+
+
+ ```
+
+::
+
+### Title Templates
+
+You can use the `titleTemplate` option to provide a dynamic template for customizing the title of your site. for example, by adding the name of your site to the title of every page.
+
+The `titleTemplate` can either be a string, where `%s` is replaced with the title, or a function.
+
+If you want to use a function (for full control), then this cannot be set in your `nuxt.config`, and it is recommended instead to set it within your `app.vue` file, where it will apply to all pages on your site:
+
+::code-group
+
+ ```vue [useHead]
+
+ ```
+
+::
+
+Now, if you set the title to `My Page` with `useHead` on another page of your site, the title would appear as 'My Page - Site Title' in the browser tab. You could also pass `null` to default to the site title.
+
+### Body Tags
+
+You can use the `body: true` option on the `link` and `script` meta tags to append them to the end of the `` tag.
+
+For example:
+
+::code-group
+
+ ```vue [useHead]
+
+ ```
+
+ ```vue [Components]
+
+
+
+
+
+ ```
+
+::
+
+## Examples
+
+### Usage With `definePageMeta`
Within your `pages/` directory, you can use `definePageMeta` along with `useHead` to set metadata based on the current route.
@@ -132,3 +242,60 @@ useHead({
:LinkExample{link="/examples/composables/use-head"}
:ReadMore{link="/guide/directory-structure/pages/#page-metadata"}
+
+### Add Dynamic Title
+
+In the example below, `titleTemplate` is set either as a string with the `%s` placeholder or as a `function`, which allows greater flexibility in setting the page title dynamically for each route of your Nuxt app:
+
+```vue [app.vue]
+
+```
+
+`nuxt.config` is also used as an alternative way of setting the page title. However, `nuxt.config` does not allow the page title to be dynamic. Therefore, it is recommended to use `titleTemplate` in the `app.vue` file to add a dynamic title, which is then applied to all routes of your Nuxt app.
+
+### Add External CSS
+
+The example below inserts Google Fonts using the `link` property of the `useHead` composable:
+
+::code-group
+
+ ```vue [useHead]
+
+ ```
+
+ ```vue [Components]
+
+
+
+
+
+
+ ```
+
+::
diff --git a/docs/content/3.api/1.composables/use-head.md b/docs/content/3.api/1.composables/use-head.md
index f34b26f0f9f..8fb170a0df2 100644
--- a/docs/content/3.api/1.composables/use-head.md
+++ b/docs/content/3.api/1.composables/use-head.md
@@ -1,5 +1,5 @@
---
-description: useHead customizes the head properties of individual pages of your Nuxt app.
+description: useHead customizes the head properties of individual pages of your Nuxt app.
---
# `useHead`
@@ -10,28 +10,32 @@ Nuxt provides the `useHead` composable to add and customize the head properties
`useHead` only works during `setup` or `Lifecycle Hooks`.
::
+::ReadMore{link="/getting-started/seo-meta"}
+::
+
## Type
```ts
-useHead(meta: Computable): void
-
-interface MetaObject extends Record {
- charset?: string
- viewport?: string
- meta?: Array>
- link?: Array>
- style?: Array>
- script?: Array>
- noscript?: Array>
- titleTemplate?: string | ((title: string) => string)
+useHead(meta: MaybeComputedRef): void
+```
+
+Below are the non-reactive types for `useMeta`. See [zhead](https://github.com/harlan-zw/zhead/tree/main/packages/schema/src) for more detailed types.
+
+```ts
+interface MetaObject {
title?: string
- bodyAttrs?: Record
- htmlAttrs?: Record
+ titleTemplate?: string | ((title?: string) => string)
+ base?: Base
+ link?: Link[]
+ meta?: Meta[]
+ style?: Style[]
+ script?: Script[]
+ noscript?: Noscript[]
+ htmlAttrs?: HtmlAttributes
+ bodyAttrs?: BodyAttributes
}
```
-Application-wide configuration of the head metadata is possible through [nuxt.config](/api/configuration/nuxt-config#head), or by placing the `useHead` in the `app.vue` file.
-
::alert{type=info}
The properties of `useHead` can be dynamic, accepting `ref`, `computed` and `reactive` properties. `meta` parameter can also accept a function returning an object to make the entire object reactive.
::
@@ -44,28 +48,10 @@ The properties of `useHead` can be dynamic, accepting `ref`, `computed` and `rea
An object accepting the following head metadata:
-- `charset`
-
- **Type**: `string`
-
- **Default**: `utf-8`
-
- Specifies character encoding for the HTML document.
-
-- `viewport`
-
- **Type**: `string`
-
- **Default**: `width=device-width, initial-scale=1`
-
- Configures the viewport (the user's visible area of a web page).
-
- `meta`
**Type**: `Array>`
- **Default**: `width=device-width, initial-scale=1`
-
Each element in the array is mapped to a newly-created `` tag, where object properties are mapped to the corresponding attributes.
- `link`
@@ -115,91 +101,3 @@ An object accepting the following head metadata:
**Type**: `Record`
Sets attributes of the `` tag. Each object property is mapped to the corresponding attribute.
-
-## Examples
-
-### Customize Metadata
-
-The example below changes the website's `title` and `description` using `meta` option of the `useHead` composable:
-
-```vue
-
-```
-
-### Add Dynamic Title
-
-In the example below, `titleTemplate` is set either as a string with the `%s` placeholder or as a `function`, which allows greater flexibility in setting the page title dynamically for each route of your Nuxt app:
-
-```vue [app.vue]
-
-```
-
-`nuxt.config` is also used as an alternative way of setting the page title. However, `nuxt.config` does not allow the page title to be dynamic. Therefore, it is recommended to use `titleTemplate` in the `app.vue` file to add a dynamic title, which is then applied to all routes of your Nuxt app.
-
-### Add External CSS
-
-The example below inserts Google Fonts using the `link` property of the `useHead` composable:
-
-```vue
-
-```
-
-### Add Third-party Script
-
-The example below inserts a third-party script using the `script` property of the `useHead` composable:
-
-```vue
-
-```
-
-You can use the `body: true` option to add the above script at the end of the `` tag.
-
-:ReadMore{link="/guide/features/head-management"}
diff --git a/docs/content/4.examples/3.composables/use-head.md b/docs/content/4.examples/3.composables/use-head.md
index 529eabc4cd3..6251ed68ca9 100644
--- a/docs/content/4.examples/3.composables/use-head.md
+++ b/docs/content/4.examples/3.composables/use-head.md
@@ -4,10 +4,6 @@ description: "This example shows how to use useHead and Nuxt built-in components
title: "useHead"
---
-::alert{type=info icon=👉}
-Learn more about [meta tags](/guide/features/head-management#meta-components).
-::
-
::ReadMore{link="/api/composables/use-head"}
::
diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json
index c393a32b54d..e1c3816f986 100644
--- a/packages/nuxt/package.json
+++ b/packages/nuxt/package.json
@@ -44,7 +44,7 @@
"@nuxt/vite-builder": "3.0.0-rc.11",
"@vue/reactivity": "^3.2.40",
"@vue/shared": "^3.2.40",
- "@vueuse/head": "^0.7.12",
+ "@vueuse/head": "~1.0.0-rc.7",
"chokidar": "^3.5.3",
"cookie-es": "^0.5.0",
"defu": "^6.1.0",
diff --git a/packages/nuxt/src/head/runtime/components.ts b/packages/nuxt/src/head/runtime/components.ts
index db72e444ce8..637660b4745 100644
--- a/packages/nuxt/src/head/runtime/components.ts
+++ b/packages/nuxt/src/head/runtime/components.ts
@@ -1,13 +1,13 @@
-import { defineComponent, PropType } from 'vue'
-import type { SetupContext } from 'vue'
+import { defineComponent } from 'vue'
+import type { PropType, SetupContext } from 'vue'
import { useHead } from './composables'
import type {
- Props,
- FetchPriority,
CrossOrigin,
+ FetchPriority,
HTTPEquiv,
- ReferrerPolicy,
LinkRelationship,
+ Props,
+ ReferrerPolicy,
Target
} from './types'
@@ -88,7 +88,9 @@ export const Script = defineComponent({
/** @deprecated **/
charset: String,
/** @deprecated **/
- language: String
+ language: String,
+ body: Boolean,
+ renderPriority: [String, Number]
},
setup: setupForUseMeta((props, { slots }) => {
const script = { ...props }
@@ -111,7 +113,9 @@ export const NoScript = defineComponent({
inheritAttrs: false,
props: {
...globalProps,
- title: String
+ title: String,
+ body: Boolean,
+ renderPriority: [String, Number]
},
setup: setupForUseMeta((props, { slots }) => {
const noscript = { ...props }
@@ -157,7 +161,9 @@ export const Link = defineComponent({
/** @deprecated **/
methods: String,
/** @deprecated **/
- target: String as PropType
+ target: String as PropType,
+ body: Boolean,
+ renderPriority: [String, Number]
},
setup: setupForUseMeta(link => ({
link: [link]
@@ -205,7 +211,9 @@ export const Meta = defineComponent({
charset: String,
content: String,
httpEquiv: String as PropType,
- name: String
+ name: String,
+ body: Boolean,
+ renderPriority: [String, Number]
},
setup: setupForUseMeta((props) => {
const meta = { ...props }
@@ -235,7 +243,9 @@ export const Style = defineComponent({
scoped: {
type: Boolean,
default: undefined
- }
+ },
+ body: Boolean,
+ renderPriority: [String, Number]
},
setup: setupForUseMeta((props, { slots }) => {
const style = { ...props }
@@ -269,7 +279,8 @@ export const Html = defineComponent({
...globalProps,
manifest: String,
version: String,
- xmlns: String
+ xmlns: String,
+ renderPriority: [String, Number]
},
setup: setupForUseMeta(htmlAttrs => ({ htmlAttrs }), true)
})
@@ -279,6 +290,9 @@ export const Body = defineComponent({
// eslint-disable-next-line vue/no-reserved-component-names
name: 'Body',
inheritAttrs: false,
- props: globalProps,
+ props: {
+ ...globalProps,
+ renderPriority: [String, Number]
+ },
setup: setupForUseMeta(bodyAttrs => ({ bodyAttrs }), true)
})
diff --git a/packages/nuxt/src/head/runtime/composables.ts b/packages/nuxt/src/head/runtime/composables.ts
index 8eaffa58283..d404b4f2f09 100644
--- a/packages/nuxt/src/head/runtime/composables.ts
+++ b/packages/nuxt/src/head/runtime/composables.ts
@@ -1,11 +1,7 @@
-import { isFunction } from '@vue/shared'
-import { computed } from 'vue'
-import type { ComputedGetter, ComputedRef } from '@vue/reactivity'
import type { MetaObject } from '@nuxt/schema'
+import type { MaybeComputedRef } from '@vueuse/head'
import { useNuxtApp } from '#app'
-type Computable = T extends Record ? ComputedGetter | { [K in keyof T]: T[K] | ComputedRef } : T
-
/**
* You can pass in a meta object, which has keys corresponding to meta tags:
* `title`, `base`, `script`, `style`, `meta` and `link`, as well as `htmlAttrs` and `bodyAttrs`.
@@ -13,13 +9,12 @@ type Computable = T extends Record ? ComputedGetter | { [K in
* Alternatively, for reactive meta state, you can pass in a function
* that returns a meta object.
*/
-export function useHead (meta: Computable) {
- const resolvedMeta = isFunction(meta) ? computed(meta) : meta
- useNuxtApp()._useHead(resolvedMeta)
+export function useHead (meta: MaybeComputedRef) {
+ useNuxtApp()._useHead(meta)
}
// TODO: remove useMeta support when Nuxt 3 is stable
/** @deprecated Please use new `useHead` composable instead */
-export function useMeta (meta: Computable) {
+export function useMeta (meta: MaybeComputedRef) {
return useHead(meta)
}
diff --git a/packages/nuxt/src/head/runtime/lib/vue-meta.plugin.ts b/packages/nuxt/src/head/runtime/lib/vue-meta.plugin.ts
index 9569534d954..ecdc4d689ae 100644
--- a/packages/nuxt/src/head/runtime/lib/vue-meta.plugin.ts
+++ b/packages/nuxt/src/head/runtime/lib/vue-meta.plugin.ts
@@ -2,10 +2,13 @@ import { createApp } from 'vue'
import { createMetaManager } from 'vue-meta'
import type { MetaObject } from '..'
import { defineNuxtPlugin } from '#app'
+// @ts-expect-error untyped
+import { appHead } from '#build/nuxt.config.mjs'
export default defineNuxtPlugin((nuxtApp) => {
// @ts-expect-error missing resolver
const manager = createMetaManager(process.server)
+ manager.addMeta(appHead)
nuxtApp.vueApp.use(manager)
diff --git a/packages/nuxt/src/head/runtime/lib/vueuse-head.plugin.ts b/packages/nuxt/src/head/runtime/lib/vueuse-head.plugin.ts
index e1e6ebce866..c055d3ef62b 100644
--- a/packages/nuxt/src/head/runtime/lib/vueuse-head.plugin.ts
+++ b/packages/nuxt/src/head/runtime/lib/vueuse-head.plugin.ts
@@ -1,52 +1,58 @@
+import type { HeadEntryOptions, MaybeComputedRef } from '@vueuse/head'
import { createHead, renderHeadToString } from '@vueuse/head'
-import { computed, ref, watchEffect, onBeforeUnmount, getCurrentInstance, ComputedGetter } from 'vue'
-import defu from 'defu'
-import type { MetaObject } from '..'
-import { defineNuxtPlugin } from '#app'
+import { onBeforeUnmount, getCurrentInstance } from 'vue'
+import type { MetaObject } from '@nuxt/schema'
+import { defineNuxtPlugin, useRouter } from '#app'
+// @ts-expect-error untyped
+import { appHead } from '#build/nuxt.config.mjs'
export default defineNuxtPlugin((nuxtApp) => {
const head = createHead()
+ head.addEntry(appHead, { resolved: true })
+
nuxtApp.vueApp.use(head)
- let headReady = false
- nuxtApp.hooks.hookOnce('app:mounted', () => {
- watchEffect(() => { head.updateDOM() })
- headReady = true
- })
-
- nuxtApp._useHead = (_meta: MetaObject | ComputedGetter) => {
- const meta = ref(_meta)
- const headObj = computed(() => {
- const overrides: MetaObject = { meta: [] }
- if (meta.value.charset) {
- overrides.meta!.push({ key: 'charset', charset: meta.value.charset })
- }
- if (meta.value.viewport) {
- overrides.meta!.push({ name: 'viewport', content: meta.value.viewport })
- }
- return defu(overrides, meta.value)
- })
- head.addHeadObjs(headObj as any)
+ if (process.client) {
+ // pause dom updates until page is ready and between page transitions
+ let pauseDOMUpdates = true
+ head.hooks['before:dom'].push(() => !pauseDOMUpdates)
+ nuxtApp.hooks.hookOnce('app:mounted', () => {
+ pauseDOMUpdates = false
+ head.updateDOM()
- if (process.server) { return }
+ // start pausing DOM updates when route changes (trigger immediately)
+ useRouter().beforeEach(() => {
+ pauseDOMUpdates = true
+ })
+ // watch for new route before unpausing dom updates (triggered after suspense resolved)
+ useRouter().afterEach(() => {
+ pauseDOMUpdates = false
+ head.updateDOM()
+ })
+ })
+ }
- if (headReady) {
- watchEffect(() => { head.updateDOM() })
+ nuxtApp._useHead = (_meta: MaybeComputedRef, options: HeadEntryOptions) => {
+ if (process.server) {
+ head.addEntry(_meta, options)
+ return
}
+ const cleanUp = head.addReactiveEntry(_meta, options)
+
const vm = getCurrentInstance()
if (!vm) { return }
onBeforeUnmount(() => {
- head.removeHeadObjs(headObj as any)
+ cleanUp()
head.updateDOM()
})
}
if (process.server) {
- nuxtApp.ssrContext!.renderMeta = () => {
- const meta = renderHeadToString(head)
+ nuxtApp.ssrContext!.renderMeta = async () => {
+ const meta = await renderHeadToString(head)
return {
...meta,
// resolves naming difference with NuxtMeta and @vueuse/head
diff --git a/packages/nuxt/src/head/runtime/plugin.ts b/packages/nuxt/src/head/runtime/plugin.ts
index c537bc20382..5ad5c81ef62 100644
--- a/packages/nuxt/src/head/runtime/plugin.ts
+++ b/packages/nuxt/src/head/runtime/plugin.ts
@@ -1,9 +1,7 @@
-import { computed, getCurrentInstance, markRaw } from 'vue'
+import { getCurrentInstance } from 'vue'
import * as Components from './components'
import { useHead } from './composables'
import { defineNuxtPlugin, useNuxtApp } from '#app'
-// @ts-ignore
-import { appHead } from '#build/nuxt.config.mjs'
type MetaComponents = typeof Components
declare module '@vue/runtime-core' {
@@ -20,7 +18,7 @@ const metaMixin = {
const nuxtApp = useNuxtApp()
const source = typeof options.head === 'function'
- ? computed(() => options.head(nuxtApp))
+ ? () => options.head(nuxtApp)
: options.head
useHead(source)
@@ -28,8 +26,6 @@ const metaMixin = {
}
export default defineNuxtPlugin((nuxtApp) => {
- useHead(markRaw({ title: '', ...appHead }))
-
nuxtApp.vueApp.mixin(metaMixin)
for (const name in Components) {
diff --git a/packages/schema/build.config.ts b/packages/schema/build.config.ts
index 80bbf5fb7b2..82e22e2b494 100644
--- a/packages/schema/build.config.ts
+++ b/packages/schema/build.config.ts
@@ -22,6 +22,7 @@ export default defineBuildConfig({
'vue-meta',
'vue-router',
'vue-bundle-renderer',
+ '@vueuse/head',
'vue',
'hookable',
'nitropack',
diff --git a/packages/schema/package.json b/packages/schema/package.json
index 553bc596110..ec8e035aa99 100644
--- a/packages/schema/package.json
+++ b/packages/schema/package.json
@@ -17,6 +17,7 @@
"@types/lodash.template": "^4",
"@types/semver": "^7",
"@vitejs/plugin-vue": "^3.1.2",
+ "@vueuse/head": "~1.0.0-rc.7",
"unbuild": "latest",
"vite": "~3.1.7"
},
diff --git a/packages/schema/src/config/_app.ts b/packages/schema/src/config/_app.ts
index 46adadd8948..176afd8ed12 100644
--- a/packages/schema/src/config/_app.ts
+++ b/packages/schema/src/config/_app.ts
@@ -2,8 +2,7 @@ import { resolve, join } from 'pathe'
import { existsSync, readdirSync } from 'node:fs'
import defu from 'defu'
import { defineUntypedSchema } from 'untyped'
-
-import { MetaObject } from '../types/meta'
+import type { AppHeadMetaObject } from '../types/meta'
export default defineUntypedSchema({
/**
@@ -116,7 +115,7 @@ export default defineUntypedSchema({
*/
head: {
$resolve: async (val, get) => {
- const resolved: Required = defu(val, await get('meta'), {
+ const resolved: Required = defu(val, await get('meta'), {
meta: [],
link: [],
style: [],
@@ -124,9 +123,15 @@ export default defineUntypedSchema({
noscript: []
})
- resolved.charset = resolved.charset ?? resolved.meta.find(m => m.charset)?.charset ?? 'utf-8'
- resolved.viewport = resolved.viewport ?? resolved.meta.find(m => m.name === 'viewport')?.content ?? 'width=device-width, initial-scale=1'
- resolved.meta = resolved.meta.filter(m => m && m.name !== 'viewport' && !m.charset)
+ // provides default charset and viewport if not set
+ if (!resolved.meta.find(m => m.charset)?.charset) {
+ resolved.meta.unshift({ charset: resolved.charset || 'utf-8' })
+ }
+ if (!resolved.meta.find(m => m.name === 'viewport')?.content) {
+ resolved.meta.unshift({ name: 'viewport', content: resolved.viewport || 'width=device-width, initial-scale=1' })
+ }
+
+ resolved.meta = resolved.meta.filter(Boolean)
resolved.link = resolved.link.filter(Boolean)
resolved.style = resolved.style.filter(Boolean)
resolved.script = resolved.script.filter(Boolean)
@@ -237,7 +242,7 @@ export default defineUntypedSchema({
},
/**
- * @type {typeof import('../src/types/meta').MetaObject}
+ * @type {typeof import('../src/types/meta').AppHeadMetaObject}
* @version 3
* @deprecated - use `head` instead
*/
diff --git a/packages/schema/src/types/config.ts b/packages/schema/src/types/config.ts
index 129844dcc47..97f7e02581c 100644
--- a/packages/schema/src/types/config.ts
+++ b/packages/schema/src/types/config.ts
@@ -2,7 +2,7 @@ import type { KeepAliveProps, TransitionProps } from 'vue'
import { ConfigSchema } from '../../schema/config'
import type { ServerOptions as ViteServerOptions, UserConfig as ViteUserConfig } from 'vite'
import type { Options as VuePluginOptions } from '@vitejs/plugin-vue'
-import type { MetaObject } from './meta'
+import type { AppHeadMetaObject } from './meta'
import type { Nuxt } from './nuxt'
type DeepPartial = T extends Function ? T : T extends Record ? { [P in keyof T]?: DeepPartial } : T
@@ -82,7 +82,7 @@ export interface AppConfigInput extends Record {
}
export interface NuxtAppConfig {
- head: MetaObject
+ head: AppHeadMetaObject
layoutTransition: boolean | TransitionProps
pageTransition: boolean | TransitionProps
keepalive: boolean | KeepAliveProps
diff --git a/packages/schema/src/types/meta.ts b/packages/schema/src/types/meta.ts
index 1ae2d75854d..d79bfe55b72 100644
--- a/packages/schema/src/types/meta.ts
+++ b/packages/schema/src/types/meta.ts
@@ -1,4 +1,20 @@
-export interface MetaObject extends Record {
+import type { HeadObjectPlain, HeadObject } from '@vueuse/head'
+
+export interface HeadAugmentations {
+ // runtime type modifications
+ base?: {}
+ link?: {}
+ meta?: {}
+ style?: {}
+ script?: {}
+ noscript?: {}
+ htmlAttrs?: {}
+ bodyAttrs?: {}
+}
+
+export type MetaObjectRaw = HeadObjectPlain
+
+export type AppHeadMetaObject = MetaObjectRaw & {
/**
* The character encoding in which the document is encoded => ``
*
@@ -12,21 +28,71 @@ export interface MetaObject extends Record {
* @default `'width=device-width, initial-scale=1'`
*/
viewport?: string
+}
- /** Each item in the array maps to a newly-created `` element, where object properties map to attributes. */
- meta?: Array>
- /** Each item in the array maps to a newly-created `` element, where object properties map to attributes. */
- link?: Array>
- /** Each item in the array maps to a newly-created `