fix(andreknie.de): close SEO review gaps
This commit is contained in:
@@ -4,16 +4,19 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/logos/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>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." />
|
||||
<link rel="canonical" href="https://andreknie.de/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://andreknie.de/" />
|
||||
<meta 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 property="og:image" content="https://andreknie.de/images/andre-knie.webp" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:image" content="https://andreknie.de/images/andre-knie.webp" />
|
||||
<title data-seo-fallback>Dr. André Knie – Digitale Souveränität und KI</title>
|
||||
<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 data-seo-fallback rel="canonical" href="https://andreknie.de/" />
|
||||
<meta data-seo-fallback property="og:type" content="website" />
|
||||
<meta data-seo-fallback property="og:url" content="https://andreknie.de/" />
|
||||
<meta data-seo-fallback property="og:title" content="Dr. André Knie – Digitale Souveränität und KI" />
|
||||
<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 data-seo-fallback property="og:image" content="https://andreknie.de/images/andre-knie.webp" />
|
||||
<meta data-seo-fallback name="twitter:card" content="summary_large_image" />
|
||||
<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 -->
|
||||
<script
|
||||
defer
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { Helmet } from 'react-helmet-async'
|
||||
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 imageUrl = absoluteUrl(image, DEFAULT_IMAGE)
|
||||
|
||||
useEffect(() => {
|
||||
document.head.querySelectorAll('[data-seo-fallback]').forEach(node => node.remove())
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Helmet>
|
||||
<title>{siteTitle}</title>
|
||||
|
||||
@@ -25,6 +25,25 @@ describe('SEOHead', () => {
|
||||
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', () => {
|
||||
const { rerender } = render(
|
||||
<HelmetProvider>
|
||||
|
||||
@@ -12,7 +12,13 @@ export default function ArticleSingle() {
|
||||
if (!post) {
|
||||
return (
|
||||
<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>
|
||||
</main>
|
||||
)
|
||||
@@ -27,7 +33,7 @@ export default function ArticleSingle() {
|
||||
<SEOHead
|
||||
url={`/artikel/${post.slug}`}
|
||||
type="article"
|
||||
title={`${post.title} | Dr. André Knie`}
|
||||
title={post.title}
|
||||
description={post.excerpt || post.title}
|
||||
/>
|
||||
<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">
|
||||
<SEOHead
|
||||
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."
|
||||
/>
|
||||
<header className="page-header">
|
||||
|
||||
Reference in New Issue
Block a user