# SERP Site Name & Schema Fix Design

**Date:** 2026-06-03  
**Branch:** `claude-amsoil-index-troubleshooting`  
**Status:** Approved for implementation

---

## Problem

Dealer subdomain pages (`*.myamsoil.com`, `*.shopamsoil.com`, `*.myamsoil.ca`, `*.shopamsoil.ca`) show **"Aimclear"** as the Google SERP site name instead of the dealer's business name.

**Root cause (confirmed via live curl + Google's site-name docs):**  
The four apex domains serve `public/index.html` unconditionally regardless of host. That file hardcodes all head references - `canonical`, `og:url`, `WebSite.@id`, `Organization.name` - to `amsoil.aimclear.com`. Google's knowledge graph links the apex domains to the AIMCLEAR entity, then falls back to that identity for subdomains whose own WebSite signals it isn't confident about.

Secondary mechanism: the dealer page canonical has no trailing slash (`https://autolife.myamsoil.com`) while the JSON-LD `WebSite.url` and `WebPage.url` emit a trailing slash (`https://autolife.myamsoil.com/`). This inconsistency reduces Google's confidence in the canonical home page URL.

Additional issue: dealer pages emit 8 `Product` nodes for AMSOIL product categories (Motor Oils, Transmission Fluids, etc.) with `Offer` nodes that have no price. These generate 8,000+ invalid rich result items across the dealer network, can never pass validation (AMSOIL MAP policy prohibits dealers from listing product prices), and the product `@id`s point to `amsoil.com` - not the dealer page. This is structurally unfixable and approaches the "thin affiliation" structured data pattern Google's spam policies flag.

---

## Scope

### Part A - Apex domain identity fix (new code)

**Files:** `app/api/landing/route.ts` (new), `proxy.ts` (3-line addition)

`proxy.ts` already handles `pathname === '/'`. Add a check before the final `nextWithoutDealerHeader` call:

```ts
const AMSOIL_APEX = new Set(['myamsoil.com', 'shopamsoil.com', 'myamsoil.ca', 'shopamsoil.ca']);
if (AMSOIL_APEX.has(hostname.split(':')[0])) {
  return NextResponse.rewrite(new URL('/api/landing', request.url));
}
```

`app/api/landing/route.ts` reads `public/index.html` once at module startup (cached), then for each request runs four string replacements:

1. All `https://amsoil.aimclear.com/` → `https://{host}/` - fixes canonical, og:url, twitter:url, all JSON-LD @id/url/logo in one pass
2. `"name": "AIMCLEAR"` → `"name": "AMSOIL Dealer Landing Pages"` - removes AIMCLEAR from entity graph on AMSOIL domains
3. `"url": "https://aimclear.com/"` → `"url": "https://{host}/"` - Organization's own URL
4. `"description": "Premier marketing and website development agency partnering with AMSOIL"` → `"description": "Platform for AMSOIL dealers to create professional landing pages"`

Result: `myamsoil.com/` emits `canonical = https://myamsoil.com/`, `WebSite.@id = https://myamsoil.com/#website`, `Organization.name = "AMSOIL Dealer Landing Pages"`.

`amsoil.aimclear.com` falls through to the existing rewrite (`/ → /index.html`) unchanged - correct, that's the platform provider's own page.

`next.config.mjs` - no change needed. Proxy rewrite intercepts apex domains before the config rewrite runs.

### Part B - Dealer canonical trailing slash (done: PR #899)

`lib/seo-utils.ts:getCanonicalUrl` - when `pagePath === ''`, return `${base}/`. Aligns canonical tag with JSON-LD `url` fields.

### Part C - Dealer page JSON-LD cleanup + enrichment

**File:** `components/DealerTemplate.tsx`, function `generateDealerSchema`

#### Remove (spam/invalid schema cleanup)

Remove all 8 `Product` nodes from the `@graph`. These describe AMSOIL product categories with `Offer` nodes that have no price (MAP policy prohibits dealer pricing), with product `@id`s pointing to `amsoil.com` - not the dealer's domain. They generate 8,000+ invalid rich result errors across the network and cannot be fixed.

Remove `makesOffer` from the `AutoPartsStore` node - the referenced Offer `@id`s only existed inside the removed Product nodes.

#### Add (differentiation signals)

All additions are conditional on data being present and use data already in the DB.

**`image`** - `${pageUrl}/og` - the per-dealer OG image already generated at publish time. Makes the AutoPartsStore visually identifiable. Fixes the Merchant Listings critical error on the apex Products as a bonus.

**`logo`** - `dealer.logoUrl` when present. Each dealer with a custom logo has a unique visual identity in the schema.

**`hasMap`** - Google Maps search URL for the dealer's address, gated on `!dealer.hideAddress`. The Maps link is already rendered in the header and footer; schema simply declares it formally. Omit entirely when `hideAddress` is true.

**`sameAs`** - Array of social profile URLs from `dealer.socialLinks` (facebook, instagram, youtube, pinterest, linkedin, x/twitter), filtered to non-empty values. Off-page presence references are one of the strongest anti-thin-content signals. Only emit when at least one social link exists.

**`priceRange`** - `"$-$$$"` on the AutoPartsStore. This is a business-level pricing tier indicator (like a restaurant's "$" vs "$$$$"), not a product price - it does not conflict with AMSOIL's MAP policy.

#### AutoPartsStore node after changes

```json
{
  "@type": "AutoPartsStore",
  "@id": "https://dealer.myamsoil.com/#localbusiness",
  "name": "Dealer Name, Authorized AMSOIL Dealer",
  "description": "...",
  "url": "https://dealer.myamsoil.com/",
  "telephone": "...",
  "image": "https://dealer.myamsoil.com/og",
  "logo": "https://cdn.example.com/dealer-logo.png",  // when present
  "priceRange": "$-$$$",
  "hasMap": "https://www.google.com/maps/search/?api=1&query=...",  // when !hideAddress
  "sameAs": ["https://facebook.com/...", "..."],  // when social links exist
  "brand": { "@type": "Brand", "@id": "https://www.amsoil.com/#brand" },
  "address": { "@type": "PostalAddress", ... }
}
```

---

## What's deferred (not in this PR)

**`geo` coordinates** - Requires a Prisma migration (`lat Float?`, `lng Float?`), geocoding API integration, and a dealer backfill. Must respect `hideAddress`. Estimated 1 day of work; clean boundary for a follow-up PR.

**Content-completeness `noindex` gate** - Thin dealer pages (no description, no address, no contact) should be `noindex` to avoid scaled-content abuse flags. Deferred to follow-up - needs product decision on what constitutes "enough" content.

**OG image size optimization** - Current OG images are ~831 KB, over WhatsApp's 600 KB limit. Separate optimization task; doesn't block this PR.

**`openingHoursSpecification`** - DLP doesn't collect business hours. Future feature.

**`aggregateRating`** - Requires review collection. Future feature.

---

## Files changed summary

| File                            | Change                                                                      |
| ------------------------------- | --------------------------------------------------------------------------- |
| `app/api/landing/route.ts`      | New - serves host-aware `index.html` for apex domains                       |
| `proxy.ts`                      | +3 lines - rewrite apex root to `/api/landing`                              |
| `components/DealerTemplate.tsx` | Remove 8 Products + makesOffer; add image, logo, hasMap, sameAs, priceRange |
| `lib/seo-utils.ts`              | Done in PR #899                                                             |

---

## Spam policy compliance summary

| Risk                                                                  | Mitigation in this PR                                                     |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| Apex canonical points to different domain (AIMCLEAR entity pollution) | Part A fixes apex to self-referential canonical + AMSOIL-branded identity |
| Dealer page invalid Product schema (thin affiliation)                 | Part C removes the 8 Product nodes entirely                               |
| Dealer canonical/JSON-LD mismatch (low confidence)                    | Part B (PR #899) aligns trailing slash                                    |
| Thin dealer pages with no unique content                              | Deferred - requires product decision                                      |
