@divyeshlakhotia @gabriel.duro.s
Not sure if you had already found a way, but I am doing the following nowadays (where I am even able dynamically show images based on specific slugs)
1. Global fallback meta tags (routes.py)
Every route automatically gets OG tags via a monkey-patched default:
_DEFAULT_DESC = "Connect with holistic health practitioners"
_DEFAULT_OG_IMAGE = "/_/theme/og-template.png"
def _global_meta(self, **_ignored):
image_url = f"{anvil.server.get_app_origin()}{_DEFAULT_OG_IMAGE}"
return {
"title": "Lōkahi",
"description": _DEFAULT_DESC,
"og:title": "Lōkahi",
"og:description": _DEFAULT_DESC,
"og:image": image_url,
}
Route.meta = _global_meta
2. Per-page overrides (routes.py)
Any route can override meta() for a custom title/description:
class Home(Route):
path = ""
form = "Pages.MainPages.Home"
def meta(self, **kwargs):
image_url = f"{anvil.server.get_app_origin()}{_DEFAULT_OG_IMAGE}"
return {
"title": "Lōkahi — Holistic Health Practitioners",
"description": _DEFAULT_DESC,
"og:title": "Lōkahi — Holistic Health Practitioners",
"og:description": _DEFAULT_DESC,
"og:image": image_url,
}
3. Dynamic images for detail pages (routes.py)
The practitioner detail route fetches real data and points og:image to a live HTTP endpoint:
class PractitionerDetail(Route):
path = "/practitioner/:slug"
form = "Pages.MainPages.Practitioners.PractitionerDetail"
def meta(self, **kwargs):
slug = (kwargs.get("params") or {}).get("slug", "")
data = kwargs.get("data") or anvil.server.call("get_practitioner_by_slug", slug)
if not data:
return {}
name = data.get("name") or (
f"{data.get('first_name', '')} {data.get('last_name', '')}".strip()
)
bio = (data.get("bio") or _DEFAULT_DESC)[:160]
image_url = f"{anvil.server.get_api_origin()}/practitioner-photo/{slug}"
return {
"title": f"{name} | Lōkahi",
"description": bio,
"og:title": f"{name} | Lōkahi",
"og:description": bio,
"og:image": image_url,
}
4. HTTP endpoint serving the image (practitioners_service.py)
The endpoint returns the actual image media with a fallback chain:
@anvil.server.http_endpoint("/practitioner-photo/:slug")
def serve_practitioner_photo(slug, **kwargs):
row = _get_practitioner_row_by_slug(slug)
if row is None:
raise anvil.server.HttpResponse(404, "Not found")
pub = row['public_user']
media = row['social_image'] or (pub['avatar'] if pub else None) or row['profile_image']
if media is None:
raise anvil.server.HttpResponse(404, "No image")
return media
Why it works
Social crawlers (WhatsApp, Facebook, iMessage, Twitter) read <meta og:image> from the server-rendered HTML. The Anvil routing library’s meta() method injects these tags server-side before any client Python runs — so crawlers see them even though they don’t execute JavaScript. The HTTP endpoint gives crawlers a direct URL to fetch the actual image.
See an example of the social image (dynamically below). The app is still under construction so you can ignore the rest of the website for now.