mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-24 22:55:13 +00:00
337 lines
8.8 KiB
Markdown
337 lines
8.8 KiB
Markdown
---
|
|
navigation.icon: uil:file-search-alt
|
|
description: Improve your Nuxt app's SEO with powerful head config, composables and components.
|
|
---
|
|
|
|
# SEO and Meta
|
|
|
|
Improve your Nuxt app's SEO with powerful head config, composables and components.
|
|
|
|
## App Head
|
|
|
|
Providing an [app.head](/docs/api/configuration/nuxt-config#head) property in your `nuxt.config.ts` allows you to customize the head for your entire app.
|
|
|
|
::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`.
|
|
::
|
|
|
|
Shortcuts are available to make configuration easier: `charset` and `viewport`. You can also provide any of the keys listed below in [Types](#types).
|
|
|
|
### Defaults
|
|
|
|
Out-of-the-box, Nuxt provides sane defaults, which you can override if needed.
|
|
|
|
- `charset`: `utf-8`
|
|
- `viewport`: `width=device-width, initial-scale=1`
|
|
|
|
### Example
|
|
|
|
```ts{}[nuxt.config.ts]
|
|
export default defineNuxtConfig({
|
|
app: {
|
|
head: {
|
|
charset: 'utf-16',
|
|
viewport: 'width=500, initial-scale=1',
|
|
title: 'My App',
|
|
meta: [
|
|
// <meta name="description" content="My amazing site">
|
|
{ name: 'description', content: 'My amazing site.' }
|
|
],
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
:ReadMore{link="/docs/api/configuration/nuxt-config/#head"}
|
|
|
|
## Composable: `useHead`
|
|
|
|
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).
|
|
|
|
As with all composables, it can only be used with a components `setup` and lifecycle hooks.
|
|
|
|
### Example
|
|
|
|
```vue{}[app.vue]
|
|
<script setup lang="ts">
|
|
useHead({
|
|
title: 'My App',
|
|
meta: [
|
|
{ name: 'description', content: 'My amazing site.' }
|
|
],
|
|
bodyAttrs: {
|
|
class: 'test'
|
|
},
|
|
script: [ { children: 'console.log(\'Hello world\')' } ]
|
|
})
|
|
</script>
|
|
```
|
|
|
|
::ReadMore{link="/docs/api/composables/use-head"}
|
|
::
|
|
|
|
## Composable: `useSeoMeta` and `useServerSeoMeta`
|
|
|
|
The `useSeoMeta` and `useServerSeoMeta` composables let you define your site's SEO meta tags as a flat object with full TypeScript support.
|
|
|
|
This helps you avoid typos and common mistakes, such as using `name` instead of `property`.
|
|
|
|
In most instances, the meta does not need to be reactive as robots will only scan the initial load. So we recommend using `useServerSeoMeta` as a performance-focused utility that will not do anything (or return a `head` object) on the client.
|
|
|
|
### Example
|
|
|
|
#### Simple
|
|
|
|
```vue{}[app.vue]
|
|
<script setup lang="ts">
|
|
useServerSeoMeta({
|
|
title: 'My Amazing Site',
|
|
ogTitle: 'My Amazing Site',
|
|
description: 'This is my amazing site, let me tell you all about it.',
|
|
ogDescription: 'This is my amazing site, let me tell you all about it.',
|
|
ogImage: 'https://example.com/image.png',
|
|
twitterCard: 'summary_large_image',
|
|
})
|
|
</script>
|
|
```
|
|
|
|
#### Reactive
|
|
|
|
When inserting tags that are reactive, for example, from an API request, you should
|
|
use the computed getter syntax, the same as `useHead`.
|
|
|
|
```vue{}[app.vue]
|
|
<script setup lang="ts">
|
|
const data = useFetch(() => $fetch('/api/example'))
|
|
|
|
useServerSeoMeta({
|
|
ogTitle: () => `${data.value?.title} - My Site`,
|
|
description: () => data.value?.description,
|
|
ogDescription: () => data.value?.description,
|
|
})
|
|
</script>
|
|
```
|
|
|
|
::ReadMore{link="https://unhead.harlanzw.com/guide/composables/use-seo-meta"}
|
|
::
|
|
|
|
## Components
|
|
|
|
Nuxt provides `<Title>`, `<Base>`, `<NoScript>`, `<Style>`, `<Meta>`, `<Link>`, `<Body>`, `<Html>` and `<Head>` components so that you can interact directly with your metadata within your component's template.
|
|
|
|
Because these component names match native HTML elements, it is very important that they are capitalized in the template.
|
|
|
|
`<Head>` and `<Body>` can accept nested meta tags (for aesthetic reasons) but this has no effect on _where_ the nested meta tags are rendered in the final HTML.
|
|
|
|
### Example
|
|
|
|
<!-- @case-police-ignore html -->
|
|
|
|
```vue{}[app.vue]
|
|
<script setup>
|
|
const title = ref('Hello World')
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<Head>
|
|
<Title>{{ title }}</Title>
|
|
<Meta name="description" :content="title" />
|
|
<Style type="text/css" children="body { background-color: green; }" />
|
|
</Head>
|
|
|
|
<h1>{{ title }}</h1>
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
## Types
|
|
|
|
The below is the non-reactive types used for `useHead`, `app.head` and components.
|
|
|
|
```ts
|
|
interface MetaObject {
|
|
title?: string
|
|
titleTemplate?: string | ((title?: string) => string)
|
|
base?: Base
|
|
link?: Link[]
|
|
meta?: Meta[]
|
|
style?: Style[]
|
|
script?: Script[]
|
|
noscript?: Noscript[];
|
|
htmlAttrs?: HtmlAttributes;
|
|
bodyAttrs?: BodyAttributes;
|
|
}
|
|
```
|
|
|
|
See [zhead](https://github.com/harlan-zw/zhead/tree/main/packages/schema/src) for more detailed types.
|
|
|
|
## Features
|
|
|
|
### Reactivity
|
|
|
|
Reactivity is supported on all properties, as computed, computed getter refs and reactive.
|
|
|
|
It's recommended to use computed getters (`() => {}`) over computed (`computed(() => {})`).
|
|
|
|
::code-group
|
|
|
|
```vue [useHead]
|
|
<script setup lang="ts">
|
|
const desc = ref('My amazing site.')
|
|
|
|
useHead({
|
|
meta: [
|
|
{ name: 'description', content: desc }
|
|
],
|
|
})
|
|
</script>
|
|
```
|
|
|
|
```vue [Components]
|
|
<script setup>
|
|
const desc = ref('My amazing site.')
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<Meta name="description" :content="desc" />
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
::
|
|
|
|
### 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]
|
|
<script setup lang="ts">
|
|
useHead({
|
|
titleTemplate: (titleChunk) => {
|
|
return titleChunk ? `${titleChunk} - Site Title` : 'Site Title';
|
|
}
|
|
})
|
|
</script>
|
|
```
|
|
|
|
::
|
|
|
|
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 `<body>` tag.
|
|
|
|
For example:
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
useHead({
|
|
script: [
|
|
{
|
|
src: 'https://third-party-script.com',
|
|
body: true
|
|
}
|
|
]
|
|
})
|
|
</script>
|
|
```
|
|
|
|
## Examples
|
|
|
|
### Usage With `definePageMeta`
|
|
|
|
Within your `pages/` directory, you can use `definePageMeta` along with `useHead` to set metadata based on the current route.
|
|
|
|
For example, you can first set the current page title (this is extracted at build time via a macro, so it can't be set dynamically):
|
|
|
|
```vue{}[pages/some-page.vue]
|
|
<script setup>
|
|
definePageMeta({
|
|
title: 'Some Page'
|
|
})
|
|
</script>
|
|
```
|
|
|
|
And then in your layout file, you might use the route's metadata you have previously set:
|
|
|
|
```vue{}[layouts/default.vue]
|
|
<script setup>
|
|
const route = useRoute()
|
|
|
|
useHead({
|
|
meta: [{ property: 'og:title', content: `App Name - ${route.meta.title}` }]
|
|
})
|
|
</script>
|
|
```
|
|
|
|
::LinkExample{link="/docs/examples/composables/use-head"}
|
|
::
|
|
|
|
:ReadMore{link="/docs/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]
|
|
<script setup>
|
|
useHead({
|
|
// as a string,
|
|
// where `%s` is replaced with the title
|
|
titleTemplate: '%s - Site Title',
|
|
// ... or as a function
|
|
titleTemplate: (productCategory) => {
|
|
return productCategory
|
|
? `${productCategory} - Site Title`
|
|
: 'Site Title'
|
|
}
|
|
})
|
|
</script>
|
|
```
|
|
|
|
`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 shows how you might enable Google Fonts using either the `link` property of the `useHead` composable or using the `<Link>` component:
|
|
|
|
::code-group
|
|
|
|
```vue [useHead]
|
|
<script setup lang="ts">
|
|
useHead({
|
|
link: [
|
|
{
|
|
rel: 'preconnect',
|
|
href: 'https://fonts.googleapis.com'
|
|
},
|
|
{
|
|
rel: 'stylesheet',
|
|
href: 'https://fonts.googleapis.com/css2?family=Roboto&display=swap',
|
|
crossorigin: ''
|
|
}
|
|
]
|
|
})
|
|
</script>
|
|
```
|
|
|
|
```vue [Components]
|
|
<template>
|
|
<div>
|
|
<Link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<Link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" crossorigin="" />
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
::
|