6.9 KiB
joelclaw Web (apps/web)
Next.js 16 App Router site for joelclaw.com.
Agent-oriented surfaces
- Human HTML pages: route
page.tsxtrees underapps/web/app/ - Markdown exports:
https://joelclaw.com/{slug}.md(rewritten toapp/[slug]/md/route.ts) andhttps://joelclaw.com/sitemap.md - API discovery + machine endpoints:
https://joelclaw.com/api,https://joelclaw.com/api/search,https://joelclaw.com/api/pi-mono,https://joelclaw.com/api/docs,https://joelclaw.com/feed.xml,https://joelclaw.com/llms.txt
Post-content revalidation contract
Post-like Convex content updates (article, tutorial, essay, note) must invalidate both the human page and the markdown projection. Revalidating only /${slug} is insufficient because the public markdown twin is served from a separate route handler and can stay stale at the edge.
Required tags:
post:<slug>article:<slug>articles
Required paths:
//${slug}/${slug}.md/${slug}/md/feed.xml/sitemap.md
Public pi-mono corpus surface
apps/web/app/api/pi-mono/route.tsis the discovery endpoint for the publicpi_mono_artifactscorpus.apps/web/app/api/search/route.tsnow acceptscollection=pi_mono_artifactson the public, Upstash-rate-limited search surface.- The discovery payload at
/api/pi-monoincludes:- corpus/search usage examples for
pi_mono_artifacts - current install steps for the public
contributing-to-pi-monoskill - current install steps for the public extension repo
joelhooks/contributing-to-pi-mono
- corpus/search usage examples for
- Public search returns external GitHub URLs directly for pi-mono artifacts, so top-result next actions can point straight at the source issue/PR/comment/commit/release.
/cool content routing
apps/web/app/cool/[slug]/page.tsxresolves tutorial posts first at Convex slugcool/<slug>.- If no tutorial exists for that slug, the route falls back to the legacy
discovery:<slug>record. - This keeps
/cool/...tutorial URLs working even when an older discovery stub still exists for the same topic.
ADR route aliases
- Vault and Convex keep the canonical ADR source slug from the filename, e.g.
0217-event-routing-queue-discipline. - Public web routes use the short alias form
/adrs/adr-0217, derived from the ADR number. apps/web/lib/adrs.tsresolvesadr-####(and bare####) back to the canonical Convex resource ID, so content sync does not need duplicate ADR records.apps/web/app/adrs/[slug]/page.tsxpermanently redirects legacy full-slug ADR URLs to the short alias route while keeping review/live-update lookups pinned to the canonical Convex resource ID.
Post display rules
apps/web/app/[slug]/page.tsxrenders the post title in the page header from Convex metadata.- The markdown/MDX body must not render a second top-level H1 below that header.
- The post renderer strips a markdown H1 before passing content into
MDXRemote. - Regex:
content.replace(/^#\s+.*$/m, "").trim() apps/web/app/[slug]/opengraph-image.tsx,apps/web/app/[slug]/agent-md/route.ts, andapps/web/app/[slug]/md/route.tsmust not exportgenerateStaticParams(). Let those dynamic-slug routes resolve per-request; build-time slug enumeration was crashing Vercel during page-data collection on Convex-backed reads.apps/web/app/[slug]/opengraph-image.tsxmust also degrade gracefully ifgetPost()throws, rendering a slug-based generic OG image instead of taking the whole deploy down.- Server-side Convex readers (
apps/web/lib/posts.ts,apps/web/lib/adrs.ts,apps/web/lib/discoveries.ts, andapps/web/app/network/page.tsx) must resolve the Convex URL lazily insidegetConvexClient(), strip literal\\nsuffix pollution from env values, and callsetAdminAuth(process.env.CONVEX_DEPLOY_KEY)when that key is present. Production build-time reads are not reliably public. - During
phase-production-build, ifCONVEX_URL/NEXT_PUBLIC_CONVEX_URLis missing, Convex-backed static generation must degrade to empty lists /nulllookups instead of throwing. This keeps Vercel deploys alive without restoring filesystem content as a canonical read path. Runtime requests should still fail loudly when Convex env is missing. - Cache Components route validation rejects empty
generateStaticParams()results, so slug routes use a build-only placeholder param when Convex-backed slug lists are empty. The placeholder must immediately resolve tonotFound()and must never become a real content slug. - Client-side Convex providers used by
(convex)owner routes or realtime islands must no-op or render a fallback shell whenNEXT_PUBLIC_CONVEX_URLis absent during prerender; never instantiateConvexReactClientwith an empty address.
CLAWMAIL view-source convention
Regular HTML pages include a deterministic head marker script labeled CLAWMAIL from the root shell:
- Component:
apps/web/components/clawmail-source-comment.tsx - Mounted in:
apps/web/app/layout.tsx - Placement: first explicit child inside
<head>inapp/layout.tsx(before JSON-LD script) - Marker form:
<script id="clawmail-agent-prompt" type="text/plain">...</script>
The marker is intended for agents using View Source and includes:
- A start path (
https://joelclaw.com/sitemap.md) for route and markdown endpoint discovery - API discovery instructions (
https://joelclaw.com/api→https://joelclaw.com/api/searchandhttps://joelclaw.com/api/docs) - Markdown endpoint instructions (
https://joelclaw.com/{slug}.md) withAccept: text/markdown - Plain-text hint endpoint instructions (
https://joelclaw.com/llms.txt) withAccept: text/plain - Explicit
Content-Typeverification requirements for markdown/plain/json responses - A wrong-endpoint guard (
Content-Type: text/htmlmeans fallback HTML / wrong route) - A concise claw-themed ASCII art header at the top of the marker payload for quick human scanning in raw source
Required content-type checks called out in the marker:
GET https://joelclaw.com/sitemap.mdwithAccept: text/markdown, text/plain;q=0.9→ expectContent-Typestarting withtext/markdown(text/markdown; charset=utf-8)GET https://joelclaw.com/{slug}.mdwithAccept: text/markdown→ expectContent-Typestarting withtext/markdown(text/markdown; charset=utf-8)GET https://joelclaw.com/llms.txtwithAccept: text/plain→ expectContent-Typestarting withtext/plain(text/plain; charset=utf-8)GET https://joelclaw.com/api(and/api/search,/api/docs) withAccept: application/json→ expectContent-Typestarting withapplication/json- If response
Content-Typeistext/html, treat it as fallback/wrong route and retry with the correct endpoint +Acceptheader.
This convention is intentionally scoped to the HTML layout only. Markdown/text route handlers (for example https://joelclaw.com/sitemap.md, https://joelclaw.com/{slug}.md, https://joelclaw.com/llms.txt, and API routes) are not wrapped by the layout and do not inject the CLAWMAIL head marker.