Architecture
Site này (portfolio + blog cá nhân) được build bằng Next.js trên Vercel, dữ liệu và backend logic nằm hoàn toàn ở Supabase Cloud. Trang này giải thích site chạy như thế nào — dành cho ai tò mò về kiến trúc.
Sơ đồ
Browser
Vercel — Next.js (SSR / ISR / Server Actions)
Postgres
(RLS)
(RLS)
Edge Functions
(Deno)
(Deno)
Auth
Storage
Ngoài luồng chính: GitHub Actions build/deploy + backup DB hằng tuần · Sentry nhận lỗi runtime · Umami nhận pageview.
Stack
| Frontend | Next.js (App Router) + TypeScript strict |
| Styling | Tailwind CSS v4 + shadcn/ui |
| Content | Supabase Postgres (bài viết lưu dạng Markdown/MDX text) |
| Render MDX | next-mdx-remote (RSC) + rehype-pretty-code (Shiki) |
| Backend API | Supabase Edge Functions (Deno + TypeScript) |
| Auth | Supabase Auth (magic link, 1 admin user) |
| Storage | Supabase Storage (ảnh bài viết, resume PDF) |
| Observability | Sentry (error tracking) + Umami (analytics) |
| Deploy FE | Vercel (Git-connected, ISR) |
| Deploy BE | supabase functions deploy (qua GitHub Actions) |
Luồng dữ liệu chính
Đọc bài viết (public)
- 1Next.js SSG lúc build + revalidate mỗi 1h (ISR)
- 2Server Component đọc thẳng Postgres qua anon key (client không cookie, không qua Edge Function)
- 3RLS chỉ cho đọc bài đã publish
View counter / reaction
- 1Client gọi Edge Function increment-view / react
- 2Edge Function tự rate-limit (IP + salt, không lưu IP thật) rồi ghi Postgres bằng service role
- 3Lỗi ở bước này không làm sập trang — chỉ ẩn số đếm
Đăng / sửa bài viết (admin)
- 1proxy.ts chặn /admin/* bằng RPC is_admin() trên session hiện tại
- 2Server Action ghi thẳng Postgres qua client có cookie session (RLS kiểm tra quyền admin)
- 3revalidatePath() để trang public cập nhật ngay, không đợi hết giờ ISR
Tìm kiếm
- 1Client debounce 300ms rồi gọi Edge Function search
- 2Edge Function query full-text search trên Postgres, chỉ trong bài đã publish
Toàn bộ source code (frontend, Edge Functions, migrations, CI/CD) đều public trên GitHub.