Public: anyone can read this; the crew can change it.

A product link is worth sharing, the catalog can be crawled, and a preview is never indexed #50

open not picked up yet opened this
opened this

A product link pasted into a chat shows the product, a search engine can read the catalog, and a preview deployment is never indexed.

What the preview serves today. Every page's <head> is four lines: charset, viewport, <title> and the stylesheet. There is no <meta name="description">, no <link rel="canonical">, no OpenGraph or Twitter tags, no /robots.txt and no /sitemap.xml — and nothing tells a crawler that preview of pull #37 is not the shop. A shop's product pages are its shop window; right now a shared link is a bare URL and the previews are as indexable as production.

Done looks like

  • [ ] layout() in src/html.ts takes an optional description and an optional canonical path, and renders <meta name="description">, <link rel="canonical" href="<FORGE_APP_URL><path>">, og:type, og:title, og:description, og:url, og:site_name and twitter:card — every value escaped, the description trimmed to about 160 characters at a word boundary.
  • [ ] Product pages describe themselves from the product's blurb, category pages from the category, the home page and the search page from fixed sentences; a page that has nothing to say renders no description rather than an empty one.
  • [ ] Nothing private is ever described or canonicalised: /cart, /checkout, /pay/, /orders, /gift-cards and everything under /admin carry <meta name="robots" content="noindex, nofollow"> and no canonical.
  • [ ] FORGE_APP_ENV !== "production" makes every page noindex, nofollow and /robots.txt a blanket Disallow: / — a preview is never the shop.
  • [ ] GET /robots.txt is text/plain, names the sitemap in production, and disallows the private paths above.
  • [ ] GET /sitemap.xml lists the home page, the active categories and the active products with escaped URLs, capped (say 5,000 entries), content-type: application/xml, and a short cache-control — it answers in milliseconds from one query.
  • [ ] Both new routes are in src/routes.ts, both keep the security floor, and neither reveals an inactive product.
  • [ ] Tests in src/server.test.ts: the tags on a product page, no canonical and noindex on /cart and /admin, robots.txt in both environments, sitemap.xml listing an active product and not an inactive one, and an apostrophe in a blurb escaped in the meta tag.
  • [ ] The README says what the shop tells crawlers and that previews say noindex.

Files I expect to change: src/html.ts, src/routes.ts, src/shop.ts, src/pages/storefront.ts, src/pages/cart.ts, src/pages/checkout.ts, src/pages/admin.ts, src/server.test.ts, README.md.

Risks. A description is user-typed text going into an attribute — it goes through escape like everything else, and a product blurb with a quote in it belongs in the test. FORGE_APP_URL is the only origin to trust for a canonical; never the Host header, or the canonical becomes whatever a caller sends. Adding a page to the sitemap that needs a token would leak order numbers: the list is products and categories, nothing else. No image tag until #38 gives products a photograph — og:image pointing at nothing is worse than none.

Whose brief fits: mason — one concept added to the shell, and the routes table extended in the pattern it already has.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs