Parent: #448
Context
공개 매거진 reader UI 가 없음 — 백엔드 read endpoint 는 이미 있음:
packages/api-server/src/domains/editorial_articles_published/handlers.rs:134 (/api/v1/editorial-articles/{id_or_slug})
- operation DB
editorial_articles snapshot 을 SELECT
남은 작업은 Next.js route + SSR + OG meta + sitemap 추가. Track 1/2/3 와 독립적으로 진행 가능.
Scope
web (신규)
web (이동/수정)
api-server
변경 없음.
재사용
MagazineRenderer 그대로 (이동만)
PublishedArticleDetail Rust struct — wire shape 동일
MagazineLayout TS 타입
DB migration
없음. editorial_articles_slug_idx 이미 존재.
slug 채움 보강 (별도 후속 PR): publish_to_operation 시 slug 가 NULL 로 들어감 (editorial_articles.rs:372). title → slugify 로직 추가 (Rust slug crate). 이번 issue 에서는 [slug] 가 UUID 도 받게 두고 enrichment 는 후속.
Sub-PR 분할
PR-3a-1: MagazineRenderer 이동 (refactor only)
admin 측 import 경로 변경. 동작 변경 X.
PR-3a-2: reader detail page
/magazine/[slug]/page.tsx + generateMetadata (OG meta) + not-found + fetch helper.
PR-3a-3: list + sitemap
/magazine 목록 + sitemap + 공유 헤더.
Test
- Manual gstack: dev 에 published article 만들고
localhost:3000/magazine/{uuid} 접근, OG meta 는 view-source 로 확인
- Unit:
generateMetadata 결과 (Vitest, mocked fetch)
- E2E: 별도 Playwright 셋업 확인 후 추가
Risk
MagazineRenderer 의 "use client" (MagazineRenderer.tsx:1) — Server Component 에서 import 가능하지만 hydration cost. VTON 버튼이 client-only 라 그대로 유지 (대안: VTON 분리 — 이번 trade-off 측면에서 유지)
- Cache invalidation on republish — 300s ISR 로 시작. 사용자 불만 시 webhook (
revalidatePath) 추가
- Slug uniqueness — slug enrichment 는 후속 PR. 충돌 시 suffix
Out of scope
- Slug enrichment (publish 시 title → slug 채움)
- View / share 카운트 트래킹
- Comment / like 기능
Parent: #448
Context
공개 매거진 reader UI 가 없음 — 백엔드 read endpoint 는 이미 있음:
packages/api-server/src/domains/editorial_articles_published/handlers.rs:134(/api/v1/editorial-articles/{id_or_slug})editorial_articlessnapshot 을 SELECT남은 작업은 Next.js route + SSR + OG meta + sitemap 추가. Track 1/2/3 와 독립적으로 진행 가능.
Scope
web (신규)
packages/web/app/magazine/[slug]/page.tsx— Server Component, Next.jsfetchw/cache: "force-cache"+revalidate: 300. slug 에 UUID 도 허용 (API 가 둘 다 받음)packages/web/app/magazine/[slug]/not-found.tsxpackages/web/app/magazine/page.tsx— 목록 페이지, published 매거진 grid + 페이지네이션 (/api/v1/editorial-articles?page활용)packages/web/lib/server/published-articles.ts— server-only fetch helperpackages/web/lib/hooks/usePublishedArticle.ts— public Tanstack Query hook (admin variant 와 별개)packages/web/app/sitemap.ts— 기존 sitemap 에 published articles 추가web (이동/수정)
packages/web/lib/components/admin/editorial/magazine/MagazineRenderer.tsx를packages/web/lib/components/magazine/MagazineRenderer.tsx로 이동 — admin/public 양쪽이 동일 컴포넌트 importpackages/web/app/admin/editorial/magazine/drafts/[id]/page.tsx:23— import 경로 수정api-server
변경 없음.
재사용
MagazineRenderer그대로 (이동만)PublishedArticleDetailRust struct — wire shape 동일MagazineLayoutTS 타입DB migration
없음.
editorial_articles_slug_idx이미 존재.slug 채움 보강 (별도 후속 PR):
publish_to_operation시 slug 가 NULL 로 들어감 (editorial_articles.rs:372). title → slugify 로직 추가 (Rustslugcrate). 이번 issue 에서는[slug]가 UUID 도 받게 두고 enrichment 는 후속.Sub-PR 분할
PR-3a-1: MagazineRenderer 이동 (refactor only)
admin 측 import 경로 변경. 동작 변경 X.
PR-3a-2: reader detail page
/magazine/[slug]/page.tsx+generateMetadata(OG meta) + not-found + fetch helper.PR-3a-3: list + sitemap
/magazine목록 + sitemap + 공유 헤더.Test
localhost:3000/magazine/{uuid}접근, OG meta 는 view-source 로 확인generateMetadata결과 (Vitest, mocked fetch)Risk
MagazineRenderer의"use client"(MagazineRenderer.tsx:1) — Server Component 에서 import 가능하지만 hydration cost. VTON 버튼이 client-only 라 그대로 유지 (대안: VTON 분리 — 이번 trade-off 측면에서 유지)revalidatePath) 추가Out of scope