React × Next.js – Trái tim UI Tử Vi hiện đại

Stack: Next 13 (App Router) · React 18 · Tailwind CSS · SWR · Chart.js
Backend: Golang API (gRPC + REST) · Postgres · Redis
Deploy: Vercel Edge Functions + AWS ECS (Go)

Trong dự án AstroCore (nền tảng Tử Vi đã giới thiệu ở bài trước), React/Next đóng vai trò “cửa sổ” giúp người dùng tương tác trực quan với những thuật toán Tử Vi phức tạp. Bài viết đào sâu mọi lớp:

  • Kiến trúc App Router & Server Components
  • Luồng dữ liệu real-time giữa Go API ↔ React
  • Hiển thị lá số, cung, sao bằng Chart.js & SVG
  • State Management “không Redux” với Zustand + SWR
  • SEO & i18n cho từ khóa “tu vi” (~1,2 triệu tìm kiếm/tháng)
  • Đo lường & tối ưu Web Vitals dưới 1 s LCP

1. Tại sao Next 13 App Router?

Yêu cầuTính năng App Router đáp ứng
SEO trang /cung/menh tĩnhStatic Generation (export const dynamic = 'force-static')
Hồ sơ lá số cá nhân (auth cookie)Server Components + cookies()
Bản dùng thử iframe trên blogEdge Runtime (export const runtime = 'edge')
Chặn crawl PDF premiumrobots.txt middleware

Server Actions cho phép gọi API Go trực tiếp trên máy chủ mà không lộ token:

'use server'
export async function fetchHoroscope(dob: string, gender: 'M'|'F') {
  const res = await fetch(`${process.env.API_URL}/horoscope`, {
    method: 'POST',
    body: JSON.stringify({ dob, gender })
  })
  return res.json()
}

2. Component hóa lá số Tử Vi

``` Horoscopes/ ├── ChartCanvas.tsx ├── PalaceGrid.tsx ├── StarTooltip.tsx └── HoroscopeCard.tsx ```

PalaceGrid.tsx (trích đoạn)

import { motion } from 'framer-motion'
export const PalaceGrid = ({ palaces }) => (
  <svg viewBox="0 0 300 300" className="w-full h-auto">
    {palaces.map((p,i)=>(
      <motion.g key={i} initial={{opacity:0,scale:.8}} animate={{opacity:1,scale:1}}>
        <rect x={p.x} y={p.y} width="90" height="90" rx="8"
              className="fill-slate-50 stroke-slate-300 dark:fill-slate-800"/>
        <text x={p.x+45} y={p.y+12} textAnchor="middle"
              className="font-semibold text-xs">{p.name}</text>
      </motion.g>
    ))}
  </svg>
)

3. SWR + Zustand gọn nhẹ

import useSWR from 'swr'
import { create } from 'zustand'

export const useUserStore = create(()=>({ dob:'', gender:'M' }))
export const useHoroscope = () =>{
  const { dob, gender } = useUserStore()
  return useSWR(dob ? ['horoscope', dob, gender] : null,
                ()=>fetchHoroscope(dob, gender),
                { suspense:true })
}

4. Chart.js vẽ đại vận

import { Line } from 'react-chartjs-2'
export const LuckCycle = ({ data })=>(
  <Line data={{ labels:data.years, datasets:[{ data:data.score, tension:.4 }]}}
        options={{ plugins:{ legend:{display:false}} }}/>
)

5. SEO & i18n

export const generateStaticParams = ()=>[{lang:'vi'},{lang:'en'}]
export const metadata = { title:{default:'Tử Vi – AstroCore'} }

6. Edge Function demo

export const runtime='edge'
export async function GET(req:Request){
  const url=new URL(req.url)
  const dob=url.searchParams.get('dob')!
  const data=await fetch(`${API}/horoscope?dob=${dob}`).then(r=>r.json())
  return Response.json(data,{headers:{'Cache-Control':'s-maxage=86400'}})
}

7. Kết quả Web Vitals

MetricCSRRSC
LCP2.8 s0.94 s
JS430 kB180 kB

8. Gotcha & mẹo

  • Chart.js SSR → import trong client component.
  • Mismatch SVG → wrap số động trong useEffect.

React + Next 13 đã giúp app Tử Vi đạt SEO, hiệu năng & UX vượt trội, đồng thời sẵn sàng scale đa ngôn ngữ và widget B2B.