Skip to content
back to portfolio

Open-source and personal projects2026

anchor — AI-native product catalog: dual-surface pages, delegated-authority checkout, AEO instrumentation

Public open-source AI-native commerce surface. Every product has a human page AND three statically-cached LLM-facing endpoints (markdown, JSON-LD, plain) optimized for citation, discovery, and agent purchase. A /.well-known/agents.json descriptor publishes endpoints, auth model, pricing-negotiation envelope, and rate limits in the Agentic Commerce Protocol shape. AEO instrumentation classifies 13 LLM crawlers by user-agent and logs every fetch to Redis via after() so cached responses still get observed. A delegated-authority checkout endpoint runs an eight-check pipeline (signature, expiry, agent binding, scope, nonce, idempotency) with HMAC-SHA256 and constant-time signature comparison, proven by a five-scenario test that asserts happy-path, replay, over-budget, wrong-SKU, and idempotent retry. A comparison agent with generative UI demonstrates structured-output dispatch: the model picks one of three shapes (spec table, pros/cons, recommendation) via a Zod discriminated union and React switch-renders the matching component, type-safe at every boundary.

Author · Applied AITypeScriptNext.js 16React 19Vercel AI SDKAnthropic SDKZodUpstashTailwind v4

Repo: github.com/midimurphdesigns/anchor

Live demo: anchor.kevinmurphywebdev.com

Read the full story: Building anchor

Anchor is an AI-native product catalog. Every product has a human page AND three statically-cached LLM-facing endpoints. A /.well-known/agents.json descriptor publishes the site's capabilities in the Agentic Commerce Protocol shape. A delegated-authority checkout endpoint runs an eight-check pipeline before any charge fires. A live AEO dashboard classifies 13 known LLM crawlers by user-agent and shows the per-product fetch counts. 40 prerendered LLM-facing routes, ~5ms TTFB on cache hits, 11/11 assertions pass on the checkout test suite.

How it's built

Next.js 16 App Router with cacheComponents: true, TypeScript strict, zero any. The whole catalog of 10 products generates 30 LLM-facing static routes via generateStaticParams, three format-specific surfaces per slug (/agent/markdown, /agent/json, /agent/plain), plus 10 redirect entries on the canonical /agent URL. Bodies are pure functions of the in-memory catalog wrapped in 'use cache' with cacheLife('hours') and cacheTag('product:<slug>'), so a sale invalidates exactly the one product entry on revalidateTag and leaves the other nine cached.

The product detail page uses Partial Prerendering. Product copy, specs, JSON-LD, and the buy CTA prerender at build time. A single dynamic Suspense hole reads headers() to opt out of static rendering and streams in the live agent-fetch tally from Redis at request time. Cached shell + live hole, served as one document.

The agent surface

LLM-facing content is split across three format-specific routes so each one can prerender independently. /agent/markdown returns the combined block: a citation opening line (Source: <canonical URL>, <brand> <name> is listed at <price>...), a JSON-LD Schema.org Product/Offer in a fenced code block, then prose with specs as bullets and a closing canonical URL line. /agent/json returns just the JSON-LD. /agent/plain returns just the prose. Each format ships ten prerendered files, served from the edge cache.

Telemetry rides outside the body. The proxy (Next 16's renamed middleware.ts) runs at the edge on every /agent request regardless of cache status, classifies the User-Agent against 13 known LLM crawlers, and queues a Redis write via after(). Cache hits stay observed because the proxy still executes. The tradeoff that mattered: putting the logging inside the route handler would have missed every cache hit.

Delegated-authority checkout

POST /api/agent/checkout runs an ordered eight-check pipeline. Signature verifies first (HMAC-SHA256 with constant-time comparison, === would leak signature bytes byte-by-byte via timing). Expiry second. Agent binding third. Scope fourth (action + SKU + maxCents must all match). Nonce (the token's jti) fifth, single-use via Redis. Idempotency-Key sixth, cached responses returned without re-running the pipeline. Inventory + floor-price seventh. Charge + tag invalidation eighth.

The order is cheapest-first so an attacker spamming malformed tokens never touches Redis. The crypto rejects them in ~1ms. Production-ready in shape, demo-ready in price (HMAC instead of asymmetric ed25519, the verification logic is identical either way, only the key model changes). Five test scenarios in scripts/test-checkout.ts cover happy path, replay attack, over-budget, wrong-SKU, and idempotent retry; eleven assertions all pass.

Generative UI with structured-output dispatch

A /compare page lets the user pick two products. The model decides which UI shape fits, side-by-side spec table for same-category rivals, pros/cons split for overlapping use cases, recommendation paragraph for unrelated products. Implemented with streamObject from AI SDK v6 against a Zod discriminated union (three shapes, each tagged with a kind literal). The client switches on kind and React-renders the matching component. Type-safe at every boundary; the model never emits markup, only structured fields.

Self-documenting build

Every route's render mode is documented in /docs/rendering, a single page that lists the full route table with the rationale for each choice (SSG vs PPR vs Dynamic) plus the ten Next 16 + AI SDK v6 primitives the build leans on, with file paths to grep for each. A dev-only render inspector overlays colored boundaries on every annotated region with a counterfactual savings panel that compares the current TTFB against a fully-dynamic baseline. Toggle it on; the architecture becomes visible.

Artifacts worth reading

  • lib/principal.ts. The eight-check verifier. HMAC, constant-time compare, ordered failure modes with stable codes for HTTP mapping.
  • lib/agents-descriptor.ts. The single source of truth for /.well-known/agents.json, /llms.txt, and the human /agents page. Documentation IS the implementation.
  • proxy.ts. Cited-by attribution + AEO logging cross-cutting concerns. Where after() rides on cached routes.
  • app/products/[slug]/agent/markdown/route.ts. The static citation-shaped LLM surface; nine lines of route logic, every byte pure.
  • lib/render-modes.ts. The render-mode catalog the docs page reads from. New routes get an entry here and the documentation updates automatically.

The trade-offs

The HMAC token shape is a demo-grade simplification. A production deployment would use asymmetric signatures (ed25519) issued by the user's wallet, with anchor verifying via the user's public key, the eight-check logic is identical, only the key model changes. The catalog of ten products is fictional; pushing this to real ecommerce data would land via a Supabase loader behind the same 'use cache' wrapper without changing the cache shape. The in-memory Redis fallback (for local dev without Upstash) is intentionally per-process and doesn't survive restarts; production needs Upstash configured for cross-instance state. The comparison agent uses Claude Haiku 4.5 at temperature 0.3 for fast structured output; a production version would A/B test the model + temperature pair against the eval scoreboard. The architecture is shaped for these additions; the demo intentionally stops before them.