structured-data-generator
Generate Schema.org JSON-LD for Swiss/Spanish SEO (Person, JobPosting, CreativeWork, Organization)
- Category: discovered
- Tags: github, community
- Version: 1.0.0
- Author: walid
- Downloads: 0
- Updated: 2025-11-22
Download the latest version (v1.0.0) from this page.
SKILL.md
---
name: structured-data-generator
description: Generate Schema.org JSON-LD for Swiss/Spanish SEO (Person, JobPosting, CreativeWork, Organization)
activation:
keywords:
- structured data
- json-ld
- schema.org
- seo schema
- rich snippets
triggers:
- "@type"
- "@context"
auto_load_project: cv-astro
---
# Structured Data Generator
## What This Is
Generate SEO-optimized JSON-LD schemas for CV_Astro:
- **Person** schema (homepage)
- **JobPosting** schema (experience section)
- **CreativeWork** schema (projects)
- **Organization** schema (companies)
- Swiss & Spanish market optimization
**For**: CV_Astro project (SEO for Switzerland 🇨🇭 + Spain 🇪🇸)
---
## When to Activate
Auto-activates when keywords detected:
- "add structured data"
- "generate json-ld"
- "schema.org person"
- "job posting schema"
---
## Supported Schemas
### 1. Person Schema (Homepage)
**Use**: Main profile page, about page
```json
{
"@context": "https://schema.org",
"@type": "Person",
"name": "Oriol Macías",
"url": "https://oriolmacias.dev",
"image": "https://oriolmacias.dev/images/profile.jpg",
"sameAs": [
"https://linkedin.com/in/oriol-macias",
"https://github.com/oriolmacias",
"https://twitter.com/oriolmacias"
],
"jobTitle": "Full Stack Developer",
"worksFor": {
"@type": "Organization",
"name": "Company Name",
"url": "https://company.com"
},
"address": {
"@type": "PostalAddress",
"addressLocality": "Zurich",
"addressRegion": "ZH",
"addressCountry": "CH"
},
"knowsAbout": [
"Web Development",
"TypeScript",
"React",
"Astro",
"Node.js",
"Python"
],
"knowsLanguage": [
{
"@type": "Language",
"name": "English",
"alternateName": "en"
},
{
"@type": "Language",
"name": "Spanish",
"alternateName": "es"
},
{
"@type": "Language",
"name": "French",
"alternateName": "fr"
}
],
"email": "oriol@example.com",
"telephone": "+41-XX-XXX-XXXX"
}
```
**Astro Integration**:
```astro
---
// src/pages/index.astro
const personSchema = {
"@context": "https://schema.org",
"@type": "Person",
// ... complete schema
};
---
<head>
<script type="application/ld+json" set:html={JSON.stringify(personSchema)} />
</head>
```
---
### 2. JobPosting Schema (Experience)
**Use**: Work experience, job history
```json
{
"@context": "https://schema.org",
"@type": "JobPosting",
"title": "Senior Full Stack Developer",
"description": "Led development of modern web applications using React, TypeScript, and Node.js",
"datePosted": "2022-01-15",
"validThrough": "2024-12-31",
"hiringOrganization": {
"@type": "Organization",
"name": "Tech Company AG",
"sameAs": "https://techcompany.com",
"logo": "https://techcompany.com/logo.png"
},
"jobLocation": {
"@type": "Place",
"address": {
"@type": "PostalAddress",
"addressLocality": "Zurich",
"addressRegion": "ZH",
"addressCountry": "CH"
}
},
"employmentType": "FULL_TIME",
"baseSalary": {
"@type": "MonetaryAmount",
"currency": "CHF",
"value": {
"@type": "QuantitativeValue",
"value": 120000,
"unitText": "YEAR"
}
},
"skills": [
"React",
"TypeScript",
"Node.js",
"PostgreSQL",
"Docker"
],
"responsibilities": [
"Design and implement scalable web applications",
"Lead technical architecture decisions",
"Mentor junior developers"
]
}
```
**React Component**:
```tsx
// src/components/JobPostingSchema.tsx
import React from 'react';
import type { FC } from 'react';
interface Job {
title: string;
company: string;
companyUrl: string;
location: { city: string; country: string; canton?: string };
startDate: string;
endDate?: string;
description: string;
employmentType: 'FULL_TIME' | 'PART_TIME' | 'CONTRACTOR';
skills: string[];
}
interface JobPostingSchemaProps {
job: Job;
}
export const JobPostingSchema: FC<JobPostingSchemaProps> = ({ job }) => {
const schema = {
"@context": "https://schema.org",
"@type": "JobPosting",
"title": job.title,
"description": job.description,
"datePosted": job.startDate,
"validThrough": job.endDate || new Date().toISOString().split('T')[0],
"hiringOrganization": {
"@type": "Organization",
"name": job.company,
"sameAs": job.companyUrl
},
"jobLocation": {
"@type": "Place",
"address": {
"@type": "PostalAddress",
"addressLocality": job.location.city,
"addressRegion": job.location.canton,
"addressCountry": job.location.country
}
},
"employmentType": job.employmentType,
"skills": job.skills
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
};
```
---
### 3. CreativeWork Schema (Projects)
**Use**: Portfolio projects, side projects
```json
{
"@context": "https://schema.org",
"@type": "CreativeWork",
"name": "E-Commerce Platform",
"description": "Modern e-commerce platform built with React, Next.js, and Stripe",
"url": "https://project-url.com",
"image": "https://oriolmacias.dev/projects/ecommerce-screenshot.jpg",
"creator": {
"@type": "Person",
"name": "Oriol Macías",
"url": "https://oriolmacias.dev"
},
"dateCreated": "2024-01-01",
"datePublished": "2024-06-15",
"keywords": [
"React",
"Next.js",
"TypeScript",
"Stripe",
"E-Commerce"
],
"inLanguage": "en",
"about": {
"@type": "Thing",
"name": "Web Development"
},
"programmingLanguage": [
"TypeScript",
"JavaScript"
],
"runtimePlatform": "Node.js",
"version": "1.0.0"
}
```
**Astro Integration**:
```astro
---
// src/pages/projects/[slug].astro
const project = {
name: "Project Name",
description: "Project description",
// ... project data
};
const projectSchema = {
"@context": "https://schema.org",
"@type": "CreativeWork",
// ... complete schema
};
---
<head>
<script type="application/ld+json" set:html={JSON.stringify(projectSchema)} />
</head>
```
---
### 4. Organization Schema (Companies)
**Use**: Company profiles, employers
```json
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Tech Company AG",
"url": "https://techcompany.com",
"logo": "https://techcompany.com/logo.png",
"description": "Leading technology company in Switzerland",
"address": {
"@type": "PostalAddress",
"addressLocality": "Zurich",
"addressRegion": "ZH",
"postalCode": "8001",
"addressCountry": "CH"
},
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+41-XX-XXX-XXXX",
"contactType": "customer service",
"areaServed": "CH",
"availableLanguage": ["English", "German", "French"]
},
"sameAs": [
"https://linkedin.com/company/techcompany",
"https://twitter.com/techcompany"
]
}
```
---
## Swiss Market Optimization
### Address with Canton
```json
{
"address": {
"@type": "PostalAddress",
"addressLocality": "Zurich",
"addressRegion": "ZH", // ← Canton code (ZH, GE, BS, etc.)
"postalCode": "8001",
"addressCountry": "CH"
}
}
```
**Swiss Cantons**:
- ZH: Zurich
- GE: Geneva
- BS: Basel-Stadt
- BE: Bern
- VD: Vaud
### Multi-Language Support
```json
{
"inLanguage": ["en", "de", "fr", "it"],
"knowsLanguage": [
{ "@type": "Language", "name": "German", "alternateName": "de" },
{ "@type": "Language", "name": "French", "alternateName": "fr" },
{ "@type": "Language", "name": "English", "alternateName": "en" }
]
}
```
### Currency
```json
{
"baseSalary": {
"@type": "MonetaryAmount",
"currency": "CHF", // ← Swiss Franc
"value": {
"@type": "QuantitativeValue",
"value": 120000,
"unitText": "YEAR"
}
}
}
```
---
## Spanish Market Optimization
### Spain-Specific Address
```json
{
"address": {
"@type": "PostalAddress",
"addressLocality": "Barcelona",
"addressRegion": "Catalonia",
"postalCode": "08001",
"addressCountry": "ES" // ← Spain
}
}
```
### Language
```json
{
"inLanguage": "es",
"knowsLanguage": [
{ "@type": "Language", "name": "Spanish", "alternateName": "es" },
{ "@type": "Language", "name": "Catalan", "alternateName": "ca" }
]
}
```
### Currency
```json
{
"baseSalary": {
"@type": "MonetaryAmount",
"currency": "EUR", // ← Euro
"value": {
"@type": "QuantitativeValue",
"value": 50000,
"unitText": "YEAR"
}
}
}
```
---
## Generation Workflow
### Step 1: Identify Schema Type
**Questions**:
- Homepage/About? → **Person**
- Work experience? → **JobPosting**
- Project portfolio? → **CreativeWork**
- Company profile? → **Organization**
### Step 2: Collect Required Fields
**Person Schema**:
- name, url, image, jobTitle, address, skills, languages
**JobPosting Schema**:
- title, company, location, startDate, employmentType, skills
**CreativeWork Schema**:
- name, description, url, image, dateCreated, keywords
### Step 3: Generate JSON-LD
**Use template from above**, fill with actual data
### Step 4: Validate
**Tools**:
- [Google Rich Results Test](https://search.google.com/test/rich-results)
- [Schema Markup Validator](https://validator.schema.org/)
- Internal: `npm run check:structured-data`
### Step 5: Integrate in Astro
```astro
<head>
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
</head>
```
---
## Validation Script
**File**: `scripts/validate-structured-data.js`
```javascript
import fs from 'fs';
import { glob } from 'glob';
async function validateStructuredData() {
const files = await glob('src/pages/**/*.astro');
const errors = [];
for (const file of files) {
const content = fs.readFileSync(file, 'utf-8');
// Check for JSON-LD
const jsonLdMatch = content.match(/<script type="application\/ld\+json".*?>(.*?)<\/script>/gs);
if (jsonLdMatch) {
jsonLdMatch.forEach((match, index) => {
try {
// Extract JSON content
const jsonContent = match.match(/set:html={JSON\.stringify\((.*?)\)}/)?.[1];
if (!jsonContent) {
const directJson = match.match(/<script.*?>(.*?)<\/script>/s)?.[1];
if (directJson) {
JSON.parse(directJson);
}
}
} catch (e) {
errors.push(`${file}: Invalid JSON-LD at position ${index + 1}`);
}
});
}
}
if (errors.length > 0) {
console.error('❌ Structured Data Validation Errors:');
errors.forEach(err => console.error(` ${err}`));
process.exit(1);
} else {
console.log('✅ All structured data valid');
}
}
validateStructuredData();
```
**Run**:
```bash
npm run check:structured-data
```
---
## Best Practices
### ✅ DO
1. **Include all required fields**:
```json
{
"@context": "https://schema.org", // ← Required
"@type": "Person", // ← Required
"name": "Oriol Macías" // ← Required
}
```
2. **Use Swiss Canton codes**:
```json
{ "addressRegion": "ZH" } // Not "Zurich"
```
3. **Validate before deploying**:
```bash
npm run check:structured-data
```
4. **Use locale-specific data**:
```astro
---
const locale = Astro.currentLocale || 'en';
const jobTitle = locale === 'es' ? 'Desarrollador Full Stack' : 'Full Stack Developer';
---
```
### ❌ DON'T
1. **Don't skip @context**:
```json
// ❌ Bad
{ "@type": "Person", "name": "..." }
// ✅ Good
{ "@context": "https://schema.org", "@type": "Person", "name": "..." }
```
2. **Don't use invalid JSON**:
```json
// ❌ Bad (trailing comma)
{ "name": "...", }
// ✅ Good
{ "name": "..." }
```
3. **Don't forget validation**:
Always test with Google Rich Results Test
---
## Quick Reference
| Schema Type | Use Case | Required Fields |
|-------------|----------|-----------------|
| **Person** | Homepage, About | @context, @type, name |
| **JobPosting** | Work experience | title, hiringOrganization, jobLocation |
| **CreativeWork** | Projects | name, creator |
| **Organization** | Companies | name, url |
**Swiss Optimization**: Canton code (ZH), CHF currency, multi-language
**Spanish Optimization**: ES country code, EUR currency, Spanish language