feat(nuxt, bridge): support titleTemplate, viewport and charset for useHead (#4221)

This commit is contained in:
Daniel Roe 2022-04-11 10:03:31 +01:00 committed by GitHub
parent 041e8694d1
commit 1091d456a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 65 additions and 31 deletions

View File

@ -1,26 +1,29 @@
# 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
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:
```js
export default {
setup () {
```vue
<script setup>
useHead({
titleTemplate: 'My App - %s', // or, title => `My App - ${title}`
viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
charset: 'utf-8',
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }
{ name: 'description', content: 'My amazing site.' }
],
bodyAttrs: {
class: 'test'
}
})
}
}
</script>
```
::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.
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]
<script setup>
@ -78,14 +81,14 @@ definePageMeta({
</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]
<script setup>
const route = useRoute()
useHead({
title: computed(() => `App Name - ${route.meta.title}`)
meta: [{ name: 'og:title', content: `App Name - ${route.meta.title}` }]
})
</script>
```

View File

@ -11,7 +11,7 @@ Nuxt 3 provides several different ways to manage your meta tags.
2. Through the `useHead` [composable](/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=📦}
Nuxt currently uses [`vueuse/head`](https://github.com/vueuse/head) to manage your meta tags, but implementation details may change.

View File

@ -27,6 +27,7 @@
export default {
setup () {
useHead({
titleTemplate: '%s - useHead example',
bodyAttrs: {
class: 'test'
}

View File

@ -21,12 +21,11 @@ export default defineNuxtModule({
// Add #head alias
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, {
meta: [
{ charset: options.charset },
{ name: 'viewport', content: options.viewport }
]
charset: options.charset,
viewport: options.viewport
})
// Add global meta configuration

View File

@ -1,5 +1,6 @@
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 { defineNuxtPlugin } from '#app'
@ -14,9 +15,26 @@ export default defineNuxtPlugin((nuxtApp) => {
headReady = true
})
nuxtApp._useHead = (meta: MetaObject) => {
const headObj = ref(meta as any)
head.addHeadObjs(headObj)
const titleTemplate = ref<MetaObject['titleTemplate']>()
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 }

View File

@ -102,6 +102,8 @@ export default {
head: {
$resolve: (val, get) => {
return defu(val, get('meta'), {
charset: 'utf-8',
viewport: 'width=device-width, initial-scale=1',
meta: [],
link: [],
style: [],

View File

@ -21,4 +21,6 @@ export interface MetaObject extends 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. */
script?: Array<Record<string, any>>
titleTemplate?: string | ((title: string) => string)
}

View File

@ -36,8 +36,6 @@ describe('pages', () => {
// should render text
expect(html).toContain('Hello Nuxt 3!')
// should render <Head> components
expect(html).toContain('<title>Basic fixture</title>')
// should inject runtime config
expect(html).toContain('RuntimeConfig | testConfig: 123')
// composables auto import
@ -119,12 +117,20 @@ describe('pages', () => {
describe('head tags', () => {
it('should render tags', async () => {
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).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).toMatch(/<html[^>]*class="html-attrs-test"/)
expect(html).toMatch(/<body[^>]*class="body-attrs-test"/)
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>')
})
})

View File

@ -5,7 +5,7 @@ useHead({
},
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")' }] })
</script>

View File

@ -1,4 +1,7 @@
export default defineNuxtPlugin(() => {
useHead({
titleTemplate: '%s - Fixture'
})
return {
provide: {
myPlugin: () => 'Injected by my-plugin'