Skip to content
This repository was archived by the owner on Apr 28, 2026. It is now read-only.

Commit 8156447

Browse files
authored
add json-ld to blogs (#1768)
* add json-ld to blogs * add more fields
1 parent 280d5f1 commit 8156447

4 files changed

Lines changed: 105 additions & 2 deletions

File tree

blog/2026-01-09-top-python-web-frameworks-2026.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
---
22
author: Tom Gotsman
3+
author_bio: Tom Gotsman is a Software Engineer at Reflex.
34
date: 2026-01-09
5+
updated_at: 2026-01-09
46
title: Top Python Web Development Frameworks in 2026
7+
title_tag: Best Python Web Frameworks 2026 - Reflex, Django, Flask & More
58
description: Reflex vs Django vs Flask vs Gradio vs Streamlit vs Dash vs FastAPI
69
image: /blog/top_python_web_frameworks_2026.png
710
tag: Builder
811
meta: [
912
{"name": "keywords", "content": "streamlit python, streamlit, streamlit alternatives, plotly, dash app, plotly python, fastapi"},
1013
]
14+
faq: [
15+
{"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."},
16+
{"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."},
17+
{"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."},
18+
]
1119
---
1220

1321

pcweb/meta/meta.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import json
2+
13
import reflex as rx
24

35
from pcweb.constants import REFLEX_DOMAIN, REFLEX_DOMAIN_URL, TWITTER_CREATOR
@@ -105,3 +107,79 @@ def create_meta_tags(
105107
image=image_url,
106108
url=page_url,
107109
)
110+
111+
112+
def _normalize_image_url(image: str) -> str:
113+
"""Ensure image path is a full URL."""
114+
if image and not image.startswith(("http://", "https://")):
115+
return f"https://reflex.dev{'' if image.startswith('/') else '/'}{image}"
116+
return image
117+
118+
119+
def blog_jsonld(
120+
title: str,
121+
description: str,
122+
author: str,
123+
date: str,
124+
image: str,
125+
url: str,
126+
faq: list[dict[str, str]] | None = None,
127+
author_bio: str | None = None,
128+
updated_at: str | None = None,
129+
) -> rx.Component:
130+
"""Create a single JSON-LD script tag with @graph for a blog post.
131+
132+
Always includes a BlogPosting entry. If faq items are provided,
133+
a FAQPage entry is also added to the graph.
134+
"""
135+
author_node: dict = {"@type": "Person", "name": author}
136+
if author_bio:
137+
author_node["description"] = author_bio
138+
139+
posting: dict = {
140+
"@type": "BlogPosting",
141+
"headline": title,
142+
"description": description,
143+
"image": _normalize_image_url(image),
144+
"datePublished": str(date),
145+
"author": author_node,
146+
}
147+
if updated_at:
148+
posting["dateModified"] = str(updated_at)
149+
150+
graph: list[dict] = [
151+
{
152+
**posting,
153+
"publisher": {
154+
"@type": "Organization",
155+
"name": "Reflex",
156+
"url": REFLEX_DOMAIN_URL,
157+
},
158+
"mainEntityOfPage": {
159+
"@type": "WebPage",
160+
"@id": url,
161+
},
162+
},
163+
]
164+
if faq:
165+
graph.append(
166+
{
167+
"@type": "FAQPage",
168+
"mainEntity": [
169+
{
170+
"@type": "Question",
171+
"name": item["question"],
172+
"acceptedAnswer": {
173+
"@type": "Answer",
174+
"text": item["answer"],
175+
},
176+
}
177+
for item in faq
178+
],
179+
}
180+
)
181+
data = {
182+
"@context": "https://schema.org",
183+
"@graph": graph,
184+
}
185+
return rx.el.script(json.dumps(data), type="application/ld+json")

pcweb/pages/blog/blog.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,15 @@ def blogs():
211211
# Get the docpage component.
212212
route = f"/blog/{path}"
213213
title = rx.utils.format.to_snake_case(path.rsplit("/", 1)[1].replace(".md", ""))
214+
# Use title_tag for <title> and og/twitter if provided, otherwise fall back to title.
215+
seo_title = document.metadata.get("title_tag") or document.metadata["title"]
214216
comp = marketing_page(
215217
path=route,
216-
title=document.metadata["title"] + " · Reflex Blog",
218+
title=seo_title,
217219
description=document.metadata["description"],
218220
image=document.metadata["image"],
219221
meta=create_meta_tags(
220-
title=document.metadata["title"],
222+
title=seo_title,
221223
description=document.metadata["description"],
222224
image=document.metadata["image"],
223225
url=f"https://reflex.dev{route}",

pcweb/pages/blog/page.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pcweb.components.marketing_button import button
88
from pcweb.constants import REFLEX_URL
99
from pcweb.flexdown import xd2 as xd
10+
from pcweb.meta.meta import blog_jsonld
1011
from pcweb.templates.docpage import get_toc, right_sidebar_item_highlight
1112

1213
from .paths import blog_data
@@ -183,7 +184,21 @@ def page(document, route) -> rx.Component:
183184
toc, _ = get_toc(document, route)
184185
toc = [(level, text) for level, text in toc if level <= 3]
185186
page_url = f"{REFLEX_URL.strip('/')}{route}"
187+
188+
jsonld_script = blog_jsonld(
189+
title=meta["title"],
190+
description=meta["description"],
191+
author=meta["author"],
192+
date=str(meta["date"]),
193+
image=meta["image"],
194+
url=page_url,
195+
faq=meta.get("faq"),
196+
author_bio=meta.get("author_bio"),
197+
updated_at=str(meta["updated_at"]) if meta.get("updated_at") else None,
198+
)
199+
186200
return rx.el.section(
201+
jsonld_script,
187202
rx.el.article(
188203
rx.el.div(
189204
rx.el.div(

0 commit comments

Comments
 (0)