Align storefront content URLs with live slug-based routes.
Use /blog/{slug}, /portfolio/{slug}, and /instruction/{slug} in sitemap defaults and emit CMS slugs for blogs/portfolios instead of title-derived paths.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
4823cf97f6
commit
9a020d748d
@@ -275,9 +275,12 @@ Minimal example:
|
|||||||
- Product: `/products/{id}/{nameFaSlug}` — build `nameFaSlug` from `nameFa` (fallback `title`); resolve page via `GET /tenants/{domain}/products/by-id/{id}` (slug segment is SEO-only; redirect to canonical if it drifts).
|
- Product: `/products/{id}/{nameFaSlug}` — build `nameFaSlug` from `nameFa` (fallback `title`); resolve page via `GET /tenants/{domain}/products/by-id/{id}` (slug segment is SEO-only; redirect to canonical if it drifts).
|
||||||
- Product category: `/products/category/{categoryId}/{nameFaSlug}` — build slug from `nameFa` (fallback `name`); resolve via `GET /tenants/{domain}/categories/by-id/{id}`, then list products with `categoryId`.
|
- Product category: `/products/category/{categoryId}/{nameFaSlug}` — build slug from `nameFa` (fallback `name`); resolve via `GET /tenants/{domain}/categories/by-id/{id}`, then list products with `categoryId`.
|
||||||
- User product (customer listing): `/user-products/{slug}` — use the listing’s `slug` from the API; resolve via `GET /tenants/{domain}/user-products/{slug}`.
|
- User product (customer listing): `/user-products/{slug}` — use the listing’s `slug` from the API; resolve via `GET /tenants/{domain}/user-products/{slug}`.
|
||||||
- Blog: `/blog/{id}/{titleSlug}` — `GET /tenants/{domain}/blogs/by-id/{id}`
|
- Blog: `/blog/{slug}` — use the blog’s `slug` from the API; resolve via `GET /tenants/{domain}/blogs/{slug}` (or list + match). Prefer slug routes over id.
|
||||||
- Portfolio: `/portfolios/{id}/{titleFaSlug}` — `GET /tenants/{domain}/portfolios/by-id/{id}`
|
- Portfolio: `/portfolio/{slug}` — use the portfolio’s `slug` from the API; resolve via `GET /tenants/{domain}/portfolios/{slug}`.
|
||||||
- When linking from lists/cards, use the same `{id}/{slug}` shape for details (slugify Farsi title: spaces → `-`, keep Persian letters). Category links use `/products/category/{id}/{nameFaSlug}`. User-product cards use `/user-products/{slug}`.
|
- Instruction: `/instruction/{slug}` — use the instruction’s `slug` from the API; resolve via `GET /tenants/{domain}/instructions/{slug}`.
|
||||||
|
- Video: `/videos/{slug}` — use the video’s `slug` from the API.
|
||||||
|
- Workshop: `/workshops/{slug}` — use the workshop’s `slug` from the API.
|
||||||
|
- When linking from lists/cards, use the same slug-based detail paths. Category links use `/products/category/{id}/{nameFaSlug}`. User-product cards use `/user-products/{slug}`.
|
||||||
- Omit `templates` in `sitemap-config.json` unless this site uses non-default paths.
|
- Omit `templates` in `sitemap-config.json` unless this site uses non-default paths.
|
||||||
- **Sitemap files (proxied by nginx — do not ship local copies):**
|
- **Sitemap files (proxied by nginx — do not ship local copies):**
|
||||||
- `/sitemap.xml` — sitemap **index** (lists child sitemaps for enabled modules)
|
- `/sitemap.xml` — sitemap **index** (lists child sitemaps for enabled modules)
|
||||||
|
|||||||
@@ -957,7 +957,7 @@
|
|||||||
"name": "Get published portfolio by id (website)",
|
"name": "Get published portfolio by id (website)",
|
||||||
"request": {
|
"request": {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"description": "Preferred for /portfolios/{id}/{titleFaSlug} pages. Includes nullable `projectUrl`.",
|
"description": "Preferred for /portfolio/{slug} pages. Includes nullable `projectUrl`.",
|
||||||
"url": "{{baseUrl}}/tenants/{{domain}}/portfolios/by-id/{{portfolioId}}"
|
"url": "{{baseUrl}}/tenants/{{domain}}/portfolios/by-id/{{portfolioId}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1606,7 +1606,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"Blogs"
|
"Blogs"
|
||||||
],
|
],
|
||||||
"summary": "Blog by id (preferred for /blog/{id}/{titleSlug} pages)",
|
"summary": "Blog by id (legacy; prefer slug routes `/blog/{slug}`)",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"$ref": "#/components/parameters/domain"
|
"$ref": "#/components/parameters/domain"
|
||||||
@@ -1959,7 +1959,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"Portfolios"
|
"Portfolios"
|
||||||
],
|
],
|
||||||
"summary": "Portfolio by id (preferred for /portfolios/{id}/{titleFaSlug} pages)",
|
"summary": "Portfolio by id (legacy; prefer slug routes `/portfolio/{slug}`)",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"$ref": "#/components/parameters/domain"
|
"$ref": "#/components/parameters/domain"
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ export const SITEMAP_URLSET_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9';
|
|||||||
export const DEFAULT_SITEMAP_PATH_TEMPLATES = {
|
export const DEFAULT_SITEMAP_PATH_TEMPLATES = {
|
||||||
product: '/products/{id}/{slug}',
|
product: '/products/{id}/{slug}',
|
||||||
productCategory: '/products/category/{id}/{slug}',
|
productCategory: '/products/category/{id}/{slug}',
|
||||||
blog: '/blog/{id}/{slug}',
|
blog: '/blog/{slug}',
|
||||||
portfolio: '/portfolios/{id}/{slug}',
|
portfolio: '/portfolio/{slug}',
|
||||||
video: '/videos/{slug}',
|
video: '/videos/{slug}',
|
||||||
instruction: '/instructions/{slug}',
|
instruction: '/instruction/{slug}',
|
||||||
workshop: '/workshops/{slug}',
|
workshop: '/workshops/{slug}',
|
||||||
userProduct: '/user-products/{slug}',
|
userProduct: '/user-products/{slug}',
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ export class SitemapService {
|
|||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
title: true,
|
slug: true,
|
||||||
updated_at: true,
|
updated_at: true,
|
||||||
},
|
},
|
||||||
orderBy: [{ updated_at: 'desc' }, { id: 'desc' }],
|
orderBy: [{ updated_at: 'desc' }, { id: 'desc' }],
|
||||||
@@ -353,7 +353,7 @@ export class SitemapService {
|
|||||||
return rows.map((row) =>
|
return rows.map((row) =>
|
||||||
this.toEntry(baseUrl, template, {
|
this.toEntry(baseUrl, template, {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
slug: slugifyForUrl(row.title, 'blog'),
|
slug: row.slug,
|
||||||
updatedAt: row.updated_at,
|
updatedAt: row.updated_at,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -371,8 +371,7 @@ export class SitemapService {
|
|||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
title: true,
|
slug: true,
|
||||||
title_fa: true,
|
|
||||||
updated_at: true,
|
updated_at: true,
|
||||||
},
|
},
|
||||||
orderBy: [{ updated_at: 'desc' }, { id: 'desc' }],
|
orderBy: [{ updated_at: 'desc' }, { id: 'desc' }],
|
||||||
@@ -381,7 +380,7 @@ export class SitemapService {
|
|||||||
return rows.map((row) =>
|
return rows.map((row) =>
|
||||||
this.toEntry(baseUrl, template, {
|
this.toEntry(baseUrl, template, {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
slug: slugifyForUrl(row.title_fa || row.title, 'portfolio'),
|
slug: row.slug,
|
||||||
updatedAt: row.updated_at,
|
updatedAt: row.updated_at,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -275,9 +275,12 @@ Minimal example:
|
|||||||
- Product: `/products/{id}/{nameFaSlug}` — build `nameFaSlug` from `nameFa` (fallback `title`); resolve page via `GET /tenants/{domain}/products/by-id/{id}` (slug segment is SEO-only; redirect to canonical if it drifts).
|
- Product: `/products/{id}/{nameFaSlug}` — build `nameFaSlug` from `nameFa` (fallback `title`); resolve page via `GET /tenants/{domain}/products/by-id/{id}` (slug segment is SEO-only; redirect to canonical if it drifts).
|
||||||
- Product category: `/products/category/{categoryId}/{nameFaSlug}` — build slug from `nameFa` (fallback `name`); resolve via `GET /tenants/{domain}/categories/by-id/{id}`, then list products with `categoryId`.
|
- Product category: `/products/category/{categoryId}/{nameFaSlug}` — build slug from `nameFa` (fallback `name`); resolve via `GET /tenants/{domain}/categories/by-id/{id}`, then list products with `categoryId`.
|
||||||
- User product (customer listing): `/user-products/{slug}` — use the listing’s `slug` from the API; resolve via `GET /tenants/{domain}/user-products/{slug}`.
|
- User product (customer listing): `/user-products/{slug}` — use the listing’s `slug` from the API; resolve via `GET /tenants/{domain}/user-products/{slug}`.
|
||||||
- Blog: `/blog/{id}/{titleSlug}` — `GET /tenants/{domain}/blogs/by-id/{id}`
|
- Blog: `/blog/{slug}` — use the blog’s `slug` from the API; resolve via `GET /tenants/{domain}/blogs/{slug}` (or list + match). Prefer slug routes over id.
|
||||||
- Portfolio: `/portfolios/{id}/{titleFaSlug}` — `GET /tenants/{domain}/portfolios/by-id/{id}`
|
- Portfolio: `/portfolio/{slug}` — use the portfolio’s `slug` from the API; resolve via `GET /tenants/{domain}/portfolios/{slug}`.
|
||||||
- When linking from lists/cards, use the same `{id}/{slug}` shape for details (slugify Farsi title: spaces → `-`, keep Persian letters). Category links use `/products/category/{id}/{nameFaSlug}`. User-product cards use `/user-products/{slug}`.
|
- Instruction: `/instruction/{slug}` — use the instruction’s `slug` from the API; resolve via `GET /tenants/{domain}/instructions/{slug}`.
|
||||||
|
- Video: `/videos/{slug}` — use the video’s `slug` from the API.
|
||||||
|
- Workshop: `/workshops/{slug}` — use the workshop’s `slug` from the API.
|
||||||
|
- When linking from lists/cards, use the same slug-based detail paths. Category links use `/products/category/{id}/{nameFaSlug}`. User-product cards use `/user-products/{slug}`.
|
||||||
- Omit `templates` in `sitemap-config.json` unless this site uses non-default paths.
|
- Omit `templates` in `sitemap-config.json` unless this site uses non-default paths.
|
||||||
- **Sitemap files (proxied by nginx — do not ship local copies):**
|
- **Sitemap files (proxied by nginx — do not ship local copies):**
|
||||||
- `/sitemap.xml` — sitemap **index** (lists child sitemaps for enabled modules)
|
- `/sitemap.xml` — sitemap **index** (lists child sitemaps for enabled modules)
|
||||||
|
|||||||
@@ -957,7 +957,7 @@
|
|||||||
"name": "Get published portfolio by id (website)",
|
"name": "Get published portfolio by id (website)",
|
||||||
"request": {
|
"request": {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"description": "Preferred for /portfolios/{id}/{titleFaSlug} pages. Includes nullable `projectUrl`.",
|
"description": "Preferred for /portfolio/{slug} pages. Includes nullable `projectUrl`.",
|
||||||
"url": "{{baseUrl}}/tenants/{{domain}}/portfolios/by-id/{{portfolioId}}"
|
"url": "{{baseUrl}}/tenants/{{domain}}/portfolios/by-id/{{portfolioId}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1606,7 +1606,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"Blogs"
|
"Blogs"
|
||||||
],
|
],
|
||||||
"summary": "Blog by id (preferred for /blog/{id}/{titleSlug} pages)",
|
"summary": "Blog by id (legacy; prefer slug routes `/blog/{slug}`)",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"$ref": "#/components/parameters/domain"
|
"$ref": "#/components/parameters/domain"
|
||||||
@@ -1959,7 +1959,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"Portfolios"
|
"Portfolios"
|
||||||
],
|
],
|
||||||
"summary": "Portfolio by id (preferred for /portfolios/{id}/{titleFaSlug} pages)",
|
"summary": "Portfolio by id (legacy; prefer slug routes `/portfolio/{slug}`)",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"$ref": "#/components/parameters/domain"
|
"$ref": "#/components/parameters/domain"
|
||||||
|
|||||||
Reference in New Issue
Block a user