SEO JSON-LD improvement

This commit is contained in:
Sabrina Hennrich 2025-05-28 09:45:02 +02:00
parent d6884ba09c
commit ecd2829c95
7 changed files with 165 additions and 16 deletions

View File

@ -24,7 +24,7 @@ export function usePageMeta () {
// Canonical URL
const config = useRuntimeConfig()
const canonical = `${config.public.appUrl}${route.path}`
const canonical = `${config.public.appUrl}${page.pageLink}`
// Robots Meta Tag
const robotsContent = route.path === '/danke' ? 'noindex, nofollow' : 'index, follow'
@ -83,6 +83,7 @@ export function usePageMeta () {
{ property: 'og:title', content: metaTitle },
{ property: 'og:description', content: metaDescription },
{ property: 'og:image', content: metaImage },
{ property: 'og:type', content: 'website' },
{ name: 'twitter:title', content: metaTitle },
{ name: 'twitter:description', content: metaDescription },
{ name: 'twitter:image', content: metaImage }

View File

@ -17,7 +17,13 @@
</template>
<script setup lang="ts">
// Nuxt 3 behandelt den 404-Status automatisch
import { createError } from 'nuxt/app'
// Wichtig: Der 404-Status **muss hier geworfen werden**
throw createError({
statusCode: 404,
statusMessage: 'Seite nicht gefunden'
})
</script>
<style lang="sass">

View File

@ -56,6 +56,7 @@
import { useHtmlConverter } from '@/composables/useHTMLConverter'
import SideBarNaviSlider from '@/components/SideBarNaviSlider.vue'
import { useI18n } from 'vue-i18n'
const runtimeConfig = useRuntimeConfig();
const route = useRoute()
const slug = route.params.link
@ -71,8 +72,6 @@
if (!articles.value) return null
return articles.value.find(item => item.slug === slug) ?? null
})
const { convertToHTML } = useHtmlConverter()
const htmlContent = (content) => convertToHTML(content)
@ -87,6 +86,74 @@
const d = new Date(article.value.dateModified)
return d.toLocaleDateString('de-DE')
})
// ARTIKEL META AND JSON_LD INFOS
watchEffect(() => {
if (!article.value) return
const truncate = (text, maxLength) => {
if (!text) return ''
return text.length <= maxLength ? text : text.slice(0, maxLength - 1).trimEnd() + '…'
}
const rawTitle = article.value.header
const rawDescription = article.value.teaser
const metaTitle = truncate(rawTitle, 60)
const metaDescription = truncate(rawDescription, 160)
const metaImage = `${runtimeConfig.public.cmsBaseUrl}${article.value.image?.url}` || 'https://strapi.digimedialoop.de/uploads/DML_Logo_Info_c4011028f9.png'
const metaKeywords = article.value.SEO?.keywords || ''
const metaType = article.value.SEO?.type || 'article'
const canonical = `${runtimeConfig.public.appUrl}${route.fullPath}`
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'NewsArticle',
headline: rawTitle,
image: [metaImage],
datePublished: article.value.dateCreated,
dateModified: article.value.dateModified,
author: {
'@type': 'Person',
name: author
},
publisher: {
'@type': 'Organization',
name: 'digimedialoop',
logo: {
'@type': 'ImageObject',
url: 'https://strapi.digimedialoop.de/uploads/DML_Logo_Info_c4011028f9.png'
}
},
description: metaDescription
}
useHead({
title: metaTitle,
meta: [
{ name: 'description', content: metaDescription },
{ name: 'keywords', content: metaKeywords },
{ name: 'robots', content: 'index, follow' },
{ property: 'og:title', content: metaTitle },
{ property: 'og:description', content: metaDescription },
{ property: 'og:image', content: metaImage },
{ property: 'og:type', content: metaType },
{ name: 'twitter:title', content: metaTitle },
{ name: 'twitter:description', content: metaDescription },
{ name: 'twitter:image', content: metaImage }
],
link: [
{ rel: 'canonical', href: canonical }
],
script: [
{
type: 'application/ld+json',
children: JSON.stringify(jsonLd)
}
]
})
})
</script>
<style scoped lang="sass">

View File

@ -33,7 +33,7 @@
format="webp"
sizes="100vw"
class="background-image"
alt=""
alt="AI and human"
aria-hidden="true"
/>
<div class="container-15 content">

View File

@ -100,12 +100,9 @@ const localePath = useLocalePath()
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
import { storeToRefs } from 'pinia'
import { useMainStore } from '@/stores/main'
const mainStore = useMainStore()
const { cmsUrl, projects, dataFetched } = storeToRefs(mainStore)
const project = computed(() => mainStore.getProjectByLink(route.params.link))
const customer = computed(() => {
if (!project.value || !project.value.customer) return null
@ -125,8 +122,6 @@ watch(project, (newProject) => {
}
})
import { useHtmlConverter } from '@/composables/useHTMLConverter'
const { convertToHTML } = useHtmlConverter()
const htmlContent = (data: string) => {
@ -138,7 +133,83 @@ const setCurrentImage = (image: any) => {
currentImage.value = image
}
// META INFO UND JSON_LD für Projekte
const runtimeConfig = useRuntimeConfig()
watchEffect(() => {
if (!project.value) return
const metaTitle = project.value.projectTitle.length > 60
? project.value.projectTitle.slice(0, 57).trim() + '…'
: project.value.projectTitle
const firstParagraph = project.value.projectDescription?.[0]?.children?.[0]?.text || ''
const metaDescription = firstParagraph.length > 160
? firstParagraph.slice(0, 157).trim() + '…'
: firstParagraph
const customerCompany = project.value.customer?.company || ''
const customerCity = project.value.customer?.city || ''
const technologyKeywords = project.value.technologies?.map(t => t.titel).join(', ') || ''
const metaKeywords = [customerCompany, customerCity, technologyKeywords]
.filter(Boolean)
.join(', ')
const metaImage = project.value.projectImages?.[0]?.url
? `${runtimeConfig.public.cmsBaseUrl}${project.value.projectImages[0].url}`
: 'https://strapi.digimedialoop.de/uploads/DML_Logo_Info_c4011028f9.png'
const canonical = `${runtimeConfig.public.appUrl}${route.fullPath}`
const jsonLd = {
"@context": "https://schema.org",
"@type": "CreativeWork",
name: project.value.projectTitle,
description: metaDescription,
url: canonical,
image: metaImage,
datePublished: project.value.launchDate,
author: {
"@type": "Organization",
name: "digimedialoop"
},
publisher: {
"@type": "Organization",
name: "digimedialoop",
logo: {
"@type": "ImageObject",
url: "https://strapi.digimedialoop.de/uploads/DML_Logo_Info_c4011028f9.png"
}
}
}
useHead({
title: metaTitle,
meta: [
{ name: 'description', content: metaDescription },
{ name: 'keywords', content: metaKeywords },
{ name: 'robots', content: 'index, follow' },
{ property: 'og:title', content: metaTitle },
{ property: 'og:description', content: metaDescription },
{ property: 'og:image', content: metaImage },
{ property: 'og:type', content: 'website' },
{ name: 'twitter:title', content: metaTitle },
{ name: 'twitter:description', content: metaDescription },
{ name: 'twitter:image', content: metaImage }
],
link: [
{ rel: 'canonical', href: canonical }
],
script: [
{
type: 'application/ld+json',
children: JSON.stringify(jsonLd)
}
]
})
})
</script>
<style lang="sass">

View File

@ -84,7 +84,7 @@ function toggleContactBubble() {
// Erstelle das JSON-LD für alle Projekte
const jsonLdProjects = computed(() => {
if (!projects.value || !Array.isArray(projects.value) || projects.value.length === 0) return null;
const origin = typeof window !== 'undefined' ? window.location.origin : '';
return {
@ -92,15 +92,19 @@ const jsonLdProjects = computed(() => {
"@type": "ItemList",
"itemListElement": projects.value.map((project, index) => ({
"@type": "ListItem",
"position": index + 1,
"url": origin + (project.link ? getProjectLink(project.link) : ''),
"position": index + 1,
"item": {
"@type": "CreativeWork", // alternativ "WebPage", wenn es sich um Projektseiten handelt
"@id": origin + (project.link ? getProjectLink(project.link) : ''),
"name": project.projectTitle || 'Projekt',
"image": cmsUrl + (project.projectImages?.[0]?.url) || '',
"image": cmsUrl + (project.projectImages?.[0]?.url || ''),
"description": project.projectDescription?.[0]?.children?.[0]?.text || ''
}))
}
}))
}
})
// useHead einbinden, wenn Projekte da sind
watchEffect(() => {
if (jsonLdProjects.value) {

View File

@ -6,7 +6,7 @@
src="/uploads/large_DML_Home_Hero_4f27bc7f8e.webp"
class="hero-bg"
sizes="sm:100vw md:100vw lg:100vw"
alt=""
alt="website example"
aria-hidden="true"
priority
loading="eager"