auto-claude: subtask-3-1 - Audit existing JsonLd components and enhance struc

Enhanced JSON-LD structured data coverage with the following improvements:

## PersonJsonLd Enhancements:
- Added hasOccupation with skills and occupationLocation
- Added knowsLanguage (German, English, Serbian)
- Added worksFor reference to business
- Added nationality for geo-targeting
- Enhanced knowsAbout with Claude AI, GPT-4, LangChain
- Upgraded image to ImageObject with dimensions

## New Components:
- ContactPageJsonLd: Contact page with ContactPoint entity
- CollectionPageJsonLd: Portfolio page with ItemList for projects
- ItemListJsonLd: Generic list schema for any items

## WebSiteJsonLd Enhancement:
- Added SearchAction for sitelinks search box

## Page Updates:
- Contact page now uses ContactPageJsonLd
- Portfolio page now uses CollectionPageJsonLd and BreadcrumbJsonLd

All schemas use @id cross-references for proper entity linking.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-25 00:39:10 +01:00
co-authored by Claude Opus 4.5
parent d7f0a6d6f1
commit 223695b3cf
4 changed files with 257 additions and 3 deletions
+2 -1
View File
@@ -2,7 +2,7 @@ import { setRequestLocale } from 'next-intl/server';
import type { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';
import { ContactForm } from '@/components/contact';
import { BreadcrumbJsonLd, WebPageJsonLd } from '@/components/seo';
import { BreadcrumbJsonLd, WebPageJsonLd, ContactPageJsonLd } from '@/components/seo';
import { type Locale } from '@/i18n/config';
type Props = {
@@ -80,6 +80,7 @@ export default async function ContactPage({ params }: Props) {
url={`/${locale}/contact`}
locale={locale as Locale}
/>
<ContactPageJsonLd locale={locale as Locale} />
<ContactForm />
</>
);
+19
View File
@@ -2,6 +2,8 @@ import { setRequestLocale } from 'next-intl/server';
import type { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';
import { PortfolioGrid } from '@/components/portfolio';
import { BreadcrumbJsonLd, CollectionPageJsonLd } from '@/components/seo';
import { type Locale } from '@/i18n/config';
type Props = {
params: Promise<{ locale: string }>;
@@ -165,8 +167,25 @@ export default async function PortfolioPage({ params }: Props) {
const t = await getTranslations('portfolio');
const breadcrumbItems = [
{ name: locale === 'de' ? 'Start' : locale === 'sr' ? 'Početna' : 'Home', url: `/${locale}` },
{ name: 'Portfolio', url: `/${locale}/portfolio` },
];
// Transform projects for JSON-LD schema
const portfolioProjects = projects.map((project) => ({
name: project.title,
description: project.description,
url: `/${locale}/portfolio/${project.slug}`,
dateCreated: project.date,
technologies: project.technologies,
}));
return (
<div className="relative min-h-screen">
<BreadcrumbJsonLd items={breadcrumbItems} locale={locale as Locale} />
<CollectionPageJsonLd locale={locale as Locale} projects={portfolioProjects} />
<section className="py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-16">
+233 -2
View File
@@ -25,7 +25,12 @@ export function PersonJsonLd({ locale }: JsonLdProps) {
? 'Razvoj AI agenata i rešenja za automatizaciju. Od voice AI platformi do autonomnih web agenata.'
: 'Remote AI developer building AI agents and automation solutions for clients in USA, UK and Europe. From voice AI platforms to autonomous web agents.',
url: baseUrl,
image: `${baseUrl}/images/og-image.jpg`,
image: {
'@type': 'ImageObject',
url: `${baseUrl}/images/og-image.jpg`,
width: 1200,
height: 630,
},
email: 'info@damjan-savic.com',
telephone: '+491756950979',
address: {
@@ -34,6 +39,33 @@ export function PersonJsonLd({ locale }: JsonLdProps) {
addressRegion: 'NRW',
addressCountry: 'DE',
},
// Languages known
knowsLanguage: [
{ '@type': 'Language', name: 'German', alternateName: 'de' },
{ '@type': 'Language', name: 'English', alternateName: 'en' },
{ '@type': 'Language', name: 'Serbian', alternateName: 'sr' },
],
// Current occupation
hasOccupation: {
'@type': 'Occupation',
name: locale === 'de' ? 'Freelance KI-Entwickler' : locale === 'sr' ? 'Freelance AI Developer' : 'Freelance AI Developer',
occupationLocation: {
'@type': 'City',
name: locale === 'sr' ? 'Keln' : locale === 'en' ? 'Cologne' : 'Köln',
},
skills: [
'AI Development',
'Process Automation',
'Voice AI',
'n8n Workflows',
'SaaS Development',
'Full-Stack Development',
],
},
// Works for self (freelancer)
worksFor: {
'@id': `${baseUrl}/#business`,
},
sameAs: [
'https://www.linkedin.com/in/damjansavic/',
'https://github.com/damjansavic',
@@ -50,6 +82,9 @@ export function PersonJsonLd({ locale }: JsonLdProps) {
'Next.js',
'n8n',
'SaaS Development',
'Claude AI',
'GPT-4',
'LangChain',
],
alumniOf: [
{
@@ -69,6 +104,11 @@ export function PersonJsonLd({ locale }: JsonLdProps) {
credentialCategory: 'degree',
},
],
// Nationality for geo-targeting
nationality: {
'@type': 'Country',
name: 'Germany',
},
};
return (
@@ -719,7 +759,7 @@ export function SoftwareAppJsonLd({
);
}
// WebSite Schema for Search Box
// WebSite Schema for Search Box with SearchAction
export function WebSiteJsonLd({ locale }: JsonLdProps) {
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
@@ -747,6 +787,197 @@ export function WebSiteJsonLd({ locale }: JsonLdProps) {
{ '@type': 'Language', name: 'English', alternateName: 'en' },
{ '@type': 'Language', name: 'Serbian', alternateName: 'sr' },
],
// Potential site search action (for future implementation)
potentialAction: {
'@type': 'SearchAction',
target: {
'@type': 'EntryPoint',
urlTemplate: `${baseUrl}/${locale}/portfolio?search={search_term_string}`,
},
'query-input': 'required name=search_term_string',
},
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// ContactPage Schema for Contact Page
export function ContactPageJsonLd({ locale }: JsonLdProps) {
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
const schema = {
'@context': 'https://schema.org',
'@type': 'ContactPage',
'@id': `${baseUrl}/${locale}/contact/#webpage`,
name: locale === 'de'
? 'Kontakt - Damjan Savić'
: locale === 'sr'
? 'Kontakt - Damjan Savić'
: 'Contact - Damjan Savić',
description: locale === 'de'
? 'Kontaktieren Sie Damjan Savić für KI-Entwicklung, Prozessautomatisierung und SaaS-Projekte.'
: locale === 'sr'
? 'Kontaktirajte Damjana Savića za AI razvoj, automatizaciju procesa i SaaS projekte.'
: 'Contact Damjan Savić for AI development, process automation, and SaaS projects.',
url: `${baseUrl}/${locale}/contact`,
inLanguage: locale === 'de' ? 'de-DE' : locale === 'sr' ? 'sr-RS' : 'en-US',
isPartOf: {
'@id': `${baseUrl}/#website`,
},
about: {
'@id': `${baseUrl}/#person`,
},
mainEntity: {
'@type': 'ContactPoint',
contactType: locale === 'de' ? 'Kundenservice' : locale === 'sr' ? 'Korisnička podrška' : 'customer service',
email: 'info@damjan-savic.com',
telephone: '+491756950979',
availableLanguage: [
{ '@type': 'Language', name: 'German' },
{ '@type': 'Language', name: 'English' },
{ '@type': 'Language', name: 'Serbian' },
],
areaServed: [
{ '@type': 'Country', name: 'Germany' },
{ '@type': 'Country', name: 'United States' },
{ '@type': 'Country', name: 'United Kingdom' },
{ '@type': 'Country', name: 'Austria' },
{ '@type': 'Country', name: 'Switzerland' },
{ '@type': 'Country', name: 'Serbia' },
],
},
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// CollectionPage Schema for Portfolio with ItemList
interface PortfolioProject {
name: string;
description: string;
url: string;
image?: string;
dateCreated?: string;
technologies?: string[];
}
export function CollectionPageJsonLd({
locale,
projects,
}: {
locale: Locale;
projects: PortfolioProject[];
}) {
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
const schema = {
'@context': 'https://schema.org',
'@type': 'CollectionPage',
'@id': `${baseUrl}/${locale}/portfolio/#webpage`,
name: locale === 'de'
? 'Portfolio - Damjan Savić'
: locale === 'sr'
? 'Portfolio - Damjan Savić'
: 'Portfolio - Damjan Savić',
description: locale === 'de'
? 'Ausgewählte KI- und Automatisierungsprojekte von Damjan Savić. Von Voice AI bis n8n-Workflows.'
: locale === 'sr'
? 'Odabrani AI i automatizacija projekti Damjana Savića. Od Voice AI do n8n workflow-ova.'
: 'Selected AI and automation projects by Damjan Savić. From Voice AI to n8n workflows.',
url: `${baseUrl}/${locale}/portfolio`,
inLanguage: locale === 'de' ? 'de-DE' : locale === 'sr' ? 'sr-RS' : 'en-US',
isPartOf: {
'@id': `${baseUrl}/#website`,
},
author: {
'@id': `${baseUrl}/#person`,
},
mainEntity: {
'@type': 'ItemList',
itemListElement: projects.map((project, index) => ({
'@type': 'ListItem',
position: index + 1,
item: {
'@type': 'CreativeWork',
name: project.name,
description: project.description,
url: project.url.startsWith('http') ? project.url : `${baseUrl}${project.url}`,
...(project.image && {
image: project.image.startsWith('http') ? project.image : `${baseUrl}${project.image}`,
}),
...(project.dateCreated && { dateCreated: project.dateCreated }),
...(project.technologies && {
keywords: project.technologies.join(', '),
}),
author: {
'@id': `${baseUrl}/#person`,
},
},
})),
numberOfItems: projects.length,
},
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// Generic ItemList Schema for any list of items
interface ItemListItem {
name: string;
description?: string;
url?: string;
image?: string;
}
export function ItemListJsonLd({
name,
description,
items,
itemType = 'ListItem',
locale,
}: {
name: string;
description?: string;
items: ItemListItem[];
itemType?: string;
locale: Locale;
}) {
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
const schema = {
'@context': 'https://schema.org',
'@type': 'ItemList',
name: name,
...(description && { description }),
inLanguage: locale === 'de' ? 'de-DE' : locale === 'sr' ? 'sr-RS' : 'en-US',
itemListElement: items.map((item, index) => ({
'@type': itemType,
position: index + 1,
name: item.name,
...(item.description && { description: item.description }),
...(item.url && {
url: item.url.startsWith('http') ? item.url : `${baseUrl}${item.url}`,
}),
...(item.image && {
image: item.image.startsWith('http') ? item.image : `${baseUrl}${item.image}`,
}),
})),
numberOfItems: items.length,
};
return (
+3
View File
@@ -12,4 +12,7 @@ export {
ProfilePageJsonLd,
VideoJsonLd,
SoftwareAppJsonLd,
ContactPageJsonLd,
CollectionPageJsonLd,
ItemListJsonLd,
} from './JsonLd';