# Website SSR Migration Plan > **Historical/reference-tenant plan:** This plan records the HomAroma website migration scope at the time it was written. Revalidate routes, tenant configuration, and completion status before applying it to another tenant. ## 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: ```text GET ${API_BASE}/pages/home?lang= ``` 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 `/:slug` without 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.tsx` and `apps/website/src/app/**`. - Classify routes as `exists-needs-upgrade`, `missing`, `client-only-ok`, or `seo-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_BAR` - `HERO` - `CATEGORIES` - `PRODUCTS` - `PRODUCT_CAROUSEL` - `BANNER` - `TEXT` - `NEWSLETTER` - Split SSR-capable sections from client-only interactive sections. - Keep payload guards local to `apps/website` if 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 `any` use unless isolated and documented as a temporary contract gap. ## Phase 4: SEO Route Migration Owner: `frontend-lead` Review: `frontend-code-auditor` Migration order: 1. `/` 2. `/products/[id]` 3. `/shop` 4. `/category/[id]` 5. `/article/[slug]` 6. `/search` Tasks: - Convert SEO-critical content to server-rendered data where possible. - Keep interactive controls as client islands. - Add or preserve `generateMetadata` for 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-auditor` - `qa-product-auditor` - `design-system-auditor` Required checks: - `npm run build -w apps/website` - `npm 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. ```text 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. ```text 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. ```text 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. ```text 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/website` support 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/spa` remain 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/summer` be supported, and if yes, what slug policy should admin/backend enforce to avoid collisions with business routes? ## Decisions ### Image Optimization Decision: defer migration from `` 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.ts` does not yet define final `images.remotePatterns` or production CDN/image host policy. - Changing to `next/image` before 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 `` usage for now. - Treat `@next/next/no-img-element` warnings 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.ts` has explicit `images.remotePatterns` for 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/website` build/lint pass. - `frontend-code-auditor` review 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.tsx` with reserved slug collision protection. - Website CMS renderer now supports SSR-safe editorial, article hero, scent notes, quote, FAQ accordion, and contact info blocks. - `CONTACT_FORM` is intentionally deferred until a real submission endpoint/API owner is ready. Do not implement simulated submit behavior. - `/track` now renders the tracking form, calls `POST /orders/track`, stores validated local order history, and consumes `track-order-layout` CMS sections. - `/order/[id]` now exists as a read-only SSR order status page with item summary, status, tracking number, metadata, and `order-success-layout` CMS sections. - `/wishlist` now exists with a website-local persisted wishlist store compatible with SPA `homaroma-wishlist`, product-card heart toggles, navbar counters, and `wishlist-layout` CMS sections. - `/profile` now exists with SPA-compatible `homaroma-auth-storage`, member dashboard, order history, address CRUD, contact settings, navbar profile entry, and `profile-layout` CMS sections. - `/checkout` now 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]`, and `checkout-layout` CMS sections. Next implementation target: - Run manual commerce QA for `/shop -> cart -> /checkout -> /order/[id] -> /track`. - Confirm website LIFF initialization/auth bootstrap before treating `/profile` as 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 /orders` contract 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/home` internally. - `/products/[id]` now uses website-local data helpers and avoids duplicate product fetch. - `/shop`, `/category/[id]`, and `/search` now render product/category content on the server instead of client-only fetch. - `/shop` preserves coupon handoff with a client-only localStorage bridge while keeping product content server-rendered. - `/search` guards repeated `q` query 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`. - `/shop` now consumes `shop-layout` CMS sections for announcement, hero/banner, and bottom content around the SSR product grid. - `/category/[id]` now consumes `category-layout` CMS sections for announcement, hero/banner, and bottom content around the SSR category product grid. - `/products/[id]` now consumes `product-detail-layout` CMS sections for announcement and bottom content around the SSR product detail. - `/search` now consumes `search-layout` CMS sections for announcement, hero/banner, and bottom content around SSR search results. - `/scent-guide` now consumes `scent-guide-layout` CMS sections for announcement, hero/banner, and bottom content around the article index. - `/scent-guide` now exposes article API failure separately from a legitimate empty article list. - `/coupons` now consumes `coupons-layout` CMS sections for announcement, hero/banner, and bottom content around the SSR coupon grid. - `/coupons` now exposes coupon API failure separately from a legitimate empty coupon list. - `apps/website` CMS parity next target is CMS catch-all custom page support and remaining SPA-only block renderer parity. - `apps/website` lint passes with warnings only. - `apps/website` production build passes. Remaining M1 follow-up: - Final manual browser smoke test of upgraded website routes. - Keep remaining `` usage for M1 and schedule `next/image` as a later optimization pass after image hosting is finalized. - Continue manual visual QA for Navbar, Footer, CartDrawer, and content pages across desktop/mobile breakpoints.