# 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.