Commit 8aa71da
claude
site: /for/{slug}/ pages redirect to live generator, retiring stale static pricing
The 195 /for/{slug}/ bundle pages were pre-pivot product artifacts:
$7.99/mo pricing, '14-day trial' CTAs pointing at a /pricing/ page
that no longer has a trial, SaaS-shaped offers that don't match the
current desktop product. Every one was a factually wrong landing for
visitors arriving via SEO. Fixing them individually would be 195
find-and-replace edits that still wouldn't match the live product's
value proposition.
This ships the alternative: retire the static pages entirely and
redirect visitors to the live bundle generator with a pre-filled
business description, producing an up-to-date result card that
matches current product reality.
Three moving parts:
site/index.html — homepage ?q= URL parameter handler (and
static/index.html via make site-sync)
- On page load, reads ?q=<string> from the querystring
- If present, length-valid (3-500 chars), pre-fills the input,
auto-submits, and replaces the URL to /? so reload doesn't
re-trigger and the sharable URL is clean
- Wrapped in try/catch so ancient browsers without
URLSearchParams just see the normal homepage
- Reuses the existing submit() code path entirely — same
render, counter bump, GA4 event, error handling, timeout,
offline state
internal/site/recommend.go — BundleQuery() helper + override map
- BundleQuery(slug) returns (query, true) for the 195 known
bundle slugs in bundles.json, defaulting to the bundle Name
field ("Breweries & Distilleries" for brewery,
"Therapists & Counselors" for therapist, etc.)
- bundleQueryOverrides{} holds slug-specific overrides for
slugs where the bundle Name produces ambiguous LLM output.
Currently one entry: ark-rust → "ARK and Rust video game
server administration" (without override, the LLM reads
"Rust" as the programming language and produces a gamedev
toolkit)
internal/site/site.go — /for/{slug}/ route handler
- orphanBundleSlugs{} identifies 3 /for/ dirs without
bundles.json entries (self-hosters, solo-developers,
startups) — pre-pivot artifacts that can't be routed to the
generator
- In the /for/ handler, before falling through to static file
serve: if the slug matches a known bundle, 302 redirect to
/?q=<url-encoded-query>. If it's an orphan, 302 to /desktop/.
- Preserved as-is: the /for/ index page (canonical
browse-all-bundles), /for/{slug}/install.sh (real endpoint
for desktop install), AI-generated cache serve for unknown
slugs
Design choices:
302 not 301 during rollout. 301s cache in browsers indefinitely —
a bug in the redirect would leave affected users stuck on bad
redirects until they manually clear their cache. 302 is
recoverable. Upgrade to 301 in a followup after confirming this
has been running cleanly for a week.
Pre-verification of all 195 slugs against live /api/recommend
before shipping. Every slug produces ≥4 tools and a coherent
title. The one exception (ark-rust) is handled by override.
Audit data in /tmp/prewarm-results.jsonl (off-repo).
Orphan slugs redirect to /desktop/ (the install page) rather
than the homepage generator. The LLM can't coherently generate
a toolkit for "self-hosters" — it isn't a business description.
/desktop/ is the next-best destination (pricing + install CTAs
they can act on).
What's NOT in this change:
- The underlying static site/for/{slug}/index.html files still
exist. They're unreachable now for the 195 + 3 redirected
slugs, but the files weren't deleted. Deletion is a separate
followup (keeping them lets us flip the redirect off quickly if
needed for debugging).
- No test coverage for the redirect behavior. Existing site-
package tests run against Register(mux, nil) where db==nil
means recommender==nil, so the redirect branch doesn't fire
in test context. Building a test helper that stands up a
Recommender with in-memory DB is a worthwhile followup.
Verification:
- go build ./internal/site/...: clean
- go vet ./internal/site/...: clean
- go test ./internal/site/...: all existing tests pass (including
TestNoDuplicateRoutes which catches accidental route conflicts)
- Secret scan: 0 hits
- URL encoding sanity-checked: 'Breweries & Distilleries' →
/?q=Breweries+%26+Distilleries (correct Go url.QueryEscape
output matches Python urllib quote_plus)
Rollback if a bad redirect escapes the 195-slug audit:
git revert HEAD && git push. ~170s redeploy. The 302 (not 301)
means browsers won't hold the bad redirect after the revert.1 parent d301968 commit 8aa71da
4 files changed
Lines changed: 183 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
318 | 318 | | |
319 | 319 | | |
320 | 320 | | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
321 | 359 | | |
322 | 360 | | |
323 | 361 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
| |||
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
24 | 39 | | |
25 | 40 | | |
26 | 41 | | |
| |||
389 | 404 | | |
390 | 405 | | |
391 | 406 | | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
392 | 459 | | |
393 | 460 | | |
394 | 461 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1033 | 1033 | | |
1034 | 1034 | | |
1035 | 1035 | | |
| 1036 | + | |
| 1037 | + | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
| 1046 | + | |
| 1047 | + | |
| 1048 | + | |
| 1049 | + | |
| 1050 | + | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
| 1071 | + | |
| 1072 | + | |
| 1073 | + | |
| 1074 | + | |
1036 | 1075 | | |
1037 | 1076 | | |
1038 | 1077 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1033 | 1033 | | |
1034 | 1034 | | |
1035 | 1035 | | |
| 1036 | + | |
| 1037 | + | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
| 1046 | + | |
| 1047 | + | |
| 1048 | + | |
| 1049 | + | |
| 1050 | + | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
| 1071 | + | |
| 1072 | + | |
| 1073 | + | |
| 1074 | + | |
1036 | 1075 | | |
1037 | 1076 | | |
1038 | 1077 | | |
| |||
0 commit comments