SEO и мета-теги
Управление тегами в head в Nuxt реализовано на Unhead: разумные значения по умолчанию, композаблы и настройки для мета-тегов и SEO.
Конфиг Nuxt
Свойство app.head в nuxt.config.ts задаёт статичные теги head для всего приложения.
useHead() в app.vue.Здесь удобно задавать неизменяемые теги: заголовок по умолчанию, язык, favicon.
export default defineNuxtConfig({
app: {
head: {
title: 'Nuxt', // заголовок по умолчанию
htmlAttrs: {
lang: 'en',
},
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
],
},
},
})
Также можно задать любые ключи из раздела Types.
Теги по умолчанию
Nuxt подставляет часть тегов сам, чтобы сайт работал из коробки:
viewport:width=device-width, initial-scale=1charset:utf-8
Переопределить их можно через соответствующие ключи:
export default defineNuxtConfig({
app: {
head: {
charset: 'utf-16',
viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
},
},
})
useHead
Композабл useHead принимает реактивные данные и позволяет управлять тегами head из кода.
<script setup lang="ts">
useHead({
title: 'Моё приложение',
meta: [
{ name: 'description', content: 'Мой замечательный сайт.' },
],
bodyAttrs: {
class: 'test',
},
script: [{ innerHTML: 'console.log(\'Привет, мир\')' }],
})
</script>
Рекомендуется ознакомиться с композаблами useHead и useHeadSafe.
useSeoMeta
Композабл useSeoMeta задаёт SEO мета-теги объектом с полной типизацией.
Так проще избежать опечаток и типичных ошибок (например, name вместо property).
<script setup lang="ts">
useSeoMeta({
title: 'Мой замечательный сайт',
ogTitle: 'Мой замечательный сайт',
description: 'Это мой замечательный сайт — расскажу о нём.',
ogDescription: 'Это мой замечательный сайт — расскажу о нём.',
ogImage: 'https://example.com/image.png',
twitterCard: 'summary_large_image',
})
</script>
Компоненты
Помимо useHead, теги head можно задавать в шаблоне компонентами: <Title>, <Base>, <NoScript>, <Style>, <Meta>, <Link>, <Body>, <Html>, <Head>. Регистр важен — так мы не конфликтуем с нативными HTML-тегами.
<Head> и <Body> могут содержать вложенные мета-теги (для удобства), но порядок вложенности не влияет на то, где они окажутся в итоговом HTML.
<script setup lang="ts">
const title = ref('Привет, мир')
</script>
<template>
<div>
<Head>
<Title>{{ title }}</Title>
<Meta
name="description"
:content="title"
/>
<Style>
body { background-color: green; }
</Style>
</Head>
<h1>{{ title }}</h1>
</div>
</template>
Рекомендуется оборачивать теги в <Head> или <Html> — дедупликация будет предсказуемее.
key у компонента <Head>.Типы
Ниже — нереактивные типы для useHead, app.head и компонентов.
interface MetaObject {
title?: string
titleTemplate?: string | ((title?: string) => string)
templateParams?: Record<string, string | Record<string, string>>
base?: Base
link?: Link[]
meta?: Meta[]
style?: Style[]
script?: Script[]
noscript?: Noscript[]
htmlAttrs?: HtmlAttributes
bodyAttrs?: BodyAttributes
}
Подробные типы: @unhead/vue.
Возможности
Реактивность
Поддерживаются вычисляемые значения, геттеры и реактивные объекты.
<script setup lang="ts">
const description = ref('Мой замечательный сайт.')
useHead({
meta: [
{ name: 'description', content: description },
],
})
</script>
<script setup lang="ts">
const description = ref('Мой замечательный сайт.')
useSeoMeta({
description,
})
</script>
<script setup lang="ts">
const description = ref('Мой замечательный сайт.')
</script>
<template>
<div>
<Meta
name="description"
:content="description"
/>
</div>
</template>
Шаблон заголовка (titleTemplate)
Опция titleTemplate задаёт шаблон заголовка (например, добавление имени сайта на каждой странице).
titleTemplate может быть строкой (плейсхолдер %s подставит заголовок) или функцией.
Функцию нельзя задать в nuxt.config; задавайте её в app.vue, чтобы она применялась ко всем страницам:
<script setup lang="ts">
useHead({
titleTemplate: (titleChunk) => {
return titleChunk ? `${titleChunk} - Site Title` : 'Site Title'
},
})
</script>
Если на другой странице задать заголовок My Page через useHead, вкладка покажет «My Page - Site Title». Передача null даст только «Site Title».
Параметры шаблона (templateParams)
templateParams добавляет в titleTemplate плейсхолдеры помимо %s для более гибкого формирования заголовка.
<script setup lang="ts">
useHead({
titleTemplate: (titleChunk) => {
return titleChunk ? `${titleChunk} %separator %siteName` : '%siteName'
},
templateParams: {
siteName: 'Site Title',
separator: '-',
},
})
</script>
Теги в body
Для подходящих тегов можно указать tagPosition: 'bodyClose', чтобы вставить их в конец <body>.
Пример:
<script setup lang="ts">
useHead({
script: [
{
src: 'https://third-party-script.com',
// допустимые значения: 'head' | 'bodyClose' | 'bodyOpen'
tagPosition: 'bodyClose',
},
],
})
</script>
Примеры
С definePageMeta
В директории app/pages/ можно комбинировать definePageMeta и useHead для метаданных в зависимости от маршрута.
Заголовок страницы задаётся так (извлекается при сборке макросом, динамически задать нельзя):
<script setup lang="ts">
definePageMeta({
title: 'Some Page',
})
</script>
В макете можно использовать заданные метаданные маршрута:
<script setup lang="ts">
const route = useRoute()
useHead({
meta: [{ property: 'og:title', content: `App Name - ${route.meta.title}` }],
})
</script>
Динамический заголовок
В примере ниже titleTemplate задаётся строкой с плейсхолдером %s или функцией для гибкой подстановки заголовка по маршрутам:
<script setup lang="ts">
useHead({
titleTemplate: '%s - Site Title',
})
</script>
<script setup lang="ts">
useHead({
titleTemplate: (productCategory) => {
return productCategory
? `${productCategory} - Site Title`
: 'Site Title'
},
})
</script>
В nuxt.config заголовок тоже можно задать, но не динамически. Для динамики рекомендуется titleTemplate в app.vue — он применится ко всем маршрутам.
Внешний CSS
Подключение Google Fonts через свойство link композабла useHead или компонент <Link>:
<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>
<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>