All postsTech

The 40 Lines of JSON-LD I Paste Into Every Client Site. Copy Them.

Google your own business name right now. If a competitor with an uglier site gets sitelinks and a clean knowledge panel while you get one plain blue link,...

Garvit Sharma

Garvit Sharma

13 June 2026 · 6 MIN READ

Tech

The 40 Lines of JSON-LD I Paste Into Every Client Site. Copy Them.

webight.com
On this page

Google your own business name right now. If a competitor with an uglier site gets sitelinks and a clean knowledge panel while you get one plain blue link, this post is for you.

Structured data decides most of that gap. Your site describes your business in paragraphs, and Google has to guess what those paragraphs mean. JSON-LD removes the guessing. You hand Google a small block of facts in its own format: this is the business, this is where it operates, these are its profiles, this is what it sells.

Here is the one idea this whole post hangs on: schema is a set of claims, and Google cross-checks every claim against what is visible on your pages. When the claims match, you build machine trust. When they conflict, you burn it. That single rule decides everything else, including the two warnings at the end.

I paste roughly the same block into every client build now. The title says 40 lines. The version below is 55 because I expanded the offer catalog so you can see the pattern. Trim what you do not need and you land right around 40.

The block

This goes in a <script type="application/ld+json"> tag in your site-wide layout. In Next.js, that is app/layout.tsx, rendered once on every page.

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": ["Organization", "ProfessionalService"],
      "@id": "https://www.webight.com/#business",
      "name": "Webight",
      "url": "https://www.webight.com",
      "logo": "https://www.webight.com/logo.png",
      "description": "Two-person design and development studio building fast websites and AI automations.",
      "email": "hello@webight.com",
      "founder": { "@type": "Person", "name": "Garvit Sharma" },
      "address": {
        "@type": "PostalAddress",
        "addressLocality": "Noida",
        "addressRegion": "Uttar Pradesh",
        "addressCountry": "IN"
      },
      "areaServed": ["IN", "AE", "US", "SG", "NL"],
      "sameAs": [
        "https://medium.com/@sgarvitg",
        "https://www.linkedin.com/company/webight"
      ],
      "hasOfferCatalog": {
        "@type": "OfferCatalog",
        "name": "Webight services",
        "itemListElement": [
          {
            "@type": "Offer",
            "price": "7999",
            "priceCurrency": "INR",
            "itemOffered": { "@type": "Service", "name": "Landing page design and build" }
          },
          {
            "@type": "Offer",
            "price": "24999",
            "priceCurrency": "INR",
            "itemOffered": { "@type": "Service", "name": "Full website build" }
          },
          {
            "@type": "Offer",
            "itemOffered": { "@type": "Service", "name": "AI and automation builds" }
          }
        ]
      }
    },
    {
      "@type": "WebSite",
      "@id": "https://www.webight.com/#website",
      "url": "https://www.webight.com",
      "name": "Webight",
      "publisher": { "@id": "https://www.webight.com/#business" }
    }
  ]
}

Swap in your own name, domain, address, profiles, and prices. That's it.

What every property does, one line each

  • @context: tells parsers the vocabulary is schema.org, so name and address have agreed meanings.
  • @graph: lets you describe multiple connected entities in one script tag instead of scattering three blocks across the page.
  • @type: ["Organization", "ProfessionalService"]: one node that is both your company and a local service business, so you get organization signals and local signals from a single entity.
  • @id: a permanent label for this node, so other nodes and other pages can point at it instead of redefining your business everywhere.
  • name: the exact business name, spelled the way it appears in your header and footer.
  • url: your canonical homepage, with or without www, matching whichever version your server actually resolves to.
  • logo: an absolute URL to a real image file Google can fetch, and it has to return a 200.
  • description: one honest sentence about what you do, no adjectives doing heavy lifting.
  • email: the contact email, and it must be the same one printed on your site (more on this below, because I got it wrong).
  • founder: connects a real person to the business, which matters more every month as search engines build entity graphs.
  • address / PostalAddress: your physical location broken into fields, even if clients never visit, because LocalBusiness types expect it.
  • areaServed: where you actually take clients, and two-letter country codes are enough. Ours lists India, the Gulf, the US, Southeast Asia, and the Netherlands because those are real client regions, then Google stops assuming we only serve one pin code in Noida.
  • sameAs: your profiles on other platforms, which is how Google links your domain to your Medium and LinkedIn and decides they are all one entity.
  • hasOfferCatalog: a structured list of what you sell, with Offer wrapping each Service.
  • price and priceCurrency: only include these if the price is public on your site. Ours are on webight.com/pricing, so the schema repeats them.
  • WebSite node: tells Google your domain is one site with one publisher, which helps sitelinks and the site name shown in results.
  • publisher with @id: points the website back at the business node instead of duplicating all the fields. This is the whole point of @graph.

