# [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 :::