Dev.to WebDev 🛠 Dev 👁 0 📖 4 min read

Adding JSON‑LD to a Small Business Site: Organization, WebSite, BlogPosting, and BreadcrumbList

Hook: Why Search Engines Need More Than a Pretty Page If you run a local service—plumbing, landscaping, or a boutique—you already know that showing up in Google Maps and the local pack can be the difference between a f

Hook: Why Search Engines Need More Than a Pretty Page

If you run a local service—plumbing, landscaping, or a boutique—you already know that showing up in Google Maps and the local pack can be the difference between a full schedule and an empty one. Structured data (JSON‑LD) is the quiet hero that tells Google exactly who you are, what you offer, and how your content is organized. The result? Rich snippets that boost click‑through rates without spending a dime on ads.

The Four Schemas I Use on My Own Site

For my own business site I keep the implementation lean: four schema types cover everything I need.

  1. Organization – identifies the business, its contact info, and logo.
  2. WebSite – tells Google about the site itself and provides a search action.
  3. BlogPosting – marks up each blog article so it can appear as a rich result.
  4. BreadcrumbList – gives search engines a clear path hierarchy for every page.

Below is the exact JSON‑LD I drop into the <head> of each page. I keep a single file (structured-data.json) that I include via PHP, so the markup stays in sync across the whole site.

Step‑by‑Step Implementation

1. Create a Shared JSON‑LD File

I store the shared parts (Organization and WebSite) in structured-data.json. The file returns a JSON array that I later merge with page‑specific data.

<?php
header('Content-Type: application/ld+json');
$shared = [
    "@context" => "https://schema.org",
    "@graph" => [
        [
            "@type" => "Organization",
            "@id" => "https://dreamfuturestech.com#organization",
            "name" => "Dream Futures Tech",
            "url" => "https://dreamfuturestech.com",
            "logo" => "https://dreamfuturestech.com/assets/logo.png",
            "contactPoint" => [
                "@type" => "ContactPoint",
                "telephone" => "+1-954-555-0198",
                "contactType" => "customer service",
                "areaServed" => "US",
                "availableLanguage" => ["en"]
            ]
        ],
        [
            "@type" => "WebSite",
            "@id" => "https://dreamfuturestech.com#website",
            "url" => "https://dreamfuturestech.com",
            "name" => "Dream Futures Tech",
            "potentialAction" => [
                "@type" => "SearchAction",
                "target" => "https://dreamfuturestech.com/search?q={search_term_string}",
                "query-input" => "required name=search_term_string"
            ]
        ]
    ]
];
echo json_encode($shared, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
?>

2. Add Page‑Specific Schemas

For a blog post I append a BlogPosting node and a BreadcrumbList. I generate these in the same PHP template that renders the article.

<?php
// Load shared data
$shared = json_decode(file_get_contents('structured-data.json'), true);

// BlogPosting data – values come from the CMS
$blog = [
    "@type" => "BlogPosting",
    "@id" => "https://dreamfuturestech.com/blog/{$slug}#blogposting",
    "url" => "https://dreamfuturestech.com/blog/{$slug}",
    "name" => $title,
    "headline" => $title,
    "description" => $excerpt,
    "author" => [
        "@type" => "Person",
        "name" => "Kaleb"
    ],
    "datePublished" => $publishedDate,
    "dateModified" => $modifiedDate,
    "image" => $imageUrl,
    "publisher" => [
        "@type" => "Organization",
        "name" => "Dream Futures Tech",
        "logo" => [
            "@type" => "ImageObject",
            "url" => "https://dreamfuturestech.com/assets/logo.png"
        ]
    ]
];

// BreadcrumbList – reflects the site hierarchy
$breadcrumbs = [
    "@type" => "BreadcrumbList",
    "@id" => "https://dreamfuturestech.com/blog/{$slug}#breadcrumb",
    "itemListElement" => [
        [
            "@type" => "ListItem",
            "position" => 1,
            "name" => "Home",
            "item" => "https://dreamfuturestech.com"
        ],
        [
            "@type" => "ListItem",
            "position" => 2,
            "name" => "Blog",
            "item" => "https://dreamfuturestech.com/blog"
        ],
        [
            "@type" => "ListItem",
            "position" => 3,
            "name" => $title,
            "item" => "https://dreamfuturestech.com/blog/{$slug}"
        ]
    ]
];

// Merge everything into @graph
$shared['@graph'][] = $blog;
$shared['@graph'][] = $breadcrumbs;

// Output the final JSON‑LD block
echo "<script type=\"application/ld+json\">" . json_encode($shared, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "</script>";
?>

3. Deploy the Markup

Because the JSON‑LD lives in the page <head>, I add the PHP include to my base layout file (header.php).

<?php include 'structured-data.json'; ?>

Whenever a new article is published, the template automatically injects the correct BlogPosting and BreadcrumbList nodes. No manual copy‑paste.

4. Validate and Monitor

  • Rich Results Test – Paste the URL, confirm each schema appears without errors.
  • Search Console → Enhancements – Look for “Breadcrumb” and “Article” entries; fix warnings promptly.
  • Automated CI Check – I added a tiny Bash step to my deployment pipeline that runs curl against the live page and greps for "@type":"BlogPosting". If the string disappears, the build fails, preventing a silent markup loss.
#!/usr/bin/env bash
URL="https://dreamfuturestech.com/blog/$SLUG"
if ! curl -s $URL | grep -q '"@type":"BlogPosting"'; then
  echo " Structured data missing on $URL"
  exit 1
fi

Why This Minimal Setup Works

  • Performance – JSON‑LD is a tiny script block; it does not block rendering.
  • Maintainability – All shared data lives in one file; page‑specific data is generated programmatically.
  • Scalability – Adding a new schema (e.g., FAQPage) is just another array push onto @graph.
  • Local Relevance – The Organization node includes a contactPoint with a US phone number, which Google surfaces in the local pack.

Common Pitfalls and How I Avoid Them

Pitfall What I Do
Duplicate @id values Use a deterministic pattern (#organization, #website, #blogposting) based on the URL.
Missing required fields Follow the official schema.org docs; the Rich Results Test flags omissions before they go live.
Hard‑coded URLs Build URLs from a central config (BASE_URL) so a domain change updates everywhere.
Forgetting to update dateModified Pull the timestamp from the CMS’s last‑edited field; the script reads it automatically.

Next Steps for Your Site

  1. Copy the shared file into your project and adjust the organization details.
  2. Add the include to your layout so every page gets the base markup.
  3. Create a template for blog posts that builds the BlogPosting and BreadcrumbList arrays.
  4. Run the Rich Results Test on a few URLs; fix any warnings.
  5. Add a CI check (the Bash snippet above) to keep the markup alive.

By treating structured data as code—version‑controlled, generated, and tested—you get the SEO benefit of rich snippets without the maintenance headache.

I build sites and automations for small businesses at dreamfuturestech.com.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.