Implementierung - SEO - 01.08.2025

This commit is contained in:
2025-08-01 14:26:05 +02:00
parent 7aa0543001
commit f176743885
35 changed files with 3427 additions and 98 deletions
+160
View File
@@ -0,0 +1,160 @@
import React, { useState } from 'react';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { useTranslation } from 'react-i18next';
interface FAQItem {
question: string;
answer: string;
category?: string;
}
export function FAQSection() {
const { t, i18n } = useTranslation();
const [openItems, setOpenItems] = useState<number[]>([]);
const faqs: FAQItem[] = i18n.language === 'de' ? [
{
question: "Wie kann ich Python für Prozessautomatisierung einsetzen?",
answer: "Python eignet sich hervorragend für Prozessautomatisierung durch Libraries wie Selenium für Web-Automation, Pandas für Datenverarbeitung und Schedule für zeitgesteuerte Aufgaben. Ich entwickle maßgeschneiderte Automatisierungslösungen für Ihre Geschäftsprozesse.",
category: "python"
},
{
question: "Was macht OLLAMA ideal für lokale KI-Entwicklung?",
answer: "OLLAMA ermöglicht das Ausführen großer Sprachmodelle lokal, was Datenschutz gewährleistet und API-Kosten reduziert. Ich spezialisiere mich auf die Integration von OLLAMA in Anwendungen für individuelle KI-Lösungen ohne Cloud-Abhängigkeiten.",
category: "ai"
},
{
question: "Welche Vorteile bieten Electron Desktop Apps?",
answer: "Electron ermöglicht die Entwicklung plattformübergreifender Desktop-Anwendungen mit Web-Technologien. Sie erhalten eine native App für Windows, Mac und Linux aus einer einzigen Codebasis. Ideal für Tools mit komplexer UI und Offline-Funktionalität.",
category: "electron"
},
{
question: "Wie lange dauert die Entwicklung einer Web-Anwendung?",
answer: "Die Entwicklungsdauer hängt vom Umfang ab. Ein MVP kann in 4-8 Wochen entwickelt werden, während komplexe Enterprise-Anwendungen 3-6 Monate benötigen. Ich arbeite agil und liefere in 2-Wochen-Sprints nutzbare Zwischenergebnisse.",
category: "general"
},
{
question: "Können Sie bestehende Python-Skripte in Web-Apps umwandeln?",
answer: "Ja, ich kann Ihre Python-Skripte in moderne Web-Anwendungen mit FastAPI oder Django Backend und React Frontend transformieren. Dies ermöglicht Multi-User-Zugriff, bessere UI/UX und zentrale Datenverwaltung.",
category: "python"
},
{
question: "Wie integrieren Sie KI in bestehende Geschäftsprozesse?",
answer: "Ich analysiere Ihre Workflows und identifiziere Automatisierungspotenziale. Mit OLLAMA oder anderen KI-Modellen entwickle ich Lösungen für Textanalyse, Dokumentenverarbeitung, Kundenservice-Automation oder Datenextraktion - alles datenschutzkonform on-premise.",
category: "ai"
}
] : [
{
question: "How can I use Python for process automation?",
answer: "Python excels at process automation through libraries like Selenium for web automation, Pandas for data processing, and Schedule for timed tasks. I develop custom automation solutions tailored to your business processes.",
category: "python"
},
{
question: "What makes OLLAMA ideal for local AI development?",
answer: "OLLAMA enables running large language models locally, ensuring data privacy and reducing API costs. I specialize in integrating OLLAMA into applications for custom AI solutions without cloud dependencies.",
category: "ai"
},
{
question: "What are the advantages of Electron desktop apps?",
answer: "Electron enables cross-platform desktop app development using web technologies. You get a native app for Windows, Mac, and Linux from a single codebase. Ideal for tools requiring complex UI and offline functionality.",
category: "electron"
},
{
question: "How long does web application development take?",
answer: "Development time depends on scope. An MVP can be developed in 4-8 weeks, while complex enterprise applications require 3-6 months. I work agile and deliver usable results in 2-week sprints.",
category: "general"
},
{
question: "Can you convert existing Python scripts into web apps?",
answer: "Yes, I can transform your Python scripts into modern web applications with FastAPI or Django backend and React frontend. This enables multi-user access, better UI/UX, and centralized data management.",
category: "python"
},
{
question: "How do you integrate AI into existing business processes?",
answer: "I analyze your workflows and identify automation potential. Using OLLAMA or other AI models, I develop solutions for text analysis, document processing, customer service automation, or data extraction - all privacy-compliant on-premise.",
category: "ai"
}
];
const toggleItem = (index: number) => {
setOpenItems(prev =>
prev.includes(index)
? prev.filter(i => i !== index)
: [...prev, index]
);
};
const faqSchema = {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": faqs.map(faq => ({
"@type": "Question",
"name": faq.question,
"acceptedAnswer": {
"@type": "Answer",
"text": faq.answer
}
}))
};
return (
<section className="faq-section py-16 px-4 max-w-4xl mx-auto">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }}
/>
<h2 className="text-3xl font-bold text-white mb-8 text-center">
{t('faq.title', 'Frequently Asked Questions')}
</h2>
<div className="space-y-4">
{faqs.map((faq, index) => (
<div
key={index}
className="faq-item bg-zinc-800/50 border border-zinc-700 rounded-lg overflow-hidden"
>
<button
onClick={() => toggleItem(index)}
className="w-full px-6 py-4 text-left flex items-center justify-between hover:bg-zinc-800/70 transition-colors"
aria-expanded={openItems.includes(index)}
aria-controls={`faq-answer-${index}`}
>
<h3 className="text-lg font-medium text-white pr-4">
{faq.question}
</h3>
{openItems.includes(index) ? (
<ChevronUp className="h-5 w-5 text-zinc-400 flex-shrink-0" />
) : (
<ChevronDown className="h-5 w-5 text-zinc-400 flex-shrink-0" />
)}
</button>
<div
id={`faq-answer-${index}`}
className={`overflow-hidden transition-all duration-300 ${
openItems.includes(index) ? 'max-h-96' : 'max-h-0'
}`}
>
<div className="px-6 py-4 text-zinc-300 border-t border-zinc-700">
<p>{faq.answer}</p>
</div>
</div>
</div>
))}
</div>
<div className="mt-8 text-center">
<p className="text-zinc-400">
{t('faq.moreQuestions', 'Have more questions?')}{' '}
<a
href="/contact"
className="text-white hover:text-zinc-300 underline transition-colors"
>
{t('faq.contactUs', 'Contact us')}
</a>
</p>
</div>
</section>
);
}
+67
View File
@@ -0,0 +1,67 @@
import React from 'react';
import { useRouter } from 'react-router-dom';
import { Helmet } from 'react-helmet-async';
import { useTranslation } from 'react-i18next';
export function HreflangTags() {
const { i18n } = useTranslation();
const location = window.location;
const currentPath = location.pathname;
const siteUrl = 'https://damjan-savic.com';
const supportedLanguages = ['de', 'en', 'sr'];
const defaultLanguage = 'de';
// Extract current language from URL
const pathSegments = currentPath.split('/').filter(Boolean);
const currentLang = supportedLanguages.includes(pathSegments[0]) ? pathSegments[0] : defaultLanguage;
// Get path without language prefix
const pathWithoutLang = currentLang === defaultLanguage
? currentPath
: currentPath.replace(`/${currentLang}`, '') || '/';
// Generate hreflang URLs
const generateHreflangUrl = (lang: string) => {
if (lang === defaultLanguage) {
return `${siteUrl}${pathWithoutLang}`;
}
return `${siteUrl}/${lang}${pathWithoutLang}`;
};
return (
<Helmet>
{/* Hreflang tags for all supported languages */}
{supportedLanguages.map((lang) => (
<link
key={lang}
rel="alternate"
hrefLang={lang}
href={generateHreflangUrl(lang)}
/>
))}
{/* x-default hreflang */}
<link
rel="alternate"
hrefLang="x-default"
href={generateHreflangUrl(defaultLanguage)}
/>
{/* Additional language meta tags */}
<html lang={currentLang} />
<meta property="og:locale" content={currentLang === 'de' ? 'de_DE' : currentLang === 'en' ? 'en_US' : 'sr_RS'} />
{/* Alternate og:locale tags */}
{supportedLanguages
.filter(lang => lang !== currentLang)
.map(lang => (
<meta
key={`og-locale-${lang}`}
property="og:locale:alternate"
content={lang === 'de' ? 'de_DE' : lang === 'en' ? 'en_US' : 'sr_RS'}
/>
))}
</Helmet>
);
}
+4
View File
@@ -19,6 +19,7 @@ import LanguageSwitcher from "./LanguageSwitcher";
import { Link, useLocation } from "react-router-dom";
import Footer from "./Footer";
import { useScrollContext } from "./ScrollContext";
import PerformanceMonitor from "./PerformanceMonitor";
interface LayoutProps {
children: ReactNode;
@@ -252,6 +253,9 @@ const Layout = ({ children }: LayoutProps) => {
{/* Footer */}
<Footer />
{/* Performance Monitor */}
<PerformanceMonitor />
</div>
);
};
+199
View File
@@ -0,0 +1,199 @@
import React, { useState, useEffect } from 'react';
import { Activity, TrendingUp, AlertCircle, CheckCircle } from 'lucide-react';
interface WebVitalMetric {
name: string;
value: number;
rating: 'good' | 'needs-improvement' | 'poor';
timestamp: number;
}
interface PerformanceMetrics {
lcp: WebVitalMetric[];
inp: WebVitalMetric[];
cls: WebVitalMetric[];
fcp: WebVitalMetric[];
ttfb: WebVitalMetric[];
}
const THRESHOLDS = {
LCP: { good: 2500, poor: 4000 },
INP: { good: 200, poor: 500 },
CLS: { good: 0.1, poor: 0.25 },
FCP: { good: 1800, poor: 3000 },
TTFB: { good: 800, poor: 1800 }
};
export function PerformanceMonitor() {
const [metrics, setMetrics] = useState<PerformanceMetrics>({
lcp: [],
inp: [],
cls: [],
fcp: [],
ttfb: []
});
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
// Listen for performance metrics from webVitals
const handlePerformanceData = (event: CustomEvent) => {
const metric = event.detail as WebVitalMetric;
setMetrics(prev => ({
...prev,
[metric.name.toLowerCase()]: [...(prev[metric.name.toLowerCase() as keyof PerformanceMetrics] || []), metric].slice(-10)
}));
};
window.addEventListener('web-vitals', handlePerformanceData as EventListener);
return () => {
window.removeEventListener('web-vitals', handlePerformanceData as EventListener);
};
}, []);
const getLatestMetric = (metricName: keyof PerformanceMetrics) => {
const metricArray = metrics[metricName];
return metricArray[metricArray.length - 1];
};
const getRatingColor = (rating?: string) => {
switch (rating) {
case 'good': return 'text-green-500';
case 'needs-improvement': return 'text-yellow-500';
case 'poor': return 'text-red-500';
default: return 'text-zinc-400';
}
};
const getRatingIcon = (rating?: string) => {
switch (rating) {
case 'good': return <CheckCircle className="h-4 w-4" />;
case 'needs-improvement': return <AlertCircle className="h-4 w-4" />;
case 'poor': return <AlertCircle className="h-4 w-4" />;
default: return null;
}
};
const formatValue = (name: string, value?: number) => {
if (value === undefined) return '-';
if (name === 'CLS') return value.toFixed(3);
return `${Math.round(value)}ms`;
};
const calculateScore = () => {
const latestLCP = getLatestMetric('lcp');
const latestINP = getLatestMetric('inp');
const latestCLS = getLatestMetric('cls');
if (!latestLCP || !latestINP || !latestCLS) return null;
let score = 100;
// LCP scoring
if (latestLCP.value > THRESHOLDS.LCP.poor) score -= 33;
else if (latestLCP.value > THRESHOLDS.LCP.good) score -= 16;
// INP scoring
if (latestINP.value > THRESHOLDS.INP.poor) score -= 33;
else if (latestINP.value > THRESHOLDS.INP.good) score -= 16;
// CLS scoring
if (latestCLS.value > THRESHOLDS.CLS.poor) score -= 34;
else if (latestCLS.value > THRESHOLDS.CLS.good) score -= 18;
return Math.max(0, Math.round(score));
};
// Only show in development or with special flag
if (process.env.NODE_ENV === 'production' && !window.location.search.includes('debug=true')) {
return null;
}
return (
<>
{/* Toggle Button */}
<button
onClick={() => setIsVisible(!isVisible)}
className="fixed bottom-4 right-4 z-50 p-3 bg-zinc-800 border border-zinc-700 rounded-full shadow-lg hover:bg-zinc-700 transition-all"
aria-label="Toggle Performance Monitor"
>
<Activity className="h-5 w-5 text-white" />
</button>
{/* Performance Panel */}
{isVisible && (
<div className="fixed bottom-20 right-4 z-50 w-80 bg-zinc-900 border border-zinc-800 rounded-lg shadow-xl p-4">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-white flex items-center gap-2">
<Activity className="h-5 w-5" />
Core Web Vitals
</h3>
<button
onClick={() => setIsVisible(false)}
className="text-zinc-400 hover:text-white"
>
×
</button>
</div>
{/* Overall Score */}
{calculateScore() !== null && (
<div className="mb-4 p-3 bg-zinc-800 rounded-lg">
<div className="flex items-center justify-between">
<span className="text-sm text-zinc-400">Performance Score</span>
<span className={`text-2xl font-bold ${
calculateScore()! >= 90 ? 'text-green-500' :
calculateScore()! >= 50 ? 'text-yellow-500' :
'text-red-500'
}`}>
{calculateScore()}
</span>
</div>
</div>
)}
{/* Metrics */}
<div className="space-y-3">
{[
{ key: 'lcp', label: 'Largest Contentful Paint', threshold: THRESHOLDS.LCP },
{ key: 'inp', label: 'Interaction to Next Paint', threshold: THRESHOLDS.INP },
{ key: 'cls', label: 'Cumulative Layout Shift', threshold: THRESHOLDS.CLS },
{ key: 'fcp', label: 'First Contentful Paint', threshold: THRESHOLDS.FCP },
{ key: 'ttfb', label: 'Time to First Byte', threshold: THRESHOLDS.TTFB }
].map(({ key, label }) => {
const latest = getLatestMetric(key as keyof PerformanceMetrics);
return (
<div key={key} className="flex items-center justify-between p-2 bg-zinc-800/50 rounded">
<div className="flex-1">
<div className="text-sm text-zinc-300">{label}</div>
<div className="text-xs text-zinc-500 uppercase">{key}</div>
</div>
<div className={`flex items-center gap-2 ${getRatingColor(latest?.rating)}`}>
{getRatingIcon(latest?.rating)}
<span className="font-mono text-sm">
{formatValue(key.toUpperCase(), latest?.value)}
</span>
</div>
</div>
);
})}
</div>
{/* Info */}
<div className="mt-4 pt-4 border-t border-zinc-800">
<p className="text-xs text-zinc-500">
Real user metrics Updates live
<a
href="https://web.dev/vitals/"
target="_blank"
rel="noopener noreferrer"
className="ml-1 text-blue-400 hover:text-blue-300"
>
Learn more
</a>
</p>
</div>
</div>
)}
</>
);
}
+161
View File
@@ -0,0 +1,161 @@
import React from 'react';
import { ExternalLink, Github, Globe, Zap, TrendingUp, Users } from 'lucide-react';
import { ProjectSchema } from './schemas/ProjectSchema';
interface ProjectShowcaseProps {
project: {
id: string;
title: string;
description: string;
technicalChallenge: string;
businessImpact: {
roi?: string;
performanceGain?: string;
usersImpacted?: string;
};
technologies: string[];
category: string;
dateCreated: string;
liveUrl?: string;
githubUrl?: string;
screenshotUrl?: string;
features: string[];
codeExample?: string;
};
}
export function ProjectShowcase({ project }: ProjectShowcaseProps) {
return (
<article className="project-showcase bg-zinc-900 rounded-lg overflow-hidden">
<ProjectSchema project={project} />
{/* Hero Section with Screenshot */}
{project.screenshotUrl && (
<div className="relative aspect-video">
<img
src={project.screenshotUrl}
alt={`Screenshot of ${project.title}`}
className="w-full h-full object-cover"
loading="lazy"
/>
<div className="absolute inset-0 bg-gradient-to-t from-zinc-900 via-transparent to-transparent" />
</div>
)}
<div className="p-8 space-y-8">
{/* Title and Description */}
<header>
<h2 className="text-3xl font-bold text-white mb-4">{project.title}</h2>
<p className="text-lg text-zinc-300 leading-relaxed">{project.description}</p>
</header>
{/* For Recruiters: Technical Challenge */}
<section className="technical-challenge">
<div className="flex items-center gap-2 mb-3">
<Zap className="h-5 w-5 text-blue-500" />
<h3 className="text-xl font-semibold text-white">Technical Challenge</h3>
</div>
<p className="text-zinc-300 bg-zinc-800/50 p-4 rounded-lg border border-zinc-700">
{project.technicalChallenge}
</p>
</section>
{/* For Clients: Business Impact */}
<section className="business-impact">
<div className="flex items-center gap-2 mb-3">
<TrendingUp className="h-5 w-5 text-green-500" />
<h3 className="text-xl font-semibold text-white">Business Impact</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{project.businessImpact.roi && (
<div className="bg-zinc-800/50 p-4 rounded-lg border border-zinc-700">
<p className="text-sm text-zinc-400 mb-1">ROI</p>
<p className="text-2xl font-bold text-green-400">{project.businessImpact.roi}</p>
</div>
)}
{project.businessImpact.performanceGain && (
<div className="bg-zinc-800/50 p-4 rounded-lg border border-zinc-700">
<p className="text-sm text-zinc-400 mb-1">Performance</p>
<p className="text-2xl font-bold text-blue-400">{project.businessImpact.performanceGain}</p>
</div>
)}
{project.businessImpact.usersImpacted && (
<div className="bg-zinc-800/50 p-4 rounded-lg border border-zinc-700">
<p className="text-sm text-zinc-400 mb-1">Users Impacted</p>
<p className="text-2xl font-bold text-purple-400">{project.businessImpact.usersImpacted}</p>
</div>
)}
</div>
</section>
{/* Technology Stack with Keywords */}
<section className="tech-stack">
<h3 className="text-xl font-semibold text-white mb-3">Technologies Used</h3>
<div className="flex flex-wrap gap-2">
{project.technologies.map(tech => (
<span
key={tech}
className="tech-tag px-3 py-1 bg-zinc-800 border border-zinc-700 text-zinc-300 rounded-full text-sm hover:border-zinc-600 transition-colors"
>
{tech}
</span>
))}
</div>
</section>
{/* Key Features */}
{project.features && project.features.length > 0 && (
<section className="features">
<h3 className="text-xl font-semibold text-white mb-3">Key Features</h3>
<ul className="space-y-2">
{project.features.map((feature, index) => (
<li key={index} className="flex items-start gap-2">
<span className="text-blue-500 mt-0.5"></span>
<span className="text-zinc-300">{feature}</span>
</li>
))}
</ul>
</section>
)}
{/* Code Quality Showcase */}
{project.codeExample && (
<section className="code-showcase">
<h3 className="text-xl font-semibold text-white mb-3">Code Example</h3>
<pre className="bg-zinc-950 p-4 rounded-lg overflow-x-auto border border-zinc-800">
<code className="text-sm text-zinc-300 font-mono">
{project.codeExample}
</code>
</pre>
</section>
)}
{/* Links */}
<div className="project-links flex gap-4 pt-4">
{project.liveUrl && (
<a
href={project.liveUrl}
rel="noopener noreferrer"
target="_blank"
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
<Globe className="h-4 w-4" />
View Live Project
</a>
)}
{project.githubUrl && (
<a
href={project.githubUrl}
rel="noopener noreferrer"
target="_blank"
className="flex items-center gap-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-white border border-zinc-700 rounded-lg transition-colors"
>
<Github className="h-4 w-4" />
View on GitHub
</a>
)}
</div>
</div>
</article>
);
}
+79 -30
View File
@@ -1,6 +1,10 @@
import React from 'react';
import { Helmet } from 'react-helmet-async';
import { useTranslation } from 'react-i18next';
import { HreflangTags } from './HreflangTags';
import { seoContent as seoContentDe } from '../i18n/locales/de/seo-content';
import { seoContent as seoContentEn } from '../i18n/locales/en/seo-content';
import { AutoBreadcrumbs } from './schemas/BreadcrumbSchema';
interface SEOProps {
title?: string;
@@ -8,14 +12,22 @@ interface SEOProps {
image?: string;
article?: boolean;
schema?: object;
keywords?: string;
author?: string;
datePublished?: string;
dateModified?: string;
}
const SEO: React.FC<SEOProps> = ({
title,
description = 'JTL Integration Expert and Digital Commerce Specialist with extensive experience in e-commerce solutions and warehouse management systems.',
image = 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
description,
image = '/portrait.jpg',
article = false,
schema,
keywords,
author = 'Damjan Savić',
datePublished,
dateModified,
}) => {
// Nur i18n wird benötigt t wurde entfernt, da es nicht genutzt wird.
const { i18n } = useTranslation();
@@ -24,31 +36,72 @@ const SEO: React.FC<SEOProps> = ({
const siteUrl = 'https://damjan-savic.com';
const currentUrl = typeof window !== 'undefined' ? window.location.href : siteUrl;
const currentLanguage = i18n.language;
// SEO-Content basierend auf Sprache auswählen
const seoContent = currentLanguage === 'de' ? seoContentDe : seoContentEn;
const defaultDescription = description || seoContent.hero.description;
// Default schema für die Website
const defaultSchema = {
'@context': 'https://schema.org',
'@type': 'Person',
name: 'Damjan Savić',
alternateName: 'Damjan Savic',
url: siteUrl,
image: image,
description: description,
image: `${siteUrl}${image}`,
description: defaultDescription,
sameAs: [
'https://linkedin.com/in/damjansavic',
'https://github.com/damjansavic'
],
jobTitle: 'JTL Integration Expert',
jobTitle: ['Fullstack Developer', 'Software Engineer', 'KI Spezialist'],
worksFor: {
'@type': 'Organization',
name: 'Independent Consultant'
name: 'CoderConda'
},
knowsAbout: [
'JTL-Wawi',
'E-commerce',
'Warehouse Management Systems',
'Digital Commerce',
'System Integration'
]
'Python Development',
'JavaScript Development',
'React.js',
'Next.js',
'TypeScript',
'Electron Desktop Applications',
'Künstliche Intelligenz (KI/AI)',
'OLLAMA AI/ML',
'ERP Systems Integration',
'E-Commerce Development',
'Process Automation',
'Backend Development',
'Frontend Development',
'Full Stack Development'
],
hasSkill: [
{
'@type': 'DefinedTerm',
name: 'Python Development',
description: 'Expert-level Python programming for automation, backend development, and AI/ML applications'
},
{
'@type': 'DefinedTerm',
name: 'JavaScript/TypeScript Development',
description: 'Full-stack JavaScript development with React, Next.js, Node.js, and TypeScript'
},
{
'@type': 'DefinedTerm',
name: 'AI/ML with OLLAMA',
description: 'Implementation of AI solutions using OLLAMA for local language models'
},
{
'@type': 'DefinedTerm',
name: 'ERP & E-Commerce Integration',
description: 'Custom ERP system development and e-commerce platform integration'
}
],
address: {
'@type': 'PostalAddress',
addressCountry: 'DE',
addressLocality: 'Köln'
}
};
// Sicherstellen, dass supportedLngs ein Array ist, bevor .filter verwendet wird
@@ -62,26 +115,21 @@ const SEO: React.FC<SEOProps> = ({
: [];
return (
<Helmet>
{/* Basic meta tags */}
<html lang={currentLanguage} />
<title>{fullTitle}</title>
<meta name="description" content={description} />
<meta name="image" content={image} />
<link rel="canonical" href={currentUrl} />
{/* Language alternates */}
{alternateUrls?.map(
({ hrefLang, href }: { hrefLang: string; href: string }) => (
<link key={hrefLang} rel="alternate" hrefLang={hrefLang} href={href} />
)
)}
<link rel="alternate" hrefLang="x-default" href={siteUrl} />
<>
<HreflangTags />
<AutoBreadcrumbs />
<Helmet>
{/* Basic meta tags */}
<html lang={currentLanguage} />
<title>{fullTitle}</title>
<meta name="description" content={defaultDescription} />
<meta name="image" content={image} />
<link rel="canonical" href={currentUrl} />
{/* Open Graph meta tags */}
<meta property="og:url" content={currentUrl} />
<meta property="og:title" content={fullTitle} />
<meta property="og:description" content={description} />
<meta property="og:description" content={defaultDescription} />
<meta property="og:image" content={image} />
<meta property="og:type" content={article ? 'article' : 'website'} />
<meta property="og:site_name" content={siteTitle} />
@@ -95,7 +143,7 @@ const SEO: React.FC<SEOProps> = ({
{/* Twitter Card meta tags */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={fullTitle} />
<meta name="twitter:description" content={description} />
<meta name="twitter:description" content={defaultDescription} />
<meta name="twitter:image" content={image} />
{/* Additional meta tags */}
@@ -103,7 +151,7 @@ const SEO: React.FC<SEOProps> = ({
<meta name="theme-color" content="#000000" />
<meta
name="keywords"
content="JTL, E-commerce, Warehouse Management, Digital Commerce, System Integration, JTL-Wawi, WMS"
content={keywords || "Damjan Savić, Fullstack Developer, Python, JavaScript, React, Next.js, TypeScript, Electron, KI, AI, OLLAMA, ERP, E-Commerce, Prozessautomatisierung, Backend, Frontend, Web Development"}
/>
<meta name="author" content="Damjan Savić" />
@@ -112,6 +160,7 @@ const SEO: React.FC<SEOProps> = ({
{JSON.stringify(schema || defaultSchema)}
</script>
</Helmet>
</>
);
};
+102
View File
@@ -0,0 +1,102 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
interface BreadcrumbItem {
name: string;
url: string;
}
interface BreadcrumbSchemaProps {
items: BreadcrumbItem[];
}
export function BreadcrumbSchema({ items }: BreadcrumbSchemaProps) {
const schema = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": items.map((item, index) => ({
"@type": "ListItem",
"position": index + 1,
"name": item.name,
"item": item.url
}))
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// Hook to generate breadcrumb items based on current route
export function useBreadcrumbs(pathname: string) {
const { t, i18n } = useTranslation();
const siteUrl = 'https://damjan-savic.com';
const currentLang = i18n.language;
const segments = pathname.split('/').filter(Boolean);
const breadcrumbs: BreadcrumbItem[] = [];
// Always add home
breadcrumbs.push({
name: t('navigation.home', 'Home'),
url: currentLang === 'de' ? siteUrl : `${siteUrl}/${currentLang}`
});
// Build breadcrumbs from path segments
let currentPath = currentLang === 'de' ? '' : `/${currentLang}`;
segments.forEach((segment, index) => {
// Skip language segment
if (index === 0 && ['en', 'de', 'sr'].includes(segment)) {
return;
}
currentPath += `/${segment}`;
// Map segment to readable name
let name = segment;
switch (segment) {
case 'portfolio':
name = t('navigation.portfolio', 'Portfolio');
break;
case 'blog':
name = t('navigation.blog', 'Blog');
break;
case 'about':
name = t('navigation.about', 'About');
break;
case 'contact':
name = t('navigation.contact', 'Contact');
break;
default:
// For dynamic segments like project names, format them nicely
name = segment
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
breadcrumbs.push({
name,
url: `${siteUrl}${currentPath}`
});
});
return breadcrumbs;
}
// Component with automatic breadcrumb generation
export function AutoBreadcrumbs() {
const pathname = window.location.pathname;
const breadcrumbs = useBreadcrumbs(pathname);
// Don't show breadcrumbs on home page
if (breadcrumbs.length <= 1) {
return null;
}
return <BreadcrumbSchema items={breadcrumbs} />;
}
+160
View File
@@ -0,0 +1,160 @@
import React from 'react';
interface HowToStep {
name: string;
text: string;
image?: string;
url?: string;
}
interface HowToSchemaProps {
title: string;
description: string;
totalTime?: string;
estimatedCost?: {
value: string;
currency: string;
};
supply?: string[];
tool?: string[];
steps: HowToStep[];
image?: string;
video?: {
name: string;
description: string;
thumbnailUrl: string;
uploadDate: string;
duration: string;
embedUrl: string;
};
}
export function HowToSchema({
title,
description,
totalTime,
estimatedCost,
supply,
tool,
steps,
image,
video
}: HowToSchemaProps) {
const schema = {
"@context": "https://schema.org",
"@type": "HowTo",
"name": title,
"description": description,
"image": image,
...(totalTime && { "totalTime": totalTime }),
...(estimatedCost && {
"estimatedCost": {
"@type": "MonetaryAmount",
"currency": estimatedCost.currency,
"value": estimatedCost.value
}
}),
...(supply && supply.length > 0 && {
"supply": supply.map(item => ({
"@type": "HowToSupply",
"name": item
}))
}),
...(tool && tool.length > 0 && {
"tool": tool.map(item => ({
"@type": "HowToTool",
"name": item
}))
}),
"step": steps.map((step, index) => ({
"@type": "HowToStep",
"name": step.name,
"text": step.text,
"position": index + 1,
...(step.image && { "image": step.image }),
...(step.url && { "url": step.url })
})),
...(video && {
"video": {
"@type": "VideoObject",
"name": video.name,
"description": video.description,
"thumbnailUrl": video.thumbnailUrl,
"uploadDate": video.uploadDate,
"duration": video.duration,
"embedUrl": video.embedUrl
}
})
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// Example HowTo content for common queries
export const howToExamples = {
pythonAutomation: {
title: "Wie kann ich Python für Prozessautomatisierung einsetzen?",
description: "Schritt-für-Schritt Anleitung zur Automatisierung von Geschäftsprozessen mit Python",
totalTime: "PT30M",
tool: ["Python 3.8+", "pip", "Virtual Environment", "Code Editor (VS Code)"],
steps: [
{
name: "Python-Umgebung einrichten",
text: "Installieren Sie Python 3.8 oder höher und erstellen Sie eine virtuelle Umgebung mit 'python -m venv automation-env'"
},
{
name: "Notwendige Libraries installieren",
text: "Aktivieren Sie die virtuelle Umgebung und installieren Sie benötigte Pakete: pip install selenium pandas schedule requests"
},
{
name: "Automatisierungsskript erstellen",
text: "Erstellen Sie ein Python-Skript, das Ihre spezifischen Aufgaben automatisiert. Nutzen Sie Selenium für Web-Automation, Pandas für Datenverarbeitung"
},
{
name: "Zeitgesteuerte Ausführung einrichten",
text: "Verwenden Sie die Schedule-Library oder Cron-Jobs (Linux/Mac) bzw. Task Scheduler (Windows) für regelmäßige Ausführung"
},
{
name: "Monitoring und Logging implementieren",
text: "Fügen Sie Logging hinzu, um Fehler zu tracken und den Erfolg der Automatisierung zu überwachen"
}
]
},
ollamaSetup: {
title: "How to Set Up OLLAMA for Local AI Development",
description: "Complete guide to installing and using OLLAMA for privacy-focused AI applications",
totalTime: "PT45M",
tool: ["OLLAMA CLI", "Python 3.8+", "8GB+ RAM", "Terminal/Command Prompt"],
steps: [
{
name: "Install OLLAMA",
text: "Download and install OLLAMA from ollama.ai. For Mac/Linux: curl -fsSL https://ollama.ai/install.sh | sh"
},
{
name: "Download a Language Model",
text: "Pull a model like Llama 2: ollama pull llama2. This downloads the model locally to your machine"
},
{
name: "Test the Model",
text: "Run the model interactively: ollama run llama2. Ask a test question to verify it's working"
},
{
name: "Install Python Integration",
text: "Install the Python library: pip install ollama. This allows you to use OLLAMA in your Python applications"
},
{
name: "Create Your First Application",
text: "Write Python code to interact with OLLAMA: import ollama; response = ollama.chat(model='llama2', messages=[{'role': 'user', 'content': 'Hello'}])"
},
{
name: "Optimize for Production",
text: "Configure model parameters, implement caching, and set up proper error handling for production use"
}
]
}
};
+85
View File
@@ -0,0 +1,85 @@
import React from 'react';
export function PersonSchema() {
const personSchema = {
"@context": "https://schema.org",
"@type": "Person",
"@id": "https://damjan-savic.com/#person",
"name": "Damjan Savić",
"alternateName": "Damjan Savic",
"jobTitle": ["Fullstack Developer", "Software Engineer", "KI Spezialist"],
"description": "Erfahrener Fullstack Entwickler spezialisiert auf Python, JavaScript, React, Next.js, TypeScript, Electron Desktop Apps, Automatisierungslösungen, KI/ML mit OLLAMA, ERP-Systeme, E-Commerce und Prozessautomatisierung",
"url": "https://damjan-savic.com",
"image": "https://damjan-savic.com/portrait.jpg",
"sameAs": [
"https://github.com/damjansavic",
"https://linkedin.com/in/damjansavic"
],
"knowsAbout": [
"Python Development",
"JavaScript Development",
"React.js",
"Next.js",
"TypeScript",
"Electron Desktop Applications",
"Künstliche Intelligenz (KI/AI)",
"OLLAMA AI/ML",
"ERP Systems Integration",
"E-Commerce Development",
"Process Automation",
"Backend Development",
"Frontend Development",
"Full Stack Development"
],
"hasSkill": [
{
"@type": "DefinedTerm",
"name": "Python Development",
"description": "Expert-level Python programming for automation, backend development, and AI/ML applications"
},
{
"@type": "DefinedTerm",
"name": "JavaScript/TypeScript Development",
"description": "Full-stack JavaScript development with React, Next.js, Node.js, and TypeScript"
},
{
"@type": "DefinedTerm",
"name": "AI/ML with OLLAMA",
"description": "Implementation of AI solutions using OLLAMA for local language models"
},
{
"@type": "DefinedTerm",
"name": "ERP & E-Commerce Integration",
"description": "Custom ERP system development and e-commerce platform integration"
},
{
"@type": "DefinedTerm",
"name": "Electron Desktop Development",
"description": "Cross-platform desktop application development with Electron and modern web technologies"
},
{
"@type": "DefinedTerm",
"name": "Process Automation",
"description": "Business process automation using Python, APIs, and custom integration solutions"
}
],
"address": {
"@type": "PostalAddress",
"addressLocality": "Köln",
"addressRegion": "NRW",
"addressCountry": "DE"
},
"worksFor": {
"@type": "Organization",
"name": "CoderConda",
"description": "Moderne Softwareentwicklung, KI-Integration und Prozessautomatisierung"
}
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(personSchema) }}
/>
);
}
+58
View File
@@ -0,0 +1,58 @@
import React from 'react';
interface Project {
title: string;
description: string;
category: string;
technologies: string[];
dateCreated: string;
url?: string;
liveUrl?: string;
githubUrl?: string;
screenshotUrl?: string;
features?: string[];
technicalChallenge?: string;
businessImpact?: {
roi?: string;
performanceGain?: string;
usersImpacted?: string;
};
}
interface ProjectSchemaProps {
project: Project;
}
export function ProjectSchema({ project }: ProjectSchemaProps) {
const schema = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": project.title,
"description": project.description,
"applicationCategory": project.category,
"operatingSystem": "Web-based",
"programmingLanguage": project.technologies,
"author": {
"@type": "Person",
"name": "Damjan Savić",
"@id": "https://damjan-savic.com/#person"
},
"dateCreated": project.dateCreated,
"url": project.url || project.liveUrl,
"screenshot": project.screenshotUrl,
"featureList": project.features || [],
"softwareRequirements": "Modern web browser with JavaScript enabled",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "EUR"
}
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}