From eb3bc3775a3a0fe6d33c6bd928435f0f8aa6ae8e Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Thu, 21 Dec 2023 15:13:49 +1100 Subject: [PATCH] feat: implement `fallbackTitle` as separate plugin --- module/src/module.ts | 11 +++++++++ module/src/runtime/plugin/titles.ts | 36 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 module/src/runtime/plugin/titles.ts diff --git a/module/src/module.ts b/module/src/module.ts index b259c22a..1df0039f 100644 --- a/module/src/module.ts +++ b/module/src/module.ts @@ -12,6 +12,12 @@ import { installNuxtSiteConfig } from 'nuxt-site-config-kit' import { version } from '../package.json' export interface ModuleOptions { + /** + * Will ensure a title is always set by providing a fallback title based on the casing the last slug segment. + * + * @default true + */ + fallbackTitle?: boolean /** * Will set up a number of defaults for meta tags and Schema.org, if the modules and config are available. * @@ -95,6 +101,11 @@ export default defineNuxtModule({ src: resolve('./runtime/plugin/defaults'), }) } + if (config.fallbackTitle) { + addPlugin({ + src: resolve('./runtime/plugin/titles'), + }) + } // if user disables certain modules we need to pollyfill the imports const polyfills: Record = { diff --git a/module/src/runtime/plugin/titles.ts b/module/src/runtime/plugin/titles.ts new file mode 100644 index 00000000..6db41189 --- /dev/null +++ b/module/src/runtime/plugin/titles.ts @@ -0,0 +1,36 @@ +import { defineNuxtPlugin } from 'nuxt/app' +import type { UseHeadOptions } from '@unhead/vue' +import { withoutTrailingSlash } from 'ufo' +import { + computed, + useHead, + useRoute, +} from '#imports' + +function titleCase(s: string) { + return s + .replaceAll('-', ' ') + .replace(/\w\S*/g, w => w.charAt(0).toUpperCase() + w.substr(1).toLowerCase()) +} + +export default defineNuxtPlugin({ + name: 'nuxt-seo:fallback-titles', + setup() { + const route = useRoute() + const title = computed(() => { + if (typeof route.meta?.title === 'string') + return route.meta?.title + // if no title has been set then we should use the last segment of the URL path and title case it + const path = withoutTrailingSlash(route.path || '/') + const lastSegment = path.split('/').pop() + return lastSegment ? titleCase(lastSegment) : null + }) + + const minimalPriority: UseHeadOptions = { + // give nuxt.config values higher priority + tagPriority: 101, + } + + useHead({ title }, minimalPriority) + }, +})