add: more SEO meta

More seometa, better markdown parser
This commit is contained in:
webfussel 2025-06-11 18:49:19 +02:00
parent ca3868299c
commit fa0435efdf
10 changed files with 52 additions and 14 deletions

View file

@ -8,8 +8,6 @@
<script setup> <script setup>
useSeoMeta({ useSeoMeta({
title: 'Home',
description: 'Headless CMS, Components & APIs by Fiona Urban. Storyblok, FirstSpirit, Nuxt.',
author: 'webfussel', author: 'webfussel',
robots: 'index, follow', robots: 'index, follow',
themeColor: '#2a2723', themeColor: '#2a2723',

View file

@ -130,7 +130,7 @@ const oneOff : Service[] = [
] ]
const { data: faq } = await useAsyncData('faq', () => queryCollection('faq').path('/snippets/faq/booking').first()) const { data: faq } = await useAsyncData('faq', () => queryCollection('faq').path('/snippets/faq/booking').first())
const texts = usePlainFaq(faq.value?.body.value) const texts = generatePlainText<['title']>(faq.value?.body.value)
if (faq) { if (faq) {
useSchemaOrg({ useSchemaOrg({
@ -138,10 +138,10 @@ if (faq) {
'@type': 'FAQPage', '@type': 'FAQPage',
'mainEntity': texts.map(entity => ({ 'mainEntity': texts.map(entity => ({
'@type': 'Question', '@type': 'Question',
'name': entity.question, 'name': entity.meta.title,
'acceptedAnswer': { 'acceptedAnswer': {
'@type': 'Answer', '@type': 'Answer',
'text': entity.answer, 'text': entity.text,
}, },
})) }))
}) })

View file

@ -139,7 +139,7 @@ const flatrate : Service[] =
] ]
const { data : faq } = await useAsyncData('faq', () => queryCollection('faq').path('/snippets/faq/flatrate').first()) const { data : faq } = await useAsyncData('faq', () => queryCollection('faq').path('/snippets/faq/flatrate').first())
const texts = usePlainFaq(faq.value?.body.value) const texts = generatePlainText<['title']>(faq.value?.body.value)
if (faq) { if (faq) {
useSchemaOrg({ useSchemaOrg({
@ -147,10 +147,10 @@ if (faq) {
'@type': 'FAQPage', '@type': 'FAQPage',
'mainEntity': texts.map(entity => ({ 'mainEntity': texts.map(entity => ({
'@type': 'Question', '@type': 'Question',
'name': entity.question, 'name': entity.meta.title,
'acceptedAnswer': { 'acceptedAnswer': {
'@type': 'Answer', '@type': 'Answer',
'text': entity.answer, 'text': entity.text,
}, },
})) }))
}) })

View file

@ -3,3 +3,10 @@
<SectionBooking /> <SectionBooking />
</div> </div>
</template> </template>
<script lang="ts" setup>
useSeoMeta({
title: 'Projektbuchung',
description: 'Buche jetzt dein Projekt auf webfussel. Du brauchst eine Schulung in JavaScript, Typescript, HTML, CSS, Vue oder Nuxt? Kein Problem.',
})
</script>

View file

@ -3,3 +3,10 @@
<SectionContact /> <SectionContact />
</div> </div>
</template> </template>
<script lang="ts" setup>
useSeoMeta({
title: 'Kontakt',
description: 'Nimm Kontakt zu webfussel auf. Egal ob über E-Mail oder Social Media - ich freue mich auf deine Nachricht.',
})
</script>

View file

@ -3,3 +3,10 @@
<SectionFlatrate /> <SectionFlatrate />
</div> </div>
</template> </template>
<script lang="ts" setup>
useSeoMeta({
title: 'Flatrate',
description: 'Buche webfussel ganz einfach für eine zugesicherte Anzahl Stunden pro Woche. Wenn immer mal wieder was anfällt - vertrau auf Fusselqualität.',
})
</script>

View file

@ -14,4 +14,9 @@ useHead({
{ rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' }, { rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' },
], ],
}) })
useSeoMeta({
title: 'Home',
description: 'Webprojekte und Retainer mit Fusselqualität. Du brauchst eine Website mit CMS? Bock auf Flatrate? webfussel by Fiona Urban',
})
</script> </script>

View file

@ -3,3 +3,10 @@
<SectionCustomers /> <SectionCustomers />
</div> </div>
</template> </template>
<script lang="ts" setup>
useSeoMeta({
title: 'Referenzen',
description: 'Schau dir dir Projekte von webfussel an. Über persönliche Webseiten, über Schulungen bis hin zu API Projekten.',
})
</script>

View file

@ -1,5 +1,12 @@
import type { MinimalElement, MinimalNode } from '@nuxt/content' import type { MinimalElement, MinimalNode } from '@nuxt/content'
type TypedRecord<T extends readonly string[]> = Record<T[number], string>
type PlainText<T extends string[]> = {
meta: TypedRecord<T>
text: string
}
const extractText = (element ?: MinimalNode) : string => { const extractText = (element ?: MinimalNode) : string => {
if (!element) return '' if (!element) return ''
if (typeof element === 'string') return element if (typeof element === 'string') return element
@ -7,13 +14,13 @@ const extractText = (element ?: MinimalNode) : string => {
return nodes?.map((el : MinimalNode) => typeof el === 'string' ? el : extractText(el)).join(' ') ?? '' return nodes?.map((el : MinimalNode) => typeof el === 'string' ? el : extractText(el)).join(' ') ?? ''
} }
export const usePlainFaq = (body ?: MinimalNode[]) => { export const generatePlainText = <T extends string[] = []>(body ?: MinimalNode[]) : PlainText<T>[] => {
if (!body) return [] if (!body) return []
return body.map(part => { return body.map<PlainText<T>>(part => {
const [, meta] = part as MinimalElement const [, meta] = part as MinimalElement
return { return {
question: meta.title, meta : meta as TypedRecord<T>,
answer: extractText(part).replace(/\n/g, ' ') text: extractText(part).replace(/\n/g, ' ')
} }
}) })
} }

View file

@ -63,5 +63,5 @@ Tja.
Ne, awas. Meld dich einfach trotzdem über meine E-Mail-Adresse und vielleicht finden wir eine Lösung. Ne, awas. Meld dich einfach trotzdem über meine E-Mail-Adresse und vielleicht finden wir eine Lösung.
Du findest weitere Kontaktmöglichkeiten auf meiner [Kontakt-Seite <IconLinkInternal />](contact/){class="text inline-flex-row"}. Du findest weitere Kontaktmöglichkeiten auf meiner [Kontakt-Seite <IconLinkInternal />](/contact/){class="text inline-flex-row"}.
:: ::