Schema.org in Astro: technical SEO that works
When I started building sites for dental practices, I realized the difference between ranking on page one or being invisible came down to structure. Google understands better when you speak its language.
Schema.org is like that. Instead of letting Google guess that a phone number is actually a phone number, you mark everything properly in the HTML. LocalBusiness, BreadcrumbList, FAQPage. Google gets it all.
Basic setup with LocalBusiness
If you build sites for clinics, medical offices or stores, LocalBusiness is mandatory. Here’s how:
---
export interface Props {
title: string;
address: string;
phone: string;
image: string;
}
const { title, address, phone, image } = Astro.props;
const schema = {
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": title,
"image": image,
"address": {
"@type": "PostalAddress",
"streetAddress": address.split(",")[0],
"addressLocality": "Rio de Janeiro",
"postalCode": "20000-000",
"addressCountry": "BR"
},
"telephone": phone,
"sameAs": "https://www.instagram.com/yourHandle"
};
---
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
I use this on every clinic website. Google starts understanding that this is a real place. With address, phone, everything correct.
BreadcrumbList for navigation
Breadcrumbs aren’t just UX, they’re hierarchy signals for Google. I put them on every blog page, case study, everything.
---
export interface Props {
items: Array<{ name: string; url: string }>;
}
const { items } = Astro.props;
const breadcrumbs = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": items.map((item, idx) => ({
"@type": "ListItem",
"position": idx + 1,
"name": item.name,
"item": `https://yourdomain.com${item.url}`
}))
};
---
<script type="application/ld+json" set:html={JSON.stringify(breadcrumbs)} />
If you’re at /blog/schema-org-in-astro, breadcrumb is: Home > Blog > Schema.org in Astro. Google understands the hierarchy.
FAQPage for common questions
FAQPage is gold. When you have FAQs properly structured, Google shows them right in search. Like those boxes that appear above the result.
---
export interface Props {
faqs: Array<{ question: string; answer: string }>;
}
const { faqs } = Astro.props;
const faqSchema = {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": faqs.map(item => ({
"@type": "Question",
"name": item.question,
"acceptedAnswer": {
"@type": "Answer",
"text": item.answer
}
}))
};
---
<script type="application/ld+json" set:html={JSON.stringify(faqSchema)} />
In my projects with Family Pilates, I added FAQs about scheduling and class types. Google started showing them directly.
Article for blog posts
A blog post deserves its own schema. Article marks who wrote it, when it was published, how long it takes to read.
---
export interface Props {
title: string;
description: string;
image: string;
author: string;
publishDate: string;
}
const { title, description, image, author, publishDate } = Astro.props;
const articleSchema = {
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": title,
"description": description,
"image": image,
"datePublished": publishDate,
"author": {
"@type": "Person",
"name": author
}
};
---
<script type="application/ld+json" set:html={JSON.stringify(articleSchema)} />
How to validate everything
Use Google’s validator directly: schema.org/validator. Paste your page’s HTML. If everything turns green, you’re good.
I made a typo in an address once, broke everything. Google complained. The validator saved me.
Why does it matter anyway?
Schema.org doesn’t change ranking position directly. It’s not like a backlink. But Google understands your page better, shows more information in search results, increases click-through rate.
I saw this on a project I built for TokFinal. After I got the schema right, impressions jumped because Google started showing business hours and details.
In Astro it’s easy because you can split things into reusable components. Make one <Schema /> component, pass props, use it everywhere. No code duplication.
- Audit your current site with schema.org/validator
- Create reusable schema components in Astro
- Implement LocalBusiness on homepage
- Add BreadcrumbList to all pages
- FAQ with 5-7 common questions
- Article schema on all blog posts
- Validate again, fix any errors
Schema.org done right is a silent advantage working for you every single day.
Real implementation: dental clinic
Built a real clinic project with Family Pilates where I put all of this to work. LocalBusiness with hours, BreadcrumbList on every content page, FAQPage with 7 common questions about scheduling.
Result in 2 months: Google started showing business hours right in search results, impressions jumped 60%, clicks increased because users saw more info before clicking.
Wasn’t magic. Was engineering. Proper Schema.org.
Rich snippets and voice search
Rich snippets are those extra boxes on Google. Hours, reviews, price. All from Schema.
With voice search growing, structured data matters more. Google uses Schema to answer user questions directly. “What are the hours?” Voice assistant finds it in Schema and responds.
If your site doesn’t have structured Schema, voice search can’t find it.
Testing beyond the validator
schema.org/validator is good, but not everything. Go to Search Console > Legacy tools > Rich results test. Google shows exactly how your Schema appears in results.
Saw a project where Schema was valid but Google rejected it for missing fields. Rich results test shows what Google actually sees.
Schema performance: watch bundle size
Schema code in your site adds a tiny bit to HTML size. Not critical, but watch for heavy scripts.
I put Schema in a reusable Astro component and keep it minified. Supabase queries don’t go in the HTML, they’re server-side rendered.
Once saw a dev put a Firebase dynamic query inside Schema. Bundle became absurd. Schema needs to be static or server-rendered.
Difference between microdata and JSON-LD
You can put Schema in data attributes or JSON-LD. JSON-LD is better because it doesn’t change semantic HTML. I recommend JSON-LD.
<!-- Not recommended -->
<div itemscope itemtype="https://schema.org/LocalBusiness">
<span itemprop="name">Clinic X</span>
</div>
<!-- Recommended -->
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
JSON-LD is cleaner and doesn’t pollute your HTML.
When Schema breaks rankings
Forgot to mark Schema correctly on a project and Google temporarily penalized it. Not punishment, Google just refused to index because it couldn’t understand the content.
Common error: Schema array without context. Or LocalBusiness without address. Google wants complete data.
If you’re seeing a ranking drop after adding Schema, validate everything in Search Console.
Integration with Supabase
With Supabase authentication, you can have dynamic data but static Schema at build-time.
My workflow:
- Query Supabase for dynamic data (hours, phone)
- Put in Astro component during build
- Schema renders with correct data
- User gets fast page, Google sees complete Schema
---
import { supabase } from '@lib/supabase';
const clinic = await supabase
.from('clinics')
.select('name, phone, address')
.eq('slug', Astro.params.slug)
.single();
const schema = {
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": clinic.name,
"telephone": clinic.phone,
"address": {
"@type": "PostalAddress",
"streetAddress": clinic.address
}
};
---
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
Dynamic but server-side. Google can read it at build.
- Audit your site with Rich results test in Search Console
- Implement LocalBusiness with real data
- Add 5+ structured FAQs
- Put BreadcrumbList on navigation
- Test with mobile-friendly test
- Set up rich results monitoring
- Document which Schema is used on each page
Proper Schema.org multiplies the value of all your SEO.