Skip to content

Commit 940a96e

Browse files
committed
Add sitemap generation to reflex app
1 parent 43d79d3 commit 940a96e

2 files changed

Lines changed: 94 additions & 0 deletions

File tree

reflex/app.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
from reflex.utils import codespaces, console, exceptions, format, prerequisites, types
8787
from reflex.utils.exec import is_prod_mode, is_testing_env, should_skip_compile
8888
from reflex.utils.imports import ImportVar
89+
from sitemap import serve_sitemap
8990

9091
# Define custom types.
9192
ComponentCallable = Callable[[], Component]
@@ -270,6 +271,7 @@ def __init__(self, **kwargs):
270271
)
271272
super().__init__(**kwargs)
272273
base_state_subclasses = BaseState.__subclasses__()
274+
self.add_sitemap()
273275

274276
# Special case to allow test cases have multiple subclasses of rx.BaseState.
275277
if not is_testing_env() and len(base_state_subclasses) > 1:
@@ -302,6 +304,11 @@ def __init__(self, **kwargs):
302304
from reflex.utils.compat import windows_hot_reload_lifespan_hack
303305

304306
self.register_lifespan_task(windows_hot_reload_lifespan_hack)
307+
308+
def add_sitemap(self):
309+
@self.api.get("/sitemap.xml")
310+
async def sitemap():
311+
return await serve_sitemap(self)
305312

306313
def _enable_state(self) -> None:
307314
"""Enable state for the app."""
@@ -462,6 +469,8 @@ def add_page(
462469
EventHandler | EventSpec | list[EventHandler | EventSpec] | None
463470
) = None,
464471
meta: list[dict[str, str]] = constants.DefaultPage.META_LIST,
472+
sitemap_priority: Optional[float] = None,
473+
sitemap_changefreq: Optional[str] = None,
465474
):
466475
"""Add a page to the app.
467476
@@ -476,6 +485,8 @@ def add_page(
476485
image: The image to display on the page.
477486
on_load: The event handler(s) that will be called each time the page load.
478487
meta: The metadata of the page.
488+
sitemap_priority: sitemap priority
489+
sitemap_changefreq: sitemap change frequency
479490
480491
Raises:
481492
ValueError: When the specified route name already exists.
@@ -522,6 +533,19 @@ def add_page(
522533
if isinstance(component, tuple):
523534
component = Fragment.create(*component)
524535

536+
# Set explicit sitemap attributes if provided
537+
if sitemap_priority is not None:
538+
component.sitemap_priority = sitemap_priority
539+
if sitemap_changefreq is not None:
540+
component.sitemap_changefreq = sitemap_changefreq
541+
542+
# Auto-detect priority if not explicitly set
543+
if not hasattr(component, 'sitemap_priority'):
544+
component.sitemap_priority = self._auto_detect_priority(route)
545+
546+
# Set default changefreq if not explicitly set
547+
if not hasattr(component, 'sitemap_changefreq'):
548+
component.sitemap_changefreq = "weekly"
525549
# Ensure state is enabled if this page uses state.
526550
if self.state is None:
527551
if on_load or component._has_stateful_event_triggers():
@@ -567,6 +591,14 @@ def add_page(
567591
on_load = [on_load]
568592
self.load_events[route] = on_load
569593

594+
595+
def _auto_detect_priority(self, route: Optional[str]) -> float:
596+
"""Auto detect sitemap priority"""
597+
if route is None or route == "/" or route == "index":
598+
return 1.0
599+
depth = route.count("/")
600+
return max(0.1, 1.0 - (depth * 0.2))
601+
570602
def get_load_events(self, route: str) -> list[EventHandler | EventSpec]:
571603
"""Get the load events for a route.
572604

reflex/sitemap.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from typing import Dict, Any, List
2+
import reflex as rx
3+
from reflex.components.component import Component
4+
from reflex.config import get_config
5+
from fastapi import Response
6+
from xml.etree.ElementTree import Element, SubElement, tostring
7+
from xml.dom import minidom
8+
9+
def generate_xml(links: List[Dict[str, Any]]) -> str:
10+
urlset = Element("urlset", xmlns="https://www.sitemaps.org/schemas/sitemap/0.9")
11+
for link in links:
12+
url = SubElement(urlset, "url")
13+
loc = SubElement(url, "loc")
14+
loc.text = link["loc"]
15+
changefreq = SubElement(url, "changefreq")
16+
changefreq.text = link["changefreq"]
17+
priority = SubElement(url, "priority")
18+
priority.text = str(link["priority"])
19+
rough_string = tostring(urlset, "utf-8")
20+
reparsed = minidom.parseString(rough_string)
21+
return reparsed.toprettyxml(indent=" ")
22+
23+
def generate_sitemap(app: rx.App) -> str:
24+
links = []
25+
for route, component in app.pages.items():
26+
# Ignore dynamic routes for now
27+
if "[" in route and "]" in route:
28+
continue
29+
30+
# Handle the index route
31+
if route == "index":
32+
route = "/"
33+
34+
35+
# Check for explicit sitemap settings
36+
sitemap_priority = getattr(component, "sitemap_priority", None)
37+
sitemap_changefreq = getattr(component, "sitemap_changefreq", "weekly")
38+
39+
# Calculate priority if not explicitly set
40+
if sitemap_priority is None:
41+
depth = route.count("/")
42+
sitemap_priority = max(0.5, 1.0 - (depth * 0.1))
43+
44+
links.append(
45+
{
46+
"loc": f"{{{{ BASE_URL}}}}{route}",
47+
"changefreq": sitemap_changefreq,
48+
"priority": sitemap_priority,
49+
}
50+
)
51+
return generate_xml(links)
52+
53+
async def serve_sitemap(app: rx.App) -> Response:
54+
sitemap_content = generate_sitemap(app)
55+
domain = get_config().deploy_url or "http://localhost:3000"
56+
modified_sitemap = sitemap_content.replace("{{ BASE_URL }}", domain)
57+
return Response(content=modified_sitemap, media_type="application/xml")
58+
59+
def add_sitemap_to_app(app: rx.App):
60+
@app.api.get("/sitemap.xml")
61+
async def sitemap():
62+
return await serve_sitemap(app)

0 commit comments

Comments
 (0)