From 7d60dc86b02cab77372e93d05c18d2bd1e71c626 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Mar 2026 11:34:47 +0100 Subject: [PATCH 1/2] add json-ld to blogs --- ...26-01-09-top-python-web-frameworks-2026.md | 5 ++ pcweb/meta/meta.py | 69 +++++++++++++++++++ pcweb/pages/blog/blog.py | 2 +- pcweb/pages/blog/page.py | 13 ++++ 4 files changed, 88 insertions(+), 1 deletion(-) diff --git a/blog/2026-01-09-top-python-web-frameworks-2026.md b/blog/2026-01-09-top-python-web-frameworks-2026.md index a7395f47a..4fa96a5c3 100644 --- a/blog/2026-01-09-top-python-web-frameworks-2026.md +++ b/blog/2026-01-09-top-python-web-frameworks-2026.md @@ -8,6 +8,11 @@ tag: Builder meta: [ {"name": "keywords", "content": "streamlit python, streamlit, streamlit alternatives, plotly, dash app, plotly python, fastapi"}, ] +faq: [ + {"question": "What is the best Python web framework in 2026?", "answer": "It depends on your use case. Reflex is ideal for full-stack apps in pure Python, Django for large-scale applications, Flask for lightweight APIs, and FastAPI for high-performance async APIs."}, + {"question": "Can I build a full-stack web app with only Python?", "answer": "Yes. Frameworks like Reflex let you build both the frontend and backend entirely in Python without writing JavaScript."}, + {"question": "What is the difference between Streamlit and Reflex?", "answer": "Streamlit is designed for quick data apps and dashboards, while Reflex is a full-stack framework for building production-grade, customizable web applications entirely in Python."}, +] --- diff --git a/pcweb/meta/meta.py b/pcweb/meta/meta.py index fd06dc52d..d0f0554f1 100644 --- a/pcweb/meta/meta.py +++ b/pcweb/meta/meta.py @@ -1,3 +1,5 @@ +import json + import reflex as rx from pcweb.constants import REFLEX_DOMAIN, REFLEX_DOMAIN_URL, TWITTER_CREATOR @@ -105,3 +107,70 @@ def create_meta_tags( image=image_url, url=page_url, ) + + +def _normalize_image_url(image: str) -> str: + """Ensure image path is a full URL.""" + if image and not image.startswith(("http://", "https://")): + return f"https://reflex.dev{'' if image.startswith('/') else '/'}{image}" + return image + + +def blog_jsonld( + title: str, + description: str, + author: str, + date: str, + image: str, + url: str, + faq: list[dict[str, str]] | None = None, +) -> rx.Component: + """Create a single JSON-LD script tag with @graph for a blog post. + + Always includes a BlogPosting entry. If faq items are provided, + a FAQPage entry is also added to the graph. + """ + graph: list[dict] = [ + { + "@type": "BlogPosting", + "headline": title, + "description": description, + "image": _normalize_image_url(image), + "datePublished": str(date), + "author": { + "@type": "Person", + "name": author, + }, + "publisher": { + "@type": "Organization", + "name": "Reflex", + "url": REFLEX_DOMAIN_URL, + }, + "mainEntityOfPage": { + "@type": "WebPage", + "@id": url, + }, + }, + ] + if faq: + graph.append( + { + "@type": "FAQPage", + "mainEntity": [ + { + "@type": "Question", + "name": item["question"], + "acceptedAnswer": { + "@type": "Answer", + "text": item["answer"], + }, + } + for item in faq + ], + } + ) + data = { + "@context": "https://schema.org", + "@graph": graph, + } + return rx.el.script(json.dumps(data), type="application/ld+json") diff --git a/pcweb/pages/blog/blog.py b/pcweb/pages/blog/blog.py index 2755cf1f0..d32038031 100644 --- a/pcweb/pages/blog/blog.py +++ b/pcweb/pages/blog/blog.py @@ -213,7 +213,7 @@ def blogs(): title = rx.utils.format.to_snake_case(path.rsplit("/", 1)[1].replace(".md", "")) comp = marketing_page( path=route, - title=document.metadata["title"] + " ยท Reflex Blog", + title=document.metadata["title"], description=document.metadata["description"], image=document.metadata["image"], meta=create_meta_tags( diff --git a/pcweb/pages/blog/page.py b/pcweb/pages/blog/page.py index 080abdbaa..99edfedb9 100644 --- a/pcweb/pages/blog/page.py +++ b/pcweb/pages/blog/page.py @@ -7,6 +7,7 @@ from pcweb.components.marketing_button import button from pcweb.constants import REFLEX_URL from pcweb.flexdown import xd2 as xd +from pcweb.meta.meta import blog_jsonld from pcweb.templates.docpage import get_toc, right_sidebar_item_highlight from .paths import blog_data @@ -183,7 +184,19 @@ def page(document, route) -> rx.Component: toc, _ = get_toc(document, route) toc = [(level, text) for level, text in toc if level <= 3] page_url = f"{REFLEX_URL.strip('/')}{route}" + + jsonld_script = blog_jsonld( + title=meta["title"], + description=meta["description"], + author=meta["author"], + date=str(meta["date"]), + image=meta["image"], + url=page_url, + faq=meta.get("faq"), + ) + return rx.el.section( + jsonld_script, rx.el.article( rx.el.div( rx.el.div( From 998393d0135378e006893536f1a178706a7f0cce Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Mar 2026 16:39:53 +0100 Subject: [PATCH 2/2] add more fields --- ...26-01-09-top-python-web-frameworks-2026.md | 3 +++ pcweb/meta/meta.py | 27 ++++++++++++------- pcweb/pages/blog/blog.py | 6 +++-- pcweb/pages/blog/page.py | 2 ++ 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/blog/2026-01-09-top-python-web-frameworks-2026.md b/blog/2026-01-09-top-python-web-frameworks-2026.md index 4fa96a5c3..221e1ca1a 100644 --- a/blog/2026-01-09-top-python-web-frameworks-2026.md +++ b/blog/2026-01-09-top-python-web-frameworks-2026.md @@ -1,7 +1,10 @@ --- author: Tom Gotsman +author_bio: Tom Gotsman is a Software Engineer at Reflex. date: 2026-01-09 +updated_at: 2026-01-09 title: Top Python Web Development Frameworks in 2026 +title_tag: Best Python Web Frameworks 2026 - Reflex, Django, Flask & More description: Reflex vs Django vs Flask vs Gradio vs Streamlit vs Dash vs FastAPI image: /blog/top_python_web_frameworks_2026.png tag: Builder diff --git a/pcweb/meta/meta.py b/pcweb/meta/meta.py index d0f0554f1..1b9d25371 100644 --- a/pcweb/meta/meta.py +++ b/pcweb/meta/meta.py @@ -124,23 +124,32 @@ def blog_jsonld( image: str, url: str, faq: list[dict[str, str]] | None = None, + author_bio: str | None = None, + updated_at: str | None = None, ) -> rx.Component: """Create a single JSON-LD script tag with @graph for a blog post. Always includes a BlogPosting entry. If faq items are provided, a FAQPage entry is also added to the graph. """ + author_node: dict = {"@type": "Person", "name": author} + if author_bio: + author_node["description"] = author_bio + + posting: dict = { + "@type": "BlogPosting", + "headline": title, + "description": description, + "image": _normalize_image_url(image), + "datePublished": str(date), + "author": author_node, + } + if updated_at: + posting["dateModified"] = str(updated_at) + graph: list[dict] = [ { - "@type": "BlogPosting", - "headline": title, - "description": description, - "image": _normalize_image_url(image), - "datePublished": str(date), - "author": { - "@type": "Person", - "name": author, - }, + **posting, "publisher": { "@type": "Organization", "name": "Reflex", diff --git a/pcweb/pages/blog/blog.py b/pcweb/pages/blog/blog.py index d32038031..3a3f0e0ef 100644 --- a/pcweb/pages/blog/blog.py +++ b/pcweb/pages/blog/blog.py @@ -211,13 +211,15 @@ def blogs(): # Get the docpage component. route = f"/blog/{path}" title = rx.utils.format.to_snake_case(path.rsplit("/", 1)[1].replace(".md", "")) + # Use title_tag for and og/twitter if provided, otherwise fall back to title. + seo_title = document.metadata.get("title_tag") or document.metadata["title"] comp = marketing_page( path=route, - title=document.metadata["title"], + title=seo_title, description=document.metadata["description"], image=document.metadata["image"], meta=create_meta_tags( - title=document.metadata["title"], + title=seo_title, description=document.metadata["description"], image=document.metadata["image"], url=f"https://reflex.dev{route}", diff --git a/pcweb/pages/blog/page.py b/pcweb/pages/blog/page.py index 99edfedb9..14891c1cf 100644 --- a/pcweb/pages/blog/page.py +++ b/pcweb/pages/blog/page.py @@ -193,6 +193,8 @@ def page(document, route) -> rx.Component: image=meta["image"], url=page_url, faq=meta.get("faq"), + author_bio=meta.get("author_bio"), + updated_at=str(meta["updated_at"]) if meta.get("updated_at") else None, ) return rx.el.section(