Skip to content

AEO: structured data, agent surfaces, crawlable content, robots hardening - #148

Merged
ragojose merged 4 commits into
mainfrom
ragojose/shader-lab-aeo-improvements
Aug 31, 2026
Merged

AEO: structured data, agent surfaces, crawlable content, robots hardening#148
ragojose merged 4 commits into
mainfrom
ragojose/shader-lab-aeo-improvements

Conversation

@ragojose

Copy link
Copy Markdown
Member

Ports the basement.studio AEO system (website-2k25) to Shader Lab.

Fixes

  • Root-layout canonical landmine: alternates.canonical: "/tools/shader-lab" was inherited by every page that didn't override it (not-found, any future page). Removed — each page owns its canonical.
  • OG siteName / applicationName was the npm package name (@basementstudio/shader-lab); now "Shader Lab", with a richer default description.
  • Previews were fully indexable: non-production deployments (VERCEL_ENV !== "production") now serve robots.txt disallow-all, a noindex meta on every page, and an empty sitemap.

Structured data

One JSON-LD @graph per page via PageJsonLd, always inlining the Organization node anchored at https://basement.studio/#organization so both properties resolve to the same entity. Canonical facts live in src/lib/structured-data/product-facts.ts; effect names derive from LAYER_CATALOG so they can't drift.

  • Editor: WebSite + WebApplication (free offer, WebGPU requirement, feature list)
  • Scenes: CreativeWork with like/remix InteractionCounters, isBasedOn for remixes, effect keywords, author Person
  • Profiles: ProfilePage (only when publishedCount > 0, matching the noindex rule)
  • Community: CollectionPage + ItemList; breadcrumbs everywhere; FAQPage on the about page

Scene titles/descriptions are user-generated, so the JSON output escapes < to prevent </script> breakout.

Crawlable content

  • New /tools/shader-lab/about: what Shader Lab is, how layers work, the full effect catalog with preview images, both npm packages, and a 6-question FAQ. The editor page had zero crawlable text (full-viewport WebGPU app, no h1) — this page is the fix. Editor shell also gets an sr-only h1.
  • Linked from the privacy page (cross-linked back), sitemap, llms.txt, and the markdown mirrors.

Agent surfaces

  • /llms.txt + /agents.md as route handlers (absolute URLs derive from the runtime base URL; community links gated on deployment config)
  • Markdown mirrors: /index.md (product overview), /sitemap.md (content index), and per-scene .md twins — /community/<slug>.md rewrites through middleware, Accept: text/markdown negotiation on the HTML path, and Link rel="alternate" advertised on scene HTML responses
  • robots.ts: named allow groups for GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-User, Claude-SearchBot, PerplexityBot, Google-Extended; disallow /api/, /auth/, /monitoring
  • manifest.ts (PWA icons pending brand assets — favicon placeholder for now) and /.well-known/mcp.json discovery card pointing at @basementstudio/shader-lab-mcp

Everything degrades gracefully when community is disabled (no community URLs, no throws).

Verified

  • next build passes; lint clean (2 pre-existing warnings); tsc --noEmit clean
  • Curl-checked on a prod server: robots groups, llms/agents/index/sitemap .md content types + canonical Link headers, mcp.json, manifest, canonicals on every page type, JSON-LD graphs, .md rewrite + Accept negotiation + alternate header
  • VERCEL_ENV=preview build: disallow-all robots, noindex meta, empty sitemap

Review notes

  • Copy is drafted for review: APP_DESCRIPTION, editor/about descriptions, about-page prose, FAQ answers.
  • Needs 512px + 180px PNG icons to finish the manifest.
  • All URLs are APP_BASE_URL-relative; set NEXT_PUBLIC_BASE_URL in Vercel prod once the domain is final.
  • The about page isn't linked from the editor UI yet — open to suggestions on placement.

…ning

Port the basement.studio AEO system to Shader Lab:

- JSON-LD @graph per page (Organization, WebSite, WebApplication,
  CreativeWork scenes with like/remix InteractionCounters, ProfilePage,
  CollectionPage, FAQPage, BreadcrumbList) anchored on the shared
  basement.studio Organization @id, driven by a canonical
  product-facts.ts
- /tools/shader-lab/about: server-rendered prose page with the full
  effect catalog, package docs, and FAQ — the editor itself has no
  crawlable text (plus an sr-only h1 on the editor shell)
- /llms.txt and /agents.md route handlers (runtime base URL, community
  links gated on deployment config)
- Markdown mirrors: /index.md, /sitemap.md, and per-scene .md twins via
  middleware rewrite + Accept: text/markdown negotiation, with
  Link rel=alternate advertised on scene HTML responses
