Parent Epic: #536
Depends on: #537, #538, #539
Objective
Expose published blog content safely through public routes, RSS, sitemap, and SEO metadata rendering.
Context
This issue makes blog_content usable on the public website while enforcing strict public visibility rules. Public routes must never leak draft, review, scheduled-future, archived, private, unlisted, or soft-deleted content.
Scope
- Public blog index
- Public post detail page
- Public category archive
- Public tag archive
- Public search
- RSS feed
- Blog sitemap
- SEO metadata rendering
- Canonical URL validation
- Safe content rendering
Out of Scope
- Admin UI
- Revision restore UI
- Template/widget/menu/ads advanced rendering unless implemented in a later issue
- Comment system
- Public user registration
Routes
GET /blog
GET /blog/[slug]
GET /blog/category/[slug]
GET /blog/tag/[slug]
GET /blog/search?q=
GET /feed.xml
GET /sitemap-blog.xml
Public Visibility Rule
All public queries must centralize and reuse this predicate:
status = 'published'
AND visibility = 'public'
AND deleted_at IS NULL
AND published_at IS NOT NULL
AND published_at <= now()
Additional listing/search/feed/sitemap rule:
SEO Requirements
- Render
seo_title when available.
- Fall back to
title when seo_title is empty.
- Render
meta_description when available.
- Fall back to
excerpt or safe generated summary when meta_description is empty.
- Render canonical URL only when valid and safe.
- Do not render unsafe URLs.
RSS Requirements
- RSS must include only published public content.
- RSS must exclude unlisted, private, archived, scheduled-future, draft, review, and deleted content.
- RSS must respect tenant context and blog settings.
Sitemap Requirements
- Sitemap must include only published public content.
- Sitemap must exclude unlisted, private, archived, scheduled-future, draft, review, and deleted content.
- Sitemap must respect tenant context and blog settings.
Content Safety Requirements
- Use structured JSON content as the source of truth.
- Rendering must sanitize or safely render content.
- Script tags must be rejected or stripped.
- Inline JavaScript must be rejected or stripped.
- Dangerous iframe/embed content must be rejected or stripped.
- Unsafe URLs must be rejected.
- Error output must not expose stack traces.
Acceptance Criteria
Validation Commands
bun run test
bun run typecheck
bun run build
Parent Epic: #536
Depends on: #537, #538, #539
Objective
Expose published blog content safely through public routes, RSS, sitemap, and SEO metadata rendering.
Context
This issue makes
blog_contentusable on the public website while enforcing strict public visibility rules. Public routes must never leak draft, review, scheduled-future, archived, private, unlisted, or soft-deleted content.Scope
Out of Scope
Routes
Public Visibility Rule
All public queries must centralize and reuse this predicate:
Additional listing/search/feed/sitemap rule:
SEO Requirements
seo_titlewhen available.titlewhenseo_titleis empty.meta_descriptionwhen available.excerptor safe generated summary whenmeta_descriptionis empty.RSS Requirements
Sitemap Requirements
Content Safety Requirements
Acceptance Criteria
bun run buildpasses.Validation Commands
bun run test bun run typecheck bun run build