Most developer portfolios have a title tag, maybe a meta description, and zero structured data. Google can read your page, but it doesn't understand your page. It doesn't know you're a service business, what you charge, where you operate, or what past clients said.
JSON-LD fixes that. It's a script block in your <head> that tells search engines exactly what your page is about, in a format they can parse.
Here are the 4 JSON-LD blocks every service portfolio should ship, with Next.js code you can copy.
1. ProfessionalService — who you are
This is the foundation. It tells Google you're a service business, not a blog or an e-commerce store.
// app/layout.tsx or a shared component
const businessSchema = {
'@context': 'https://schema.org',
'@type': 'ProfessionalService',
name: 'Your Name — Full-Stack Developer',
url: 'https://yoursite.com',
logo: 'https://yoursite.com/logo.png',
image: 'https://yoursite.com/og-image.jpg',
description:
'Full-stack development for startups. Next.js, React Native, Stripe integration.',
address: {
'@type': 'PostalAddress',
addressLocality: 'Bratislava',
addressCountry: 'SK',
},
geo: {
'@type': 'GeoCoordinates',
latitude: 48.1486,
longitude: 17.1077,
},
priceRange: '€€',
areaServed: {
'@type': 'GeoCircle',
geoMidpoint: {
'@type': 'GeoCoordinates',
latitude: 48.85,
longitude: 2.35,
},
geoRadius: '3000 km',
},
knowsLanguage: ['en', 'ru', 'uk'],
sameAs: [
'https://github.com/yourusername',
'https://linkedin.com/in/yourusername',
],
}
Why it matters: Google uses ProfessionalService to populate knowledge panels and local business results. Even if you work remotely, the areaServed field tells Google your market reach.
Common mistake: Using Organization instead of ProfessionalService. Organization is too generic — Google can't infer that you sell services.
2. Service — what you sell
One Service block per service you offer. Be specific — "MVP Development" converts better in search than "Web Development."
const serviceSchema = {
'@context': 'https://schema.org',
'@type': 'Service',
serviceType: 'MVP Development',
provider: {
'@type': 'ProfessionalService',
name: 'Your Name',
url: 'https://yoursite.com',
},
areaServed: 'Europe',
description:
'End-to-end MVP development for startups. From scoping to launch in 12 weeks.',
offers: {
'@type': 'Offer',
priceCurrency: 'EUR',
price: '8000',
priceSpecification: {
'@type': 'PriceSpecification',
minPrice: '8000',
maxPrice: '25000',
priceCurrency: 'EUR',
},
},
}
Why it matters: Service schema can trigger rich snippets with pricing info in search results. For bottom-of-funnel searches like "MVP developer Europe price," this is high-value real estate.
Tip: If you have multiple services (web app, mobile app, consulting), create separate Service blocks. Don't lump everything into one.
3. Review / AggregateRating — social proof
If you have testimonials on your site, mark them up. Google can display star ratings in search results for service businesses.
const reviewSchema = {
'@context': 'https://schema.org',
'@type': 'ProfessionalService',
name: 'Your Name',
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: '5',
reviewCount: '12',
bestRating: '5',
},
review: [
{
'@type': 'Review',
author: { '@type': 'Person', name: 'Client Name' },
datePublished: '2026-03-15',
reviewBody:
'Shipped our MVP in 10 weeks. Clean code, clear communication, no surprises.',
reviewRating: { '@type': 'Rating', ratingValue: '5', bestRating: '5' },
},
],
}
Important: Only mark up real reviews from real clients. Google penalizes fabricated reviews. If you have 3 genuine testimonials, mark up 3 — not 30.
Where to place it: On the page where the testimonials actually appear. Don't add review schema to pages that don't show reviews — Google considers that misleading.
4. Article — for blog posts
If you have a blog (and you should), each post should have Article schema.
const articleSchema = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
description: post.description,
datePublished: post.date,
dateModified: post.date,
author: {
'@type': 'Person',
name: 'Your Name',
url: 'https://yoursite.com',
},
publisher: {
'@type': 'ProfessionalService',
name: 'Your Name',
logo: { '@type': 'ImageObject', url: 'https://yoursite.com/logo.png' },
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://yoursite.com/blog/${post.slug}`,
},
}
Why it matters: Article schema helps Google understand content freshness (via dateModified), authorship, and topic. It can trigger rich results with publish dates and author info in search.
How to add JSON-LD in Next.js
In App Router, add a <script> tag in your layout or page:
export default function Layout({ children }) {
return (
<html>
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(businessSchema),
}}
/>
</head>
<body>{children}</body>
</html>
)
}
For page-specific schema (articles, services), add the script block in the page component, not the layout:
export default function BlogPost({ post }) {
const articleSchema = {
/* ... */
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(articleSchema),
}}
/>
<article>{/* post content */}</article>
</>
)
}
Validation
After adding JSON-LD, validate it:
- Google's Rich Results Test — tells you if your schema is eligible for rich results
- Schema.org Validator — checks structural correctness
- Google Search Console — after indexing, check the "Enhancements" section for schema errors
Run validation after every schema change. A missing comma in JSON-LD silently breaks the entire block — no runtime error, just no structured data.
What not to bother with
- BreadcrumbList — useful for e-commerce, overkill for a portfolio with 5 pages.
- FAQ schema — Google has been de-prioritizing FAQ rich results. Don't add it unless your page is genuinely an FAQ.
- HowTo schema — for recipe sites and tutorials, not service businesses.
Four blocks: ProfessionalService, Service, Review, Article. That's the schema.org investment that pays off for a developer portfolio. Everything else is diminishing returns.
Want structured data set up properly on your site? Let's talk — I ship technical SEO by default on every client project.