- robots.ts: per-AI-bot allow groups, disallow /api/ /auth/ /monitoring,
  and disallow-all + noindex meta + empty sitemap on non-production
  deployments (previews were fully indexable)
- Remove the root-layout canonical that mislabeled every page not
  overriding it; fix OG siteName (was the npm package name); richer
  default description
- manifest.ts (PWA icons pending brand assets) and a .well-known/mcp.json
  discovery card for @basementstudio/shader-lab-mcp
@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
shader-lab Ready Ready Preview Aug 31, 2026 2:20pm

@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds structured data, crawlable product content, Markdown and agent discovery surfaces, deployment-aware crawler controls, and page-specific metadata.

  • Adds JSON-LD graphs for the editor, community, scenes, profiles, breadcrumbs, and FAQ content.
  • Adds about, llms.txt, agents.md, Markdown mirrors, manifest, MCP discovery, sitemap, and robots surfaces.
  • Extends middleware with scene Markdown rewrites and content negotiation.

Confidence Score: 3/5

The PR should not merge until user-authored Markdown is escaped and non-Vercel development or staging deployments retain the promised indexing protections.

Published metadata can alter the new Markdown documents, while an unset VERCEL_ENV enables indexing across all three crawler-control surfaces; Accept negotiation also has a non-blocking standards edge case.

Files Needing Attention: src/app/api/md/scenes/[slug]/markdown.ts, src/app/sitemap.md/route.ts, src/lib/app.ts, src/middleware.ts

Prompt To Fix All With AI
### Issue 1
src/app/api/md/scenes/[slug]/markdown.ts:32-36
**Unescaped metadata alters Markdown**

When a published title, description, author name, or remix-source field contains Markdown syntax or newlines, this builder inserts it directly into headings, links, and body content, allowing malformed links or arbitrary agent-facing content in the scene mirror and `sitemap.md`.

### Issue 2
src/lib/app.ts:32-35
**Unset environment enables indexing**

If a development, staging, or self-hosted deployment runs without `VERCEL_ENV`, this function classifies it as production, causing the layout to omit `noindex`, robots.txt to permit crawling, and the sitemap to expose pages despite the stated non-production invariant.

### Issue 3
src/middleware.ts:71-73
**Accept qualities are ignored**

The substring check selects Markdown even for headers such as `text/html;q=1, text/markdown;q=0`, so clients that reject Markdown or prefer HTML receive the Markdown mirror instead of the canonical scene page.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "AEO: structured data, agent surfaces, cr..." | Re-trigger Greptile

Comment thread src/app/api/md/scenes/[slug]/markdown.ts Outdated
Comment thread src/lib/app.ts
Comment thread src/middleware.ts Outdated
…values

- mdText() neutralizes markdown syntax and collapses whitespace in
  user-generated scene titles, descriptions, and author names before they
  are interpolated into the scene .md mirrors and /sitemap.md, so
  published metadata can't inject links/headings into agent-facing docs
- Middleware content negotiation now parses Accept q-values instead of a
  substring check: text/markdown;q=0 or an html preference no longer
  serves the markdown mirror
- Document why unset VERCEL_ENV counts as production (only occurs
  off-Vercel; every real Vercel deployment sets it)
- Cap next/image deviceSizes at 2560 (drops multi-MB 3840 srcset
  candidates on full-bleed community hero imagery)
The ?effect= filter URLs were linked from every scene tag but shared one
canonical with no distinct metadata — nothing an answer engine could cite
for queries like "browser CRT shader effect".

- /tools/shader-lab/community/effects/[effect]: one landing page per
  effect with unique title/description/canonical built from the layer
  catalog, example image, community scenes using it (reuses
  PublicSceneGrid with server-fetched initial data), sibling-effect nav,
  and CollectionPage + BreadcrumbList JSON-LD
- /tools/shader-lab/community/effects: index hub of all effects with
  previews and descriptions
- Scene-page tags and the about-page catalog now link the effect pages
  (the interactive gallery filter keeps its query URLs)
- Effect pages added to sitemap.xml, sitemap.md, and llms.txt (llms.txt
  now lists each effect with a description and link when community is
  enabled)
- Middleware excludes the `effects` segment from scene .md handling
With cacheComponents enabled, awaiting params in the page's top-level
component fails the PPR shell prerender ("Uncached data was accessed
outside of <Suspense>") — but only when community is enabled at build
time, so the local community-disabled build passed while the Vercel
deployment failed. Restructured to the scene-page pattern: sync default
export, params awaited in a child rendered inside Suspense with a
skeleton fallback. Reproduced the failure and verified the fix locally
with community env vars set.
@ragojose
ragojose merged commit 54b4497 into main Aug 31, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants