Technical SEO Audit WorkshopRunning the crawl and gathering evidence · Lesson 6 of 14

Rendering and structured data checks: raw vs rendered, markup vs visible

Article · 14 min · 8 min lecture

Video lecture

Rendering and structured data checks: raw vs rendered, markup vs visible

13 chapters · about 8 min · full transcript

Coming soon

Chapter 1 of 13

Rendering and structured data checks

  • Raw vs rendered parity
  • Markup vs visible facts
  • Eligibility and reports

The narrated lecture is in production

Every chapter is scripted and ready. Browse the chapters and read the full transcript now — the video will appear here when it’s published.

Chapters

Why this is its own step

Kiran Home runs a headless React storefront with server-side rendering. SSR reduces risk, but it doesn't eliminate it: components can still fetch content on the client, hydration can change canonicals, and JSON-LD can drift from the visible price. This lesson checks the three templates that matter most (product, category, journal) for raw vs rendered parity and structured data accuracy.

Check 1: raw vs rendered parity per template

Use the rendered and raw crawls from Module 1 (or the Playwright diff script from Technical SEO Mastery, Lesson 1.2) on 20 URLs per template. Compare:

ElementProductCategoryJournal
Canonical identical raw vs renderedYesYesNo — rendered canonical switches market via cookie
Robots meta identicalYesYesYes
Main content in raw HTMLYesOnly first 24 productsYes
Internal links in raw HTMLBreadcrumbs yes; "Customers also bought" noPagination noYes
JSON-LD in raw HTMLYesn/aYes

Illustrative results. Two new findings: journal canonicals change after hydration (feeds RC1), and "Customers also bought" links exist only after interaction (discovery value lost).

Check 2: JSON-LD vs visible price and availability

# pip install requests beautifulsoup4
import json, re, requests
from bs4 import BeautifulSoup

def check(url):
    s = BeautifulSoup(requests.get(url, timeout=20).text, "html.parser")
    visible = s.select_one(".product-price__amount")
    visible = re.sub(r"[^\d.]", "", visible.get_text()) if visible else None
    offers = []
    for tag in s.find_all("script", type="application/ld+json"):
        try:
            data = json.loads(tag.string or "{}")
        except json.JSONDecodeError:
            return url, "INVALID JSON-LD", None, None
        items = data.get("@graph", [data]) if isinstance(data, dict) else data
        for it in items:
            if isinstance(it, dict) and it.get("@type") == "Product":
                o = it.get("offers") or {}
                o = o[0] if isinstance(o, list) else o
                offers.append((str(o.get("price")), o.get("priceCurrency"), o.get("availability")))
    if not offers:
        return url, "NO PRODUCT MARKUP", visible, None
    price, cur, avail = offers[0]
    status = "OK" if visible and price and abs(float(price) - float(visible)) < 0.01 else "MISMATCH"
    return url, status, visible, (price, cur, avail)

for u in open("product_sample.txt").read().split():
    print(check(u))

For Kiran Home (illustrative), 18 of 60 sampled products have offers missing entirely — the template omits it when a product has variants — and 5 show a sale price visibly while JSON-LD still has the old price. That's RC6 with hard numbers.

Check 3: eligibility and reports

  • Run one URL per template through the Rich Results Test (rendered view) and note errors vs warnings.
  • Check Search Console's Product snippets / Merchant listings and Breadcrumbs reports for trend changes since the relaunch.
  • Don't recommend markup for retired features (for example FAQ rich results ended in May 2026) — note existing FAQPage markup as harmless.

Check 4: market parity for international templates

Because Kiran Home serves four markets, repeat checks 1 and 2 for one product in each market folder. Confirm for each market version: the canonical is its own URL, the hreflang set lists all four markets plus x-default, the visible currency matches the market (PKR, AED or GBP), and the JSON-LD priceCurrency matches the visible currency. On the relaunched storefront (illustrative), the /ar-ae/ template renders prices in AED but its JSON-LD says GBP — inherited from the UK template — which becomes another RC6 example.

Summarising the checks

CheckResult (illustrative)Root causeTicket
Journal canonical changes after hydration20/20 sampledRC1Canonical from URL path on server
"Customers also bought" only after interactionAll product templatesRC4Server-render related links as anchors
Offers missing on variant products18/60RC6Always output offers (lowest price / AggregateOffer)
Stale sale prices in JSON-LD5/60RC6Generate from live price field
/ar-ae/ JSON-LD currency GBPAll /ar-ae/ productsRC6Currency from market config

Worked example 2: an Abu Dhabi clinic's schema plugin

A clinic group (illustrative) has three plugins each outputting MedicalClinic/LocalBusiness markup with different opening hours. Rich Results Test shows no errors — all three blocks are valid — but the facts conflict. The finding isn't "invalid markup", it's "conflicting facts": keep one source, generated from the same fields as the visible opening hours, and align Business Profile hours.

Common mistakes

  • Treating "valid in the Rich Results Test" as "correct".
  • Checking one URL per template when the bug only appears on variants or sale items.
  • Recommending markup for rich result types that no longer exist.

Key takeaways

  • SSR reduces but doesn't eliminate rendering risk; compare raw and rendered HTML per template.
  • Content or links that appear only after interaction provide no discovery value.
  • Verify JSON-LD against visible price and availability on samples that include variants and sale items.
  • Valid markup isn't necessarily correct; conflicting valid blocks are a finding.
  • Don't recommend markup for retired rich results such as FAQ (ended May 2026).

Check your understanding

Quick questions to lock in the lesson. They don’t count towards your certificate.

  1. The Rich Results Test shows no errors, but JSON-LD price differs from the visible price. What's the finding?
  2. A journal page's canonical changes after hydration because of a locale cookie. Why is this serious?
  3. Which sample is best for checking structured data accuracy?

Put it into practice

Run the JSON-LD price check on 20 product or service pages, including at least five variants or sale items, and record mismatches with counts and examples.

Enrol for free to save your progress

Reading is always free. Enrol to keep your place, take the final assessment and earn a verifiable certificate.