# [Nuxt] Nuxt 3 - SEO & Meta
###### tags: `Nuxt`
* 已有 charset 和 viewport 的預設值,如有不同再複寫即可
* `charset`: `utf-8`
* `viewport`: `width=device-width, initial-scale=1`
* 可使用 `app.head`、Composables、Components 設定
## app.head
:::info
[官方文件](https://nuxt.com/docs/getting-started/seo-meta#defaults "SEO and Meta · Get Started with Nuxt")
:::
寫在 `nuxt.config.ts` 的 `app.head` 內為全站共用,若頁面沒有設定,則會使用這邊的值
```typescript
export default defineNuxtConfig({
app: {
head: {
// charset: 'utf-8',
// viewport: 'width=device-width, initial-scale=1',
// title: 'Nuxt 3 Playground',
// %s 會替換成 title 的值
title: 'Index',
titleTemplate: '%s - Nuxt 3 Playground',
meta: [
{ name: "description", content: '這是 Nuxt 3 Playground' },
{ property: 'og:title', content: 'Nuxt 3 Playground' },
{ property: 'og:url', content: 'http://localhost:3000/' },
{ property: 'og:description', content: '這是 Nuxt 3 Playground' },
],
script: [
// <script src="https://awesome-lib.js" async=""></script>
{
src: 'https://awesome-lib.js',
async: true,
tagPosition: 'bodyClose', // 添加位置,預設是 'head'
},
],
link: [
// <link rel="stylesheet" href="https://meyerweb.com/eric/tools/css/reset/reset200802.css">
{ rel: 'stylesheet', href: 'https://meyerweb.com/eric/tools/css/reset/reset200802.css' },
],
style: [
// <style>:root { color: #009688 }</style>
{ children: ':root { color: #009688 }' },
],
noscript: [
// <noscript>JavaScript is required</noscript>
{ children: 'JavaScript is required' },
],
},
},
});
```
### 注意事項
`titleTemplate` 的值可以是 string 或 function,但 `app.head` 不支援以 function 設置動態 title,建議在 `app.vue` 設定,網站所有頁面皆會套用此設定
```javascript
// app.vue
<script setup>
useHead({
titleTemplate: (title) => title ? `${title} - Nuxt 3 Playground` : 'Nuxt 3 Playground',
});
</script>
```
```javascript
// 頁面.vue
useHead({
title: 'SEO',
});
```
## Composables
### useHead
:::info
* [官方文件 - SEO and Meta](https://nuxt.com/docs/getting-started/seo-meta#usehead "SEO and Meta · Get Started with Nuxt")
* [官方文件 - useHead composable](https://nuxt.com/docs/api/composables/use-head "useHead · Nuxt Composables")
:::
若資料來源為 user 或不受信任的資料,建議改用 `useHeadSafe`
```javascript
useHead({
title: 'SEO',
meta: [
{ name: 'description', content: 'SEO Page'},
{ property: 'og:title', content: 'SEO Page' },
{ property: 'og:url', content: 'http://xxx.xxx/seo' },
{ property: 'og:description', content: '這是 SEO Page' },
],
});
```
### useHeadSafe
:::info
* [官方文件 - useHeadSafe composable](https://nuxt.com/docs/api/composables/use-head-safe "useHeadSafe · Nuxt Composables")
:::
限制可輸入的值
### useSeoMeta
:::info
* [官方文件 - SEO and Meta](https://nuxt.com/docs/getting-started/seo-meta#useseometa-and-useserverseometa "SEO and Meta · Get Started with Nuxt")
* [官方文件 - useSeoMeta composable](https://nuxt.com/docs/api/composables/use-seo-meta "useSeoMeta · Nuxt Composables")
:::
* 有更好的 TypeScript 支援
* 避免撰寫 meta tag 的屬性錯誤
```javascript
useSeoMeta({
title: 'SEO',
description: 'SEO Page',
ogTitle: 'SEO Page',
ogUrl: 'http://xxx.xx/seo',
ogDescription: '這是 SEO Page',
});
```
### useServerSeoMeta
:::info
* [官方文件 - SEO and Meta](https://nuxt.com/docs/getting-started/seo-meta#useseometa-and-useserverseometa "SEO and Meta · Get Started with Nuxt")
* [官方文件 - useServerSeoMeta composable](https://nuxt.com/docs/api/composables/use-server-seo-meta "useServerSeoMeta · Nuxt Composables")
:::
* 有更好的 TypeScript 支援
* 避免撰寫 meta tag 的屬性錯誤
* 會改變初始加載 meta tag,可做到讓爬蟲讀取寫的資料,但使用者看到的是別的資訊
* 網頁點右鍵 "檢視網頁原始碼",可以發現設定的值與使用者看到的不同
```javascript
const res = await useFetch('https://xxx.xx/api/seo');
useServerSeoMeta({
title: () => `${res.data?.value?.title} - Nuxt3`,
description: () => `${res.data?.value?.description} - Nuxt3`,
ogTitle: () => `${res.data?.value?.title} - Nuxt3`,
ogDescription: () => `${res.data?.value?.description} - Nuxt3`,
});
```
## Components
:::info
[官方文件](https://nuxt.com/docs/getting-started/seo-meta#components "SEO and Meta · Get Started with Nuxt")
:::
* 寫在 `<template>` 內,名稱開頭必須==大寫==
* 可使用 `<Title>`、`<Base>`、`<NoScript>`、`<Style>`、`<Meta>`、`<Link>`、`<Body>`、`<Html>`、`<Head>`
```javascript
<script>
const res = await useFetch('https://xxx.xx/api/seo');
const title = computed(() => res.data?.value?.title);
const description = computed(() => res.data?.value?.description);
</script>
```
```htmlembedded
<template>
<Head>
<Title>{{ title }}</Title>
<Meta name="description" :content="description" />
<Style children="body { background-color: #87cefa; }" />
</Head>
</template>
```
## Types
:::info
[官方文件](https://nuxt.com/docs/getting-started/seo-meta#types "SEO and Meta · Get Started with Nuxt")
:::
可用在 `useHead`、`app.head`、components,詳細 types 看 [@unhead/schema](https://github.com/unjs/unhead/blob/main/packages/schema/src/schema.ts "unjs/unhead schema.ts")
```typescript
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;
}
```
## 搭配 definePageMeta
:::info
[官方文件](https://nuxt.com/docs/getting-started/seo-meta#with-definepagemeta "SEO and Meta · Get Started with Nuxt")
:::
* 在 `pages/` 內可以使用 `useHead` 搭配 `definePageMeta()`
* 要注意無法用在動態 title(`titleTemplate: (x) => {...}`),要使用動態 title 建議將值寫在頁面 `useHead` 的 `title`
```javascript
// app.vue 或 layouts/default.vue
<script setup>
const route = useRoute();
useHead({
title: route.meta.title,
meta: [{ name: 'og:title', content: `${route.meta.title} - Nuxt 3 Playground` }],
});
</script>
```
```javascript
// 頁面.vue
<script setup>
definePageMeta({
title: 'seo',
});
</script>
```
---
:::info
建立日期:2023-09-01
更新日期:2023-09-04
:::