Rendering — what crawlers receive
The site is a React app, but crawlers never depend on JavaScript: the edge worker (apps/web/worker) renders each page's SEO HTML and injects it into the app shell. React then mounts on #root with the same content.
Per request
www→ apex (301)./→/{best locale}fromAccept-Language(302,Vary: Accept-Language).- Static files are served as-is.
/{locale}/…is parsed byparseRoute()(worker/seo.ts). Unknown shapes → 404.renderSeo()reads the API through theAPIservice binding and returns title, description, head tags and body HTML.inject()writes it into the shell:<html lang>,<title>, meta description, head tags,#rootcontent.
What each page carries
| Page | <title> pattern | JSON-LD |
|---|---|---|
| Artwork | {title} — {artist} | ArtLove365 | VisualArtwork, BreadcrumbList (no price: artwork pages carry no ordering, F-ART-06) |
| Artist | {artist} — {n} hand-painted oil painting reproductions | Person, BreadcrumbList |
| Museum / movement / subject / genre | {name} — {n} hand-painted oil painting reproductions | CollectionPage, BreadcrumbList |
| Home | site title | WebSite (+ SearchAction), Organization |
| Listings | {section} – page N | — |
Every page also gets a canonical (https://artlove365.com/{locale}{path}[?page=N]), hreflang links for all 6 languages plus x-default (English), and Open Graph tags. Pagination is made of real <a href> links.
Not indexed
/{l}/orders/*,/{l}/checkout/*,/{l}/favorites→noindex,nofollow(personal)./admin→noindex,nofollow, alsoDisallowinrobots.txt.- 404 pages →
noindex.
Caching
Rendered pages are stored in the Cloudflare edge cache for 1 hour (s-maxage=3600), so a crawler burst costs one render per URL per hour. Search results (?q=) and personal pages are never cached.
Lesson learned
The shell must be fetched as /, never /index.html: with html_handling: drop-trailing-slash the assets layer answers /index.html with a 307 and an empty body, which once shipped a blank site with status 200. CI now checks the page body after every deploy.