fix(andreknie.de): close SEO review gaps

This commit is contained in:
2026-07-28 22:53:37 +02:00
parent 215aacf4a4
commit 4ca2c1ece8
5 changed files with 46 additions and 14 deletions
+13 -10
View File
@@ -4,16 +4,19 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/logos/favicon.png" /> <link rel="icon" type="image/png" href="/logos/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dr. André Knie Digitale Souveränität und KI</title> <title data-seo-fallback>Dr. André Knie Digitale Souveränität und KI</title>
<meta name="description" content="Dr. André Knie unterstützt Organisationen dabei, digitale Souveränität zu erlangen und sicher von KI zu profitieren." /> <meta data-seo-fallback name="description" content="Dr. André Knie unterstützt Organisationen dabei, digitale Souveränität zu erlangen und sicher von KI zu profitieren." />
<link rel="canonical" href="https://andreknie.de/" /> <link data-seo-fallback rel="canonical" href="https://andreknie.de/" />
<meta property="og:type" content="website" /> <meta data-seo-fallback property="og:type" content="website" />
<meta property="og:url" content="https://andreknie.de/" /> <meta data-seo-fallback property="og:url" content="https://andreknie.de/" />
<meta property="og:title" content="Dr. André Knie Digitale Souveränität und KI" /> <meta data-seo-fallback property="og:title" content="Dr. André Knie Digitale Souveränität und KI" />
<meta property="og:description" content="Dr. André Knie unterstützt Organisationen dabei, digitale Souveränität zu erlangen und sicher von KI zu profitieren." /> <meta data-seo-fallback property="og:description" content="Dr. André Knie unterstützt Organisationen dabei, digitale Souveränität zu erlangen und sicher von KI zu profitieren." />
<meta property="og:image" content="https://andreknie.de/images/andre-knie.webp" /> <meta data-seo-fallback property="og:image" content="https://andreknie.de/images/andre-knie.webp" />
<meta name="twitter:card" content="summary_large_image" /> <meta data-seo-fallback name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="https://andreknie.de/images/andre-knie.webp" /> <meta data-seo-fallback name="twitter:url" content="https://andreknie.de/" />
<meta data-seo-fallback name="twitter:title" content="Dr. André Knie Digitale Souveränität und KI" />
<meta data-seo-fallback name="twitter:description" content="Dr. André Knie unterstützt Organisationen dabei, digitale Souveränität zu erlangen und sicher von KI zu profitieren." />
<meta data-seo-fallback name="twitter:image" content="https://andreknie.de/images/andre-knie.webp" />
<!-- Umami Analytics --> <!-- Umami Analytics -->
<script <script
defer defer
@@ -1,4 +1,4 @@
import React from 'react' import React, { useEffect } from 'react'
import { Helmet } from 'react-helmet-async' import { Helmet } from 'react-helmet-async'
import { absoluteUrl, BRAND_SUFFIX, DEFAULT_DESCRIPTION, DEFAULT_IMAGE, DEFAULT_TITLE, normalizeTitle } from './seo.js' import { absoluteUrl, BRAND_SUFFIX, DEFAULT_DESCRIPTION, DEFAULT_IMAGE, DEFAULT_TITLE, normalizeTitle } from './seo.js'
@@ -9,6 +9,10 @@ export default function SEOHead({ title, description, url, image, type = 'websit
const canonicalUrl = absoluteUrl(url || '/', '/') const canonicalUrl = absoluteUrl(url || '/', '/')
const imageUrl = absoluteUrl(image, DEFAULT_IMAGE) const imageUrl = absoluteUrl(image, DEFAULT_IMAGE)
useEffect(() => {
document.head.querySelectorAll('[data-seo-fallback]').forEach(node => node.remove())
}, [])
return ( return (
<Helmet> <Helmet>
<title>{siteTitle}</title> <title>{siteTitle}</title>
@@ -25,6 +25,25 @@ describe('SEOHead', () => {
expect(document.head.querySelector('link[rel="canonical"]')?.getAttribute('href')).toBe('https://andreknie.de/artikel') expect(document.head.querySelector('link[rel="canonical"]')?.getAttribute('href')).toBe('https://andreknie.de/artikel')
}) })
it('replaces static fallback metadata instead of duplicating it', () => {
document.head.insertAdjacentHTML('beforeend', `
<title data-seo-fallback>Fallback</title>
<meta data-seo-fallback name="description" content="Fallback description">
<link data-seo-fallback rel="canonical" href="https://andreknie.de/">
`)
render(
<HelmetProvider>
<SEOHead title="Artikel" description="Article description" url="/artikel" />
</HelmetProvider>,
)
expect(document.head.querySelectorAll('[data-seo-fallback]')).toHaveLength(0)
expect(document.head.querySelectorAll('title')).toHaveLength(1)
expect(document.head.querySelectorAll('meta[name="description"]')).toHaveLength(1)
expect(document.head.querySelectorAll('link[rel="canonical"]')).toHaveLength(1)
})
it('normalizes relative and absolute image URLs', () => { it('normalizes relative and absolute image URLs', () => {
const { rerender } = render( const { rerender } = render(
<HelmetProvider> <HelmetProvider>
@@ -12,7 +12,13 @@ export default function ArticleSingle() {
if (!post) { if (!post) {
return ( return (
<main className="page-container app-container" style={{ textAlign: 'center', paddingTop: '150px' }}> <main className="page-container app-container" style={{ textAlign: 'center', paddingTop: '150px' }}>
<h2>Artikel nicht gefunden</h2> <SEOHead
title="Artikel nicht gefunden"
description="Der angeforderte Artikel wurde nicht gefunden."
url={`/artikel/${slug}`}
noindex
/>
<h1>Artikel nicht gefunden</h1>
<Link to="/artikel" className="primary-button" style={{ marginTop: '20px' }}>Zurück zur Übersicht</Link> <Link to="/artikel" className="primary-button" style={{ marginTop: '20px' }}>Zurück zur Übersicht</Link>
</main> </main>
) )
@@ -27,7 +33,7 @@ export default function ArticleSingle() {
<SEOHead <SEOHead
url={`/artikel/${post.slug}`} url={`/artikel/${post.slug}`}
type="article" type="article"
title={`${post.title} | Dr. André Knie`} title={post.title}
description={post.excerpt || post.title} description={post.excerpt || post.title}
/> />
<div className="app-container" style={{ maxWidth: '800px', margin: '0 auto', paddingTop: '120px' }}> <div className="app-container" style={{ maxWidth: '800px', margin: '0 auto', paddingTop: '120px' }}>
@@ -23,7 +23,7 @@ export default function Articles() {
<main className="page-container articles-page"> <main className="page-container articles-page">
<SEOHead <SEOHead
url="/artikel" url="/artikel"
title="Artikel & Beiträge | Dr. André Knie" title="Artikel & Beiträge"
description="Aktuelle Artikel und LinkedIn-Beiträge zu den Themen Softwareentwicklung, Leadership und Technologie." description="Aktuelle Artikel und LinkedIn-Beiträge zu den Themen Softwareentwicklung, Leadership und Technologie."
/> />
<header className="page-header"> <header className="page-header">