Skip to content

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 ​

  1. www → apex (301). / → /{best locale} from Accept-Language (302, Vary: Accept-Language).
  2. Static files are served as-is.
  3. /{locale}/… is parsed by parseRoute() (worker/seo.ts). Unknown shapes → 404.
  4. renderSeo() reads the API through the API service binding and returns title, description, head tags and body HTML.
  5. inject() writes it into the shell: <html lang>, <title>, meta description, head tags, #root content.

What each page carries ​

Page<title> patternJSON-LD
Artwork{title} — {artist} | ArtLove365VisualArtwork, BreadcrumbList (no price: artwork pages carry no ordering, F-ART-06)
Artist{artist} — {n} hand-painted oil painting reproductionsPerson, BreadcrumbList
Museum / movement / subject / genre{name} — {n} hand-painted oil painting reproductionsCollectionPage, BreadcrumbList
Homesite titleWebSite (+ 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, also Disallow in robots.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.