60 lines
3.1 KiB
Markdown
60 lines
3.1 KiB
Markdown
# Architect — F-015 SEO core: structured data, sitemap, redirects
|
|
|
|
## Feature
|
|
F-015 adds SEO crawlability and structured data to the existing F-014 storefront catalog pages.
|
|
|
|
## Design
|
|
|
|
### Storefront SEO module
|
|
Create a small storefront SEO module under `project/storefront/src/lib/seo/`:
|
|
- `json-ld.ts` builds typed JSON-LD objects for Organization, BreadcrumbList and Product.
|
|
- `redirects.ts` defines a `RedirectRepository` interface and an env-backed implementation.
|
|
- Keep public URL generation centralized through existing `absoluteUrl()` / `SITE_URL`.
|
|
|
|
No new runtime dependency is required.
|
|
|
|
### Structured data
|
|
- Root layout embeds Organization JSON-LD once.
|
|
- Product pages embed:
|
|
- Product JSON-LD using product name, description, canonical URL and main image when available.
|
|
- BreadcrumbList JSON-LD for Home → Product.
|
|
- Category and brand pages embed BreadcrumbList JSON-LD.
|
|
- JSON-LD is rendered as `application/ld+json` with JSON serialization, not string concatenation.
|
|
|
|
### Sitemap and robots
|
|
Use Next.js metadata routes:
|
|
- `src/app/sitemap.ts` returns `MetadataRoute.Sitemap`.
|
|
- `src/app/robots.ts` returns `MetadataRoute.Robots`.
|
|
|
|
Sitemap source:
|
|
- Static public URLs: `/`, `/products/search`.
|
|
- Product URLs from `searchProducts({ limit: 100, offset })`, which returns active products only through backend `SearchProducts.activeOnly = true`; page through results to avoid silently truncating the first page.
|
|
- Category URLs from `listCategoryTree()` flattened recursively.
|
|
- Brand URLs from a new public `GET /brands` backend endpoint and storefront `listBrands()` client helper.
|
|
- On API failure, sitemap degrades to static URLs rather than failing the build.
|
|
|
|
### Redirect management
|
|
- Add `src/middleware.ts` using an env-backed redirect store.
|
|
- `RedirectRepository.findBySourcePath(pathname)` returns a redirect target when configured.
|
|
- Configure redirects with `REDIRECTS_JSON`, an array of `{ "from": "/old", "to": "/new" }` entries.
|
|
- Middleware returns permanent `301` redirects only for same-origin local paths. External targets are rejected by the parser to avoid open redirects.
|
|
- Redirect source and target paths must start with `/`; source cannot equal target.
|
|
|
|
### Backend brand listing
|
|
Extend the brands module minimally:
|
|
- Add `BrandRepository.list()`.
|
|
- Add `ListBrands` use case.
|
|
- Add public `GET /brands` returning `{ items: [...] }` in stable `name ASC, id ASC` order.
|
|
|
|
## Acceptance trace
|
|
- Product JSON-LD: product page embeds Product schema script.
|
|
- Sitemap: lists active product URLs via search API, category URLs, brand URLs and static pages; draft products excluded by backend active search.
|
|
- Redirect: middleware redirects stored old path with HTTP 301 to same-origin local target.
|
|
- Canonical URLs: existing F-014 canonical metadata remains on product/category/brand/search and root layout gains canonical metadata.
|
|
- `verify.sh`: must pass after gates.
|
|
|
|
## Risks / constraints
|
|
- No hreflang and no external SEO tooling by scope.
|
|
- Lighthouse/Google validator may be unavailable locally; validate structurally and through build output.
|
|
- Env redirects are enough for this slice; a database-backed admin UI is intentionally deferred.
|