Add backend-generated sitemap with website manifest sync and nginx SEO proxy support.

Stores synced route config from /meshkee/sitemap-config.json, serves sitemap.xml and robots.txt per tenant, and invalidates cache on CMS writes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-22 15:15:32 +03:30
co-authored by Cursor
parent c7d5204b49
commit 2e8d7c1cfd
27 changed files with 1167 additions and 3 deletions
+3
View File
@@ -262,6 +262,9 @@ All routes are prefixed with `/api/v1`.
| POST | `/auth/verify-otp` | Verify OTP (marks cell verified; no tokens) |
| POST | `/auth/handoff/consume` | One-time SSO ticket → tokens (customer → business dashboard) |
| GET | `/tenants/:host` | Resolve business from domain (`specialProductsSource`, `enabledModules`, `homeCharts`, `ePayment`) |
| GET | `/tenants/:host/sitemap.xml` | XML sitemap (static pages from synced manifest + published products/blogs/portfolios) |
| GET | `/tenants/:host/robots.txt` | robots.txt pointing to apex `/sitemap.xml` |
| POST | `/businesses/:id/website/sitemap/sync` | Import manifest from `GET https://{domain}/meshkee/sitemap-config.json` |
| GET | `/tenants/:host/store-specials` | Active store specials (`source` is `product` or `store_item`) |
| GET | `/tenants/:host/website/category-groups` | Homepage category rows |
| GET | `/tenants/:host/website/brand-groups` | Homepage brand rows |
+30
View File
@@ -85,6 +85,36 @@ Use `slot.key` in the placeholder. Match `pageKey` to the website page. For a si
`kind` is `single` (one image) or `list` (duplicatable). For a fixed row, set `itemCount` (e.g. `2`). Leave `itemCount` null for an unbounded slider. `aspectRatio` must look like `16:9`. Do not invent CMS/upload APIs.
### Sitemap (SEO)
Meshkee generates `https://<WEBSITE_DOMAIN>/sitemap.xml` on the API from published CMS content plus a manifest your site publishes. Nginx on the storefront proxies `/sitemap.xml` and `/robots.txt` to the API — do not hardcode a static sitemap file in the repo unless you know nginx is not proxying yet.
**Publish route manifest** so the business dashboard can sync static pages and URL templates:
`GET https://<WEBSITE_DOMAIN>/meshkee/sitemap-config.json`
```json
{
"baseUrl": "https://sanihome.ir",
"staticPages": [
{ "path": "/", "changefreq": "daily", "priority": 1.0 },
{ "path": "/about", "priority": 0.6 },
{ "path": "/contact", "priority": 0.5 },
{ "path": "/blog", "priority": 0.7 }
],
"templates": {
"product": "/products/{slug}",
"blog": "/blog/{slug}",
"portfolio": "/portfolios/{slug}"
}
}
```
- `path` must start with `/`. Do not include checkout, login, or customer-dashboard URLs.
- Each `templates` value must include `{slug}` and match how this site routes detail pages.
- After deploy, the business owner syncs from **Website → Settings** in the dashboard.
- Dynamic URLs (products, blogs, portfolios) come from the CMS; only paths/templates are declared here.
If OpenAPI and this brief conflict, **OpenAPI wins**.
---
+78
View File
@@ -24,6 +24,9 @@
{
"name": "Tenant"
},
{
"name": "SEO"
},
{
"name": "Homepage"
},
@@ -186,6 +189,81 @@
}
}
},
"/tenants/{domain}/sitemap.xml": {
"get": {
"tags": [
"SEO"
],
"summary": "XML sitemap for search engines",
"description": "Published products, blogs, and portfolios for this tenant (respects enabled modules), plus static pages and URL templates from the synced website manifest (`GET https://{domain}/meshkee/sitemap-config.json`). Default paths when not synced: `/`, `/products/{slug}`, `/blog/{slug}`, `/portfolios/{slug}`. Proxied from `https://{domain}/sitemap.xml` on the storefront. Cached in Redis; refreshed after CMS writes and manifest sync.",
"parameters": [
{
"$ref": "#/components/parameters/domain"
}
],
"responses": {
"200": {
"description": "Sitemap XML (urlset)",
"content": {
"application/xml": {
"schema": {
"type": "string"
}
}
},
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"example": "public, max-age=900"
}
}
}
},
"404": {
"description": "Unknown domain"
}
}
}
},
"/tenants/{domain}/robots.txt": {
"get": {
"tags": [
"SEO"
],
"summary": "robots.txt with sitemap reference",
"description": "Plain-text robots file pointing crawlers to `https://{domain}/sitemap.xml`. Proxied from `https://{domain}/robots.txt` on the storefront.",
"parameters": [
{
"$ref": "#/components/parameters/domain"
}
],
"responses": {
"200": {
"description": "robots.txt",
"content": {
"text/plain": {
"schema": {
"type": "string",
"example": "User-agent: *\\nAllow: /\\n\\nSitemap: https://example.com/sitemap.xml\\n"
}
}
},
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"example": "public, max-age=900"
}
}
}
},
"404": {
"description": "Unknown domain"
}
}
}
},
"/tenants/{domain}/website/business-info": {
"get": {
"tags": [