Skip to content

Commit a563c0e

Browse files
committed
feat: implement dynamic page titles using unhead and i18n
1 parent df0c6ab commit a563c0e

File tree

9 files changed

+68
-23
lines changed

9 files changed

+68
-23
lines changed

src/App.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
<script lang="ts" setup>
2+
const { t } = useI18n()
3+
4+
const title = computed(() => {
5+
return t('app.title')
6+
})
7+
8+
useHead({
9+
titleTemplate: (_t) => {
10+
return _t && _t !== title.value ? `${_t} - ${title.value}` : title.value
11+
},
12+
})
13+
</script>
14+
115
<template>
216
<RouterView />
317
</template>

src/modules/unhead.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { createHead } from '@unhead/vue'
2+
3+
export const head = createHead()
4+
5+
export default head

src/modules/vue-router.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,7 @@ router.beforeEach(() => {
1616
NProgress.start()
1717
})
1818

19-
router.afterEach((to) => {
20-
const { setLocaleTitle } = useLocale()
21-
if (to.meta.locale)
22-
setLocaleTitle(to.meta.locale, true)
23-
else if (to.meta.title)
24-
setLocaleTitle(to.meta.title)
25-
else
26-
setLocaleTitle()
27-
19+
router.afterEach(() => {
2820
NProgress.done()
2921
})
3022

src/pages/[...path].vue

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
<route lang="json">
22
{
3-
"name": "404",
4-
"meta": {
5-
"title": "Page Not Found",
6-
"locale": "page.not-found.title"
7-
}
3+
"name": "not-found"
84
}
95
</route>
106

117
<script setup lang="ts">
128
const { t } = useI18n()
139
10+
const title = computed(() => {
11+
return t('page.not-found.title')
12+
})
13+
14+
useHead({
15+
title,
16+
})
17+
1418
const router = useRouter()
1519
1620
function goBack() {

src/pages/about.vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
<route lang="json">
22
{
3-
"name": "About",
3+
"name": "about",
44
"meta": {
5-
"title": "About Page",
6-
"locale": "page.about.title",
75
"layout": "page"
86
}
97
}
108
</route>
119

1210
<script lang="ts" setup>
1311
const { t } = useI18n()
12+
13+
const title = computed(() => {
14+
return t('page.about.title')
15+
})
16+
17+
useHead({
18+
title,
19+
})
1420
</script>
1521

1622
<template>

src/pages/index.vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
<route lang="json">
22
{
3-
"name": "Index",
3+
"name": "index",
44
"meta": {
5-
"title": "Index Page",
6-
"locale": "page.index.title",
75
"layout": "page"
86
}
97
}
@@ -14,6 +12,14 @@ import type { TodoList } from '@/types/todo'
1412
1513
const { t } = useI18n()
1614
15+
const title = computed(() => {
16+
return t('page.index.title')
17+
})
18+
19+
useHead({
20+
title,
21+
})
22+
1723
const { headerLogo } = storeToRefs(useLayoutStore())
1824
1925
const { toggleLogo } = useLayoutStore()

typings/auto-imports.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ declare global {
4545
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
4646
const effectScope: typeof import('vue')['effectScope']
4747
const extendRef: typeof import('@vueuse/core')['extendRef']
48+
const getActiveHead: typeof import('@unhead/vue')['getActiveHead']
4849
const getActivePinia: typeof import('pinia')['getActivePinia']
4950
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
5051
const getCurrentScope: typeof import('vue')['getCurrentScope']
@@ -53,6 +54,7 @@ declare global {
5354
const h: typeof import('vue')['h']
5455
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
5556
const inject: typeof import('vue')['inject']
57+
const injectHead: typeof import('@unhead/vue')['injectHead']
5658
const injectLocal: typeof import('@vueuse/core')['injectLocal']
5759
const installModules: typeof import('../src/utils/install')['installModules']
5860
const isDark: typeof import('../src/composables/dark')['isDark']
@@ -203,6 +205,8 @@ declare global {
203205
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
204206
const useGamepad: typeof import('@vueuse/core')['useGamepad']
205207
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
208+
const useHead: typeof import('@unhead/vue')['useHead']
209+
const useHeadSafe: typeof import('@unhead/vue')['useHeadSafe']
206210
const useI18n: typeof import('vue-i18n')['useI18n']
207211
const useId: typeof import('vue')['useId']
208212
const useIdle: typeof import('@vueuse/core')['useIdle']
@@ -260,6 +264,10 @@ declare global {
260264
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
261265
const useScroll: typeof import('@vueuse/core')['useScroll']
262266
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
267+
const useSeoMeta: typeof import('@unhead/vue')['useSeoMeta']
268+
const useServerHead: typeof import('@unhead/vue')['useServerHead']
269+
const useServerHeadSafe: typeof import('@unhead/vue')['useServerHeadSafe']
270+
const useServerSeoMeta: typeof import('@unhead/vue')['useServerSeoMeta']
263271
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
264272
const useShare: typeof import('@vueuse/core')['useShare']
265273
const useSlots: typeof import('vue')['useSlots']
@@ -375,6 +383,7 @@ declare module 'vue' {
375383
readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
376384
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
377385
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
386+
readonly getActiveHead: UnwrapRef<typeof import('@unhead/vue')['getActiveHead']>
378387
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
379388
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
380389
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
@@ -383,6 +392,7 @@ declare module 'vue' {
383392
readonly h: UnwrapRef<typeof import('vue')['h']>
384393
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
385394
readonly inject: UnwrapRef<typeof import('vue')['inject']>
395+
readonly injectHead: UnwrapRef<typeof import('@unhead/vue')['injectHead']>
386396
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
387397
readonly installModules: UnwrapRef<typeof import('../src/utils/install')['installModules']>
388398
readonly isDark: UnwrapRef<typeof import('../src/composables/dark')['isDark']>
@@ -533,6 +543,8 @@ declare module 'vue' {
533543
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
534544
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
535545
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
546+
readonly useHead: UnwrapRef<typeof import('@unhead/vue')['useHead']>
547+
readonly useHeadSafe: UnwrapRef<typeof import('@unhead/vue')['useHeadSafe']>
536548
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
537549
readonly useId: UnwrapRef<typeof import('vue')['useId']>
538550
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
@@ -590,6 +602,10 @@ declare module 'vue' {
590602
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
591603
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
592604
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
605+
readonly useSeoMeta: UnwrapRef<typeof import('@unhead/vue')['useSeoMeta']>
606+
readonly useServerHead: UnwrapRef<typeof import('@unhead/vue')['useServerHead']>
607+
readonly useServerHeadSafe: UnwrapRef<typeof import('@unhead/vue')['useServerHeadSafe']>
608+
readonly useServerSeoMeta: UnwrapRef<typeof import('@unhead/vue')['useServerSeoMeta']>
593609
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
594610
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
595611
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>

typings/vue-router.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ declare module 'vue-router/auto-routes' {
1818
* Route name map generated by unplugin-vue-router
1919
*/
2020
export interface RouteNamedMap {
21-
'Index': RouteRecordInfo<'Index', '/', Record<never, never>, Record<never, never>>,
22-
'404': RouteRecordInfo<'404', '/:path(.*)', { path: ParamValue<true> }, { path: ParamValue<false> }>,
23-
'About': RouteRecordInfo<'About', '/about', Record<never, never>, Record<never, never>>,
21+
'index': RouteRecordInfo<'index', '/', Record<never, never>, Record<never, never>>,
22+
'not-found': RouteRecordInfo<'not-found', '/:path(.*)', { path: ParamValue<true> }, { path: ParamValue<false> }>,
23+
'about': RouteRecordInfo<'about', '/about', Record<never, never>, Record<never, never>>,
2424
}
2525
}

vite.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { resolve } from 'node:path'
44
import { cwd } from 'node:process'
55
import VueI18n from '@intlify/unplugin-vue-i18n/vite'
6+
import { unheadVueComposablesImports } from '@unhead/vue'
67
import vue from '@vitejs/plugin-vue'
78
import vueJsx from '@vitejs/plugin-vue-jsx'
89
import UnoCSS from 'unocss/vite'
@@ -76,6 +77,7 @@ export default defineConfig(({ mode }) => {
7677
imports: [
7778
'vue',
7879
VueRouterAutoImports,
80+
unheadVueComposablesImports,
7981
{
8082
// add any other imports you were relying on
8183
'vue-router/auto': ['useLink'],

0 commit comments

Comments
 (0)