14 KiB
Website SSR Migration Plan
Goal
Upgrade apps/website into the Next.js SSR/SEO version of the current apps/spa experience.
apps/spa is the UX and behavior reference. apps/website is the only application implementation target for this migration.
Scope
Allowed edit scope:
apps/website/**- planning or handoff documents such as this file
Do not edit without separate approval:
apps/spa/**apps/backend/**apps/admin/**packages/types/**
If a correct fix requires backend, schema, shared types, admin CMS, or SPA changes, stop and report it as a blocker instead of using a workaround.
Source Of Truth
- SPA route map:
apps/spa/src/App.tsx - SPA home CMS usage:
apps/spa/src/pages/HomePage.tsx - SPA dynamic renderer:
apps/spa/src/components/DynamicPageRenderer.tsx - Website API resolver:
apps/website/src/lib/api.ts - Website App Router pages:
apps/website/src/app/** - Backend CMS API contract, read-only reference only:
apps/backend/src/routes/pages.ts - Shared CMS payload schemas, read-only reference only:
packages/types/index.ts
Important Clarification
The SPA does not expose a frontend route named /pages/home.
The SPA route / renders HomePage, and HomePage calls the backend CMS API endpoint:
GET ${API_BASE}/pages/home?lang=<current-language>
For apps/website, this means Next pages should keep public SEO routes such as /, /shop, /category/[id], and /products/[id], while using the same CMS API endpoint internally when needed.
Migration Strategy
Do not copy the SPA wholesale into Next.
Use this pattern instead:
- Server components fetch SEO-critical data and render meaningful initial HTML.
- Client components handle cart, wishlist, LIFF, forms, filters, payment, and localStorage state.
- CMS section rendering is adapted into website-local components.
- Published single-segment CMS pages can be rendered through
/:slugwithout replacing route-specific commerce pages. - API/environment logic stays website-local and uses
NEXT_PUBLIC_API_URL/INTERNAL_API_URL.
Phase 1: Website Foundation Audit
Owner: frontend-code-auditor review only
Tasks:
- Build a route parity matrix between
apps/spa/src/App.tsxandapps/website/src/app/**. - Classify routes as
exists-needs-upgrade,missing,client-only-ok, orseo-critical. - Identify legacy classes, missing design tokens, hardcoded URLs, and silent API fallbacks in
apps/website. - Identify pages where current client-only fetch weakens SEO.
Deliverables:
- Route parity matrix.
- Website-only risk list.
- Recommended migration order.
Phase 2: Website Data Layer
Owner: frontend-lead
Review: frontend-code-auditor
Tasks:
- Create or refine website-local fetch helpers under
apps/website/src/lib. - Separate server-safe fetch helpers from client-only fetch helpers.
- Ensure all helpers use
apps/website/src/lib/api.ts. - Add consistent handling for API failures without hiding important contract issues.
- Avoid importing runtime API constants from
@homaroma/types.
Acceptance criteria:
- No edits outside allowed scope.
- API helpers work in Next server and client contexts as intended.
- No silent empty fallback on SEO-critical pages without an explicit fallback reason.
Phase 3: CMS Dynamic Renderer For Website
Owner: frontend-lead
Review: frontend-code-auditor
Tasks:
- Create website-local dynamic page renderer under
apps/website/src/components. - Adapt these section types first:
ANNOUNCEMENT_BARHEROCATEGORIESPRODUCTSPRODUCT_CAROUSELBANNERTEXTNEWSLETTER
- Split SSR-capable sections from client-only interactive sections.
- Keep payload guards local to
apps/websiteif shared types are insufficient.
Acceptance criteria:
- Home page can render CMS sections from
/pages/home. - Unknown section types fail visibly in development logs, but do not crash production rendering.
- No broad
anyuse unless isolated and documented as a temporary contract gap.
Phase 4: SEO Route Migration
Owner: frontend-lead
Review: frontend-code-auditor
Migration order:
//products/[id]/shop/category/[id]/article/[slug]/search
Tasks:
- Convert SEO-critical content to server-rendered data where possible.
- Keep interactive controls as client islands.
- Add or preserve
generateMetadatafor pages that can provide SEO data. - Generate fallback metadata from available API data if CMS metadata is unavailable.
Acceptance criteria:
- Initial HTML contains meaningful product/category/article/page content.
- Cart, wishlist, filters, and add-to-cart remain interactive.
- Build and lint pass for
apps/website.
Phase 5: Commerce And LINE OA Client Features
Owner: frontend-lead
Review: frontend-code-auditor
Flow QA: qa-product-auditor
Routes:
/checkout/order/[id]/track/wishlist/profile
Tasks:
- Adapt SPA behavior into website-local client components.
- Add website-local auth, wishlist, and LIFF integration if needed.
- Keep SEO shell/server rendering where useful, but treat these routes as client-heavy flows.
- Use existing backend API endpoints without changing backend contracts.
Acceptance criteria:
- Checkout, order success, tracking, wishlist, and profile flows work on desktop and mobile.
- LINE OA/LIFF logic only runs on the client.
- Payment/order/upload failures have visible error states.
Phase 6: Visual And UX Alignment
Owner: ux-ui-designer + frontend-lead
Review: design-system-auditor
Tasks:
- Align website visual language with SPA source of truth.
- Remove or replace legacy website-only classes that are not defined.
- Align navbar, footer, cart drawer, product card, loading states, empty states, and error states.
- Check mobile layout and common shopping flows.
Acceptance criteria:
- Website no longer mixes incompatible legacy dark/cyan styling with SPA visual language.
- UI remains readable and responsive.
- Design token usage is consistent within
apps/website.
Phase 7: Verification And Release Gate
Owner: orchestrator
Reviewers:
frontend-code-auditorqa-product-auditordesign-system-auditor
Required checks:
npm run build -w apps/websitenpm run lint -w apps/website- Manual route smoke test for SEO routes.
- Manual commerce flow test for checkout/order/tracking.
- Verify no files outside allowed scope were edited.
- Verify all blockers requiring backend/schema/shared type changes are reported, not worked around.
Delegation Prompts
Frontend Code Auditor
Use for Phase 1 and review gates.
Review only. Do not edit files.
Inspect apps/website against apps/spa as reference.
Focus on route parity, SSR SEO risks, type/API contract risks, hardcoded values, missing states, and anti-patterns.
Allowed output: findings, severity, file paths, recommended fix, escalation needed.
Frontend Lead
Use for implementation phases.
Implement only inside apps/website.
Use apps/spa as UX and behavior reference, but do not edit apps/spa.
Do not edit apps/backend, apps/admin, or packages/types.
If the correct fix requires those areas, stop and report a blocker.
Preserve Next.js SSR for SEO-critical content and use client islands for interactive behavior.
QA Product Auditor
Use after route groups are implemented.
Review user flows only. Do not edit files unless explicitly asked.
Check broken flows, empty states, error states, mobile behavior, checkout/order/tracking/profile/wishlist flows, and release readiness.
Design System Auditor
Use after visual alignment.
Review visual consistency only. Do not edit files unless explicitly asked.
Check contrast, readability, missing classes, token usage, responsive layout, and SPA/website visual alignment.
Open Decisions
- Should
apps/websitesupport locale-specific URL paths later, or keep language state client-side for now? - Should dynamic CMS SEO metadata be generated only from existing API data in this migration?
- Should
apps/sparemain the dedicated LINE OA app after website reaches parity, or should website eventually replace it? - Should shared type/runtime cleanup be scheduled as a separate project after website-only migration?
- Should nested CMS paths such as
/campaign/summerbe supported, and if yes, what slug policy should admin/backend enforce to avoid collisions with business routes?
Decisions
Image Optimization
Decision: defer migration from <img> to next/image for M1.
Reason:
- Current image sources are mixed between local public paths such as
/images/...and backend/CMS upload paths such as/uploads/.... apps/website/next.config.tsdoes not yet define finalimages.remotePatternsor production CDN/image host policy.- Changing to
next/imagebefore image hosting is finalized can create broken remote image rendering or force premature config assumptions. - M1 priority is SSR/SEO route parity, API correctness, visual cleanup, and build stability.
Current policy:
- Keep existing
<img>usage for now. - Treat
@next/next/no-img-elementwarnings as accepted M1 optimization debt, not a release blocker. - Revisit after production image domain/CDN and upload URL strategy are finalized.
Future acceptance criteria for next/image migration:
- Production image host or CDN is known.
apps/website/next.config.tshas explicitimages.remotePatternsfor allowed hosts.- Hero, product primary image, category hero, article cover, and CMS hero/banner images are migrated first.
- Lint still passes and images render in dev, preview, and production-like environments.
Initial Milestone
M1: Website SEO Foundation
Target:
- Phase 1 complete.
- Phase 2 complete.
- Basic CMS renderer exists in
apps/website. /and/products/[id]upgraded first.apps/websitebuild/lint pass.frontend-code-auditorreview passed.
Current status:
- Phase 1 route parity audit documented in
docs/website-route-parity-audit.md. - Website-local data helpers added under
apps/website/src/lib. - Website-local result helpers now expose API errors instead of converting every failure into an empty list.
- CMS layout parity is now wired for
/,/shop,/category/[id],/products/[id],/search,/scent-guide, and/coupons. - Published single-segment CMS pages now render through
apps/website/src/app/[slug]/page.tsxwith reserved slug collision protection. - Website CMS renderer now supports SSR-safe editorial, article hero, scent notes, quote, FAQ accordion, and contact info blocks.
CONTACT_FORMis intentionally deferred until a real submission endpoint/API owner is ready. Do not implement simulated submit behavior./tracknow renders the tracking form, callsPOST /orders/track, stores validated local order history, and consumestrack-order-layoutCMS sections./order/[id]now exists as a read-only SSR order status page with item summary, status, tracking number, metadata, andorder-success-layoutCMS sections./wishlistnow exists with a website-local persisted wishlist store compatible with SPAhomaroma-wishlist, product-card heart toggles, navbar counters, andwishlist-layoutCMS sections./profilenow exists with SPA-compatiblehomaroma-auth-storage, member dashboard, order history, address CRUD, contact settings, navbar profile entry, andprofile-layoutCMS sections./checkoutnow exists with a website-local client checkout, SPA-compatible cart/auth storage,GET /settings,GET /promotions/active,POST /coupons/validate, saved address support, points redemption,POST /orders, local order history, cart clearing, redirect to/order/[id], andcheckout-layoutCMS sections.
Next implementation target:
- Run manual commerce QA for
/shop -> cart -> /checkout -> /order/[id] -> /track. - Confirm website LIFF initialization/auth bootstrap before treating
/profileas fully production-ready. - Extend
/order/[id]payment/slip upload only after explicitly planning the payment side effects and QA gate. - Escalate backend server-side pricing validation before treating checkout/payment as production-grade, because the current
POST /orderscontract trusts client totals, discounts, points, and shipping values. - Basic website CMS renderer added under
apps/website/src/components/dynamic. /now renders CMS sections from/pages/homeinternally./products/[id]now uses website-local data helpers and avoids duplicate product fetch./shop,/category/[id], and/searchnow render product/category content on the server instead of client-only fetch./shoppreserves coupon handoff with a client-only localStorage bridge while keeping product content server-rendered./searchguards repeatedqquery params before trimming.- Navbar, Footer, CartDrawer, and legacy content pages now use the website
pop-*visual language instead of undefined dark/cyan/glass classes. - CMS parity planning added in
docs/website-cms-parity-plan.md. /shopnow consumesshop-layoutCMS sections for announcement, hero/banner, and bottom content around the SSR product grid./category/[id]now consumescategory-layoutCMS sections for announcement, hero/banner, and bottom content around the SSR category product grid./products/[id]now consumesproduct-detail-layoutCMS sections for announcement and bottom content around the SSR product detail./searchnow consumessearch-layoutCMS sections for announcement, hero/banner, and bottom content around SSR search results./scent-guidenow consumesscent-guide-layoutCMS sections for announcement, hero/banner, and bottom content around the article index./scent-guidenow exposes article API failure separately from a legitimate empty article list./couponsnow consumescoupons-layoutCMS sections for announcement, hero/banner, and bottom content around the SSR coupon grid./couponsnow exposes coupon API failure separately from a legitimate empty coupon list.apps/websiteCMS parity next target is CMS catch-all custom page support and remaining SPA-only block renderer parity.apps/websitelint passes with warnings only.apps/websiteproduction build passes.
Remaining M1 follow-up:
- Final manual browser smoke test of upgraded website routes.
- Keep remaining
<img>usage for M1 and schedulenext/imageas a later optimization pass after image hosting is finalized. - Continue manual visual QA for Navbar, Footer, CartDrawer, and content pages across desktop/mobile breakpoints.