The bug that taught me the matching rule

See, I learned the cross-checking rule from my own site, the embarrassing way.

When I ran a full audit on webight.com in June, the schema block listed contact@webight.com. The contact page said hello@webight.com. One word. Same domain, same inbox owner, completely different string. Every crawler that compared the two saw a business whose structured claims disagreed with its visible pages.

Nothing crashed. No error showed up anywhere. That is what makes this class of bug nasty: the validator passed, because contact@webight.com is a perfectly valid email. A validator checks shape, and it has no way of knowing whether the string is true. Only a human comparing the schema against the rendered page catches it.

This is the NAP rule, and it extends past name, address, and phone. Every string in your schema that also appears on your site must match character for character. Email, business name, address spelling, the www in your URL. If your footer says "Webight Studio" and your schema says "Webight", pick one and fix the other. Consistency is the signal. An inconsistent entity reads like two different businesses sharing a domain, and Google resolves that doubt by trusting neither version fully.

So before you paste my block, open your own contact page in one tab and the schema in another. Compare every field. It takes 4 minutes and it is the highest-value step in this entire post.

The one property you should not copy

You will see other tutorials add this:

"aggregateRating": {
  "@type": "AggregateRating",
  "ratingValue": "4.9",
  "reviewCount": "87"
}

Do not add it unless those reviews exist on your site, visible on the page and attached to the thing being rated. Google Maps reviews do not count for this markup, and "we have happy clients" counts even less. The reviews have to live on the page itself.

Fabricated ratings are one of the few schema sins Google punishes with a manual action, and a manual action means a human reviewer flagged your site and your rich results disappear until you fix it and file a reconsideration request. I have read enough of those threads to know the recovery takes weeks. For a 4.9 that nobody could even see? Lazy trade.

Notice my 55 lines carry zero rating properties. The block claims exactly what webight.com can prove. That restraint is why the rest of it gets believed.

How to verify it works

  1. Paste your filled-in block at validator.schema.org. It catches malformed JSON and unknown properties.
  2. Run the same URL through Google's Rich Results Test. It tells you what Google will actually use, which is a smaller set than what schema.org allows.
  3. Deploy, then watch the structured data reports in Search Console over the next 2 weeks.

For example, in Next.js the cleanest mount looks like this:

// app/layout.tsx
const jsonLd = { /* the block above as a JS object */ };

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>

One block, sitewide, rendered on the server so crawlers see it without executing anything. After we shipped the corrected block on webight.com, branded impressions in Search Console climbed 23% over the following 5 weeks. Small site, small numbers, but the direction was immediate.

So here is the deal. Copy the block, fill in your real details, and then do the part nobody does: read your own contact page next to your own schema and check that every string matches. If you find a mismatch like my contact@ versus hello@, you just found a bug that has been quietly costing you trust for as long as it has existed. Fix it tonight, validate it, and tell me what you found. I reply to every message.

SEOJSON-LDstructured dataweb developmentsmall business
Next move

We build custom platforms, websites, and automation.

The two people who build it are the two you talk to, and every price is on the page.

Garvit Sharma

Garvit Sharma

Full-stack developer and co-founder of Webight, a two-person web and AI studio in India. He writes these from real client work. More about us.