Service path
Technical SEO and site audits
See how audits, implementation priorities, schema, and crawl cleanup are sequenced inside the service stack.
Review the serviceThe exact JSON-LD structure, automation approach, and validation process we use to implement schema markup across hundreds of law firm pages without errors.
Reading path
Use technical articles as decision support for crawl cleanup, speed work, schema, and internal linking, then connect them back to the service and audit layer.
Most law firm websites have no schema markup at all. The ones that do usually have a single Organization block on the homepage, copy-pasted from a WordPress plugin, and nothing else.
That is a missed opportunity. Google uses structured data to understand what a page is about, who wrote it, what services are offered, what questions are answered, and how the business relates to specific locations. When your schema is complete and accurate, you become eligible for the rich results that still apply to your page type, and you make the site much easier for search engines to interpret consistently.
We recently implemented full JSON-LD schema markup across more than 500 law firm pages, including practice area pages, long-form guides, blog articles, tool pages, and location-relevant service pages. Here is exactly how we did it, what mistakes we made along the way, and what the structure looks like.
If you have 5 pages, you can write JSON-LD by hand. Copy the template, change the title, change the URL, change the description. It takes 10 minutes per page.
At 500+ pages, that approach falls apart. You make typos. You forget to update the breadcrumb on one page. You reference an @id that does not exist. You use datePublished: "2026-03-15" instead of the full ISO 8601 format with timezone. Google’s Rich Results Test passes, but the data is inconsistent and some of it is quietly ignored.
The real cost is maintenance. When you change your business name, phone number, or add a new practice area, you have to find and update every single schema block. Miss one and you have conflicting structured data across your site.
Every page on the site gets a JSON-LD @graph array. The reusable page-level entities live as separate siblings linked by @id. Small inline objects are still fine when nothing else needs to reference them, but WebPage, Service, BlogPosting, BreadcrumbList, Organization, and FAQPage should usually stand on their own. This is the part most implementations get wrong.
Here is what a typical practice area page graph looks like:
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebPage",
"@id": "https://lawfirmseo.pro/industries/seo-for-personal-injury-lawyers/#webpage",
"name": "SEO for Personal Injury Lawyers",
"url": "https://lawfirmseo.pro/industries/seo-for-personal-injury-lawyers/",
"about": { "@id": "https://lawfirmseo.pro/industries/seo-for-personal-injury-lawyers/#service" },
"breadcrumb": { "@id": "https://lawfirmseo.pro/industries/seo-for-personal-injury-lawyers/#breadcrumb" },
"isPartOf": { "@id": "https://lawfirmseo.pro/#website" }
},
{
"@type": "Service",
"@id": "https://lawfirmseo.pro/industries/seo-for-personal-injury-lawyers/#service",
"name": "SEO for Personal Injury Lawyers",
"provider": { "@id": "https://lawfirmseo.pro/#organization" }
},
{
"@type": "BreadcrumbList",
"@id": "https://lawfirmseo.pro/industries/seo-for-personal-injury-lawyers/#breadcrumb",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "https://lawfirmseo.pro/" },
{ "@type": "ListItem", "position": 2, "name": "SEO for Personal Injury Lawyers" }
]
},
{
"@type": "Organization",
"@id": "https://lawfirmseo.pro/#organization",
"name": "LawFirmSEO.pro",
"url": "https://lawfirmseo.pro/"
}
]
}
Notice what is happening here:
WebPage references Service and BreadcrumbList by @id only — they are not embedded inside WebPageService references Organization by @id onlyitem property — this is correct per Google’s spec for the current pageitem, not an object@idFor blog articles, the pattern shifts slightly. BlogPosting replaces Service as the about entity, and we add author, publisher, datePublished, and dateModified:
{
"@type": "BlogPosting",
"@id": "https://lawfirmseo.pro/resources/core-web-vitals-law-firm-websites/#article",
"headline": "Core Web Vitals for Law Firms: What Actually Matters",
"author": {
"@type": "Organization",
"@id": "https://lawfirmseo.pro/#organization",
"name": "LawFirmSEO.pro",
"url": "https://lawfirmseo.pro/"
},
"publisher": { "@id": "https://lawfirmseo.pro/#organization" },
"datePublished": "2026-03-15T00:00:00+00:00",
"dateModified": "2026-03-15T00:00:00+00:00",
"mainEntityOfPage": { "@id": "https://lawfirmseo.pro/resources/core-web-vitals-law-firm-websites/#webpage" }
}
Dates use full ISO 8601 with timezone offset. Not "2026-03-15". Google accepts the short format but the full format is unambiguous and avoids timezone interpretation issues.
Any page with an FAQ section gets a FAQPage entity as a separate sibling in the @graph array. Not nested inside WebPage. Not nested inside BlogPosting.
{
"@type": "FAQPage",
"@id": "https://lawfirmseo.pro/industries/seo-for-personal-injury-lawyers/#faq",
"mainEntity": [
{
"@type": "Question",
"name": "How long does SEO take for personal injury lawyers?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Most personal injury firms start seeing measurable improvements in organic traffic within 3-6 months..."
}
}
]
}
As of March 2026, Google limits FAQ rich results to well-known government and health websites. Law firm sites should not expect FAQ dropdowns in the search results. The markup is still worth implementing because it helps Google understand the Q&A content on the page and makes that content explicit in the graph.
Mistake 1: Nesting entities inside WebPage. Our first version had the Service entity embedded directly in the WebPage about field as a full object. Google’s testing tool accepted it. But the @id references broke because nested entities do not always resolve correctly when other parts of the graph try to reference them. Moving everything to top-level siblings with @id references fixed it.
Mistake 2: Using bare dates. We started with "datePublished": "2026-03-15" because it is cleaner to read. Google’s Rich Results Test accepted it without warnings. But the structured data documentation specifies ISO 8601 with timezone, and we found inconsistencies in how Google interpreted the dates across different time zones. Switching to "2026-03-15T00:00:00+00:00" eliminated the ambiguity.
Mistake 3: Putting item on the last breadcrumb. The last item in a BreadcrumbList represents the current page. Google’s spec says it should not have an item property — the name alone is sufficient. We had item on every breadcrumb entry, including the last one. The Rich Results Test did not flag it, but Google’s documentation is clear. We removed it.
Mistake 4: Using object format for breadcrumb items. Early versions used "item": { "@type": "WebPage", "@id": "https://..." } for breadcrumb items. The simpler format is a plain string: "item": "https://lawfirmseo.pro/". Both work, but the string format is what Google’s own examples use, and it reduces the overall size of the JSON-LD block.
The site is built on Astro, which means every page is a component that can accept props. We built a schema generation layer that takes page metadata (title, URL, type, dates, FAQs) and outputs the correct @graph array for that page type.
The key insight is that you do not need a different schema template for every page. You need a different template for every page type:
Each template is defined once. Page-specific data comes from frontmatter or props. When we add a new blog article, the schema is generated automatically from the article’s metadata. When we change the organization name, we change it in one place and every page updates.
This is not a plugin. It is about 200 lines of template logic. The output is a <script type="application/ld+json"> block injected into the <head> of every page at build time.
Google’s Rich Results Test works for one page at a time. That does not scale to 500+ pages.
We built a validation step into our build process. After the site builds, a script extracts every JSON-LD block from every HTML file and checks:
@id that is referenced in the graph actually exists as a defined entitydatePublished and dateModified use the full ISO 8601 formatitem propertyprovider and publisher are @id references, not full objectsFAQPage is a top-level graph sibling, not nestedThis catches mistakes before they go live. The validation runs in under a second for the entire site.
sameAs links to social profiles we do not actively maintain. Empty or outdated sameAs references are worse than none.If you are implementing schema across a law firm site with more than a handful of pages:
Schema markup is not glamorous work. But for a law firm competing in local search, it is the difference between Google understanding your pages and Google guessing about them. At 500+ pages, the compounding effect is significant.
Need a clearer next move?
We'll check every page on your law firm's site for structured data errors, missing entities, and optimization opportunities. Full report with corrected JSON-LD included.
Next steps
The strongest next move is usually a technical service review, a deeper implementation guide, or a tool that helps you validate the basics.
Service path
See how audits, implementation priorities, schema, and crawl cleanup are sequenced inside the service stack.
Review the serviceGuide path
Get the broader technical framework for law firm websites, from crawlability to page architecture.
Read the guideTool path
Use the audit as a practical starting point if you need to separate urgent issues from cosmetic ones.
Run the auditTechnical SEO
A practical guide to LCP, INP, and CLS for law firm websites. Learn what the metrics mean, what breaks them, and how to fix the right things first.
Read the articleTechnical SEO
A practical guide to fixing law firm website speed without wrecking design, tracking, or conversions. Learn what to fix first and what to ignore.
Read the articleTechnical SEO
Google indexes your mobile site first. Fix the mobile issues killing your law firm's rankings -- tap targets, font sizes, popups, and load times. Get a free audit!
Read the articleFrequently asked questions
Quick answers to the most common questions about this topic.
01
Google uses structured data to understand what a page is about, who wrote it, what services are offered, what questions are answered, and how the business relates to specific locations. When your schema is complete and accurate, you become eligible for the rich results that still apply to your page type, and you make the site much easier for search engines to interpret consistently. For law firms competing in local search, it is the difference between Google understanding your pages and Google guessing about them.
02
Every page should use a JSON-LD @graph array with reusable page-level entities as separate siblings linked by @id. A typical practice area page includes WebPage, Service, BreadcrumbList, Organization, and FAQPage as separate @graph siblings. Small inline objects are still fine when nothing else needs to reference them, but the major page-level entities should stand on their own. This structure makes relationships explicit and easier to validate at scale.
03
Not for the major reusable entities. WebPage, Service, BlogPosting, BreadcrumbList, Organization, and FAQPage should usually be top-level siblings in the @graph array, connected by @id references. Small inline objects can still be fine, but embedding full Service or BlogPosting entities inside WebPage makes large implementations harder to validate and maintain.
04
Use full ISO 8601 with timezone offset: 2026-03-15T00:00:00+00:00. Never use the bare date format like 2026-03-15. Google accepts the short format, but the full format is unambiguous and avoids timezone interpretation issues across different regions.
05
The last item in a BreadcrumbList represents the current page and should not have an item property. Only the name is needed. All other breadcrumb items should use a plain string URL for the item field, not an object. For example: position 1 has name Home with item as the homepage URL string, and the final position has only a name with no item property.
06
WordPress schema plugins generate generic markup that is usually wrong for legal services pages. The Organization block is typically fine, but everything else needs to be page-type-specific. Practice area pages need Service entities, blog articles need BlogPosting entities, and location pages need LocalBusiness entities. A plugin that outputs the same generic schema on every page misses the value of structured data entirely.
07
Google's Rich Results Test works for one page at a time and does not scale. Build a validation step into your build process that extracts every JSON-LD block from every HTML file and checks that every referenced @id exists as a defined entity, dates use full ISO 8601 format, the last breadcrumb has no item property, provider and publisher are @id references, and FAQPage is a top-level graph sibling. This catches mistakes before they go live.
08
No. Schema should be in the HTML source, not injected by JavaScript. Google says they process JavaScript-rendered structured data, but static HTML is faster and more reliable. Injecting schema through GTM adds a dependency on JavaScript execution and introduces a delay before Google can read the structured data. Put your JSON-LD directly in the page source.
Next step
Book a free strategy session. We'll audit your current structured data, identify what's missing, and show you how proper schema implementation can improve your search visibility.