mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 23:22:02 +00:00
feat(nuxt, bridge): support titleTemplate
, viewport
and charset
for useHead
(#4221)
This commit is contained in:
parent
041e8694d1
commit
1091d456a8
@ -1,26 +1,29 @@
|
|||||||
# Head Management
|
# Head Management
|
||||||
|
|
||||||
You can customize the meta tags for your site through several different ways:
|
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 customizing other meta tags for your site in several different ways.
|
||||||
|
|
||||||
|
:ReadMore{link="/api/configuration/nuxt.config#head"}
|
||||||
|
|
||||||
## `useHead` Composable
|
## `useHead` Composable
|
||||||
|
|
||||||
Within your `setup` function, you can call `useHead` with an object of meta properties with keys corresponding to meta tags: `title`, `base`, `script`, `style`, `meta` and `link`, as well as `htmlAttrs` and `bodyAttrs`. Alternatively, you can pass a function returning the object for reactive metadata.
|
Within your `setup` function, you can call `useHead` with an object of meta properties with keys corresponding to meta tags: `title`, `titleTemplate`, `base`, `script`, `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:
|
For example:
|
||||||
|
|
||||||
```js
|
```vue
|
||||||
export default {
|
<script setup>
|
||||||
setup () {
|
useHead({
|
||||||
useHead({
|
titleTemplate: 'My App - %s', // or, title => `My App - ${title}`
|
||||||
meta: [
|
viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
|
||||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }
|
charset: 'utf-8',
|
||||||
],
|
meta: [
|
||||||
bodyAttrs: {
|
{ name: 'description', content: 'My amazing site.' }
|
||||||
class: 'test'
|
],
|
||||||
}
|
bodyAttrs: {
|
||||||
})
|
class: 'test'
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
::ReadMore{link="/api/composables/use-head"}
|
::ReadMore{link="/api/composables/use-head"}
|
||||||
@ -68,7 +71,7 @@ export default {
|
|||||||
|
|
||||||
You can use `definePageMeta` along with `useHead` to set metadata based on the current route.
|
You can use `definePageMeta` along with `useHead` to set metadata based on the current route.
|
||||||
|
|
||||||
For example, to include the page title alongside your app name, first define your page title:
|
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]
|
```vue{}[pages/some-page.vue]
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -78,14 +81,14 @@ definePageMeta({
|
|||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
And then in your layout file:
|
And then in your layout file you might use the route metadata you've previously set:
|
||||||
|
|
||||||
```vue{}[layouts/default.vue]
|
```vue{}[layouts/default.vue]
|
||||||
<script setup>
|
<script setup>
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: computed(() => `App Name - ${route.meta.title}`)
|
meta: [{ name: 'og:title', content: `App Name - ${route.meta.title}` }]
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
@ -11,7 +11,7 @@ Nuxt 3 provides several different ways to manage your meta tags.
|
|||||||
2. Through the `useHead` [composable](/guide/features/head-management)
|
2. Through the `useHead` [composable](/guide/features/head-management)
|
||||||
3. Through [global meta components](/guide/features/head-management)
|
3. Through [global meta components](/guide/features/head-management)
|
||||||
|
|
||||||
You can customize `title`, `base`, `script`, `style`, `meta`, `link`, `htmlAttrs` and `bodyAttrs`.
|
You can customize `title`, `titleTemplate`, `base`, `script`, `style`, `meta`, `link`, `htmlAttrs` and `bodyAttrs`.
|
||||||
|
|
||||||
::alert{icon=📦}
|
::alert{icon=📦}
|
||||||
Nuxt currently uses [`vueuse/head`](https://github.com/vueuse/head) to manage your meta tags, but implementation details may change.
|
Nuxt currently uses [`vueuse/head`](https://github.com/vueuse/head) to manage your meta tags, but implementation details may change.
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
export default {
|
export default {
|
||||||
setup () {
|
setup () {
|
||||||
useHead({
|
useHead({
|
||||||
|
titleTemplate: '%s - useHead example',
|
||||||
bodyAttrs: {
|
bodyAttrs: {
|
||||||
class: 'test'
|
class: 'test'
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,11 @@ export default defineNuxtModule({
|
|||||||
// Add #head alias
|
// Add #head alias
|
||||||
nuxt.options.alias['#head'] = runtimeDir
|
nuxt.options.alias['#head'] = runtimeDir
|
||||||
|
|
||||||
// Global meta
|
// Global meta -for Bridge, this is necessary to repeat here
|
||||||
|
// and in packages/schema/src/config/_app.ts
|
||||||
const globalMeta: MetaObject = defu(nuxt.options.app.head, {
|
const globalMeta: MetaObject = defu(nuxt.options.app.head, {
|
||||||
meta: [
|
charset: options.charset,
|
||||||
{ charset: options.charset },
|
viewport: options.viewport
|
||||||
{ name: 'viewport', content: options.viewport }
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add global meta configuration
|
// Add global meta configuration
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { createHead, renderHeadToString } from '@vueuse/head'
|
import { createHead, renderHeadToString } from '@vueuse/head'
|
||||||
import { ref, watchEffect, onBeforeUnmount, getCurrentInstance } from 'vue'
|
import { computed, ref, unref, watchEffect, onBeforeUnmount, getCurrentInstance, ComputedGetter } from 'vue'
|
||||||
|
import defu from 'defu'
|
||||||
import type { MetaObject } from '..'
|
import type { MetaObject } from '..'
|
||||||
import { defineNuxtPlugin } from '#app'
|
import { defineNuxtPlugin } from '#app'
|
||||||
|
|
||||||
@ -14,9 +15,26 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
headReady = true
|
headReady = true
|
||||||
})
|
})
|
||||||
|
|
||||||
nuxtApp._useHead = (meta: MetaObject) => {
|
const titleTemplate = ref<MetaObject['titleTemplate']>()
|
||||||
const headObj = ref(meta as any)
|
|
||||||
head.addHeadObjs(headObj)
|
nuxtApp._useHead = (meta: MetaObject | ComputedGetter<MetaObject>) => {
|
||||||
|
titleTemplate.value = (unref(meta) as MetaObject).titleTemplate || titleTemplate.value
|
||||||
|
|
||||||
|
const headObj = computed(() => {
|
||||||
|
const overrides: MetaObject = { meta: [] }
|
||||||
|
const val = unref(meta) as MetaObject
|
||||||
|
if (titleTemplate.value && 'title' in val) {
|
||||||
|
overrides.title = typeof titleTemplate.value === 'function' ? titleTemplate.value(val.title) : titleTemplate.value.replace(/%s/g, val.title)
|
||||||
|
}
|
||||||
|
if (val.charset) {
|
||||||
|
overrides.meta!.push({ key: 'charset', charset: val.charset })
|
||||||
|
}
|
||||||
|
if (val.viewport) {
|
||||||
|
overrides.meta!.push({ name: 'viewport', content: val.viewport })
|
||||||
|
}
|
||||||
|
return defu(overrides, val)
|
||||||
|
})
|
||||||
|
head.addHeadObjs(headObj as any)
|
||||||
|
|
||||||
if (process.server) { return }
|
if (process.server) { return }
|
||||||
|
|
||||||
|
@ -102,6 +102,8 @@ export default {
|
|||||||
head: {
|
head: {
|
||||||
$resolve: (val, get) => {
|
$resolve: (val, get) => {
|
||||||
return defu(val, get('meta'), {
|
return defu(val, get('meta'), {
|
||||||
|
charset: 'utf-8',
|
||||||
|
viewport: 'width=device-width, initial-scale=1',
|
||||||
meta: [],
|
meta: [],
|
||||||
link: [],
|
link: [],
|
||||||
style: [],
|
style: [],
|
||||||
|
@ -21,4 +21,6 @@ export interface MetaObject extends Record<string, any> {
|
|||||||
style?: Array<Record<string, any>>
|
style?: Array<Record<string, any>>
|
||||||
/** Each item in the array maps to a newly-created `<script>` element, where object properties map to attributes. */
|
/** Each item in the array maps to a newly-created `<script>` element, where object properties map to attributes. */
|
||||||
script?: Array<Record<string, any>>
|
script?: Array<Record<string, any>>
|
||||||
|
|
||||||
|
titleTemplate?: string | ((title: string) => string)
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,6 @@ describe('pages', () => {
|
|||||||
|
|
||||||
// should render text
|
// should render text
|
||||||
expect(html).toContain('Hello Nuxt 3!')
|
expect(html).toContain('Hello Nuxt 3!')
|
||||||
// should render <Head> components
|
|
||||||
expect(html).toContain('<title>Basic fixture</title>')
|
|
||||||
// should inject runtime config
|
// should inject runtime config
|
||||||
expect(html).toContain('RuntimeConfig | testConfig: 123')
|
expect(html).toContain('RuntimeConfig | testConfig: 123')
|
||||||
// composables auto import
|
// composables auto import
|
||||||
@ -119,12 +117,20 @@ describe('pages', () => {
|
|||||||
describe('head tags', () => {
|
describe('head tags', () => {
|
||||||
it('should render tags', async () => {
|
it('should render tags', async () => {
|
||||||
const html = await $fetch('/head')
|
const html = await $fetch('/head')
|
||||||
expect(html).toContain('<title>Using a dynamic component</title>')
|
expect(html).toContain('<title>Using a dynamic component - Fixture</title>')
|
||||||
expect(html).not.toContain('<meta name="description" content="first">')
|
expect(html).not.toContain('<meta name="description" content="first">')
|
||||||
|
expect(html).toContain('<meta charset="utf-16">')
|
||||||
|
expect(html).not.toContain('<meta charset="utf-8">')
|
||||||
expect(html).toContain('<meta name="description" content="overriding with an inline useHead call">')
|
expect(html).toContain('<meta name="description" content="overriding with an inline useHead call">')
|
||||||
expect(html).toMatch(/<html[^>]*class="html-attrs-test"/)
|
expect(html).toMatch(/<html[^>]*class="html-attrs-test"/)
|
||||||
expect(html).toMatch(/<body[^>]*class="body-attrs-test"/)
|
expect(html).toMatch(/<body[^>]*class="body-attrs-test"/)
|
||||||
expect(html).toContain('script>console.log("works with useMeta too")</script>')
|
expect(html).toContain('script>console.log("works with useMeta too")</script>')
|
||||||
|
|
||||||
|
const index = await $fetch('/')
|
||||||
|
// should render charset by default
|
||||||
|
expect(index).toContain('<meta charset="utf-8">')
|
||||||
|
// should render <Head> components
|
||||||
|
expect(index).toContain('<title>Basic fixture - Fixture</title>')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
2
test/fixtures/basic/pages/head.vue
vendored
2
test/fixtures/basic/pages/head.vue
vendored
@ -5,7 +5,7 @@ useHead({
|
|||||||
},
|
},
|
||||||
meta: [{ name: 'description', content: 'first' }]
|
meta: [{ name: 'description', content: 'first' }]
|
||||||
})
|
})
|
||||||
useHead({ meta: [{ name: 'description', content: 'overriding with an inline useHead call' }] })
|
useHead({ charset: 'utf-16', meta: [{ name: 'description', content: 'overriding with an inline useHead call' }] })
|
||||||
useMeta({ script: [{ children: 'console.log("works with useMeta too")' }] })
|
useMeta({ script: [{ children: 'console.log("works with useMeta too")' }] })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
3
test/fixtures/basic/plugins/my-plugin.ts
vendored
3
test/fixtures/basic/plugins/my-plugin.ts
vendored
@ -1,4 +1,7 @@
|
|||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
|
useHead({
|
||||||
|
titleTemplate: '%s - Fixture'
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
provide: {
|
provide: {
|
||||||
myPlugin: () => 'Injected by my-plugin'
|
myPlugin: () => 'Injected by my-plugin'
|
||||||
|
Loading…
Reference in New Issue
Block a user