Xây lại portfolio developer của tôi
Nhật ký rebuild rivernguyen.id.vn — Next.js App Router, routing song ngữ, case study NDA-safe, River Bot không API key, và các tradeoff Core Web Vitals thật.
Nguyễn Đình Giang
· · 5 phút đọc
Lập trình viên frontend ở Hà Nội. Mình viết về Next.js, WordPress và hiệu năng của những thứ mình thực sự ship.
Sau vài năm ship frontend cho khách, tôi mới dành thời gian rebuild lại site của mình từ đầu. Không phải đổi theme — mà là nghĩ lại cách recruiter, client và cả AI crawler hiểu mình là ai và mình đã ship gì.
Bài này là case study ngôi thứ nhất cho rivernguyen.id.vn: lựa chọn stack, routing song ngữ, case study ẩn danh, “River Bot” local, và những con số lab vẫn còn phải cải thiện.
Vì sao phải rebuild?
Portfolio cũ đủ dùng — tên, liên hệ, danh sách dự án phẳng. Nó không phản ánh cách tôi làm việc hôm nay: Next.js App Router, WordPress headless trên dự án khách, GSAP / Motion có chủ đích, và thiên hướng performance/SEO.
Tôi muốn một site:
- Load ổn và đo được trung thực bằng Lighthouse / CrUX sau này
- Hỗ trợ Anh + Việt mà không tự tạo duplicate content
- Publish bài MDX và case study từ data module
- Tinh chỉnh, không biến thành dashboard toàn card
Tiêu chí đơn giản: bỏ nav đi, viewport đầu vẫn phải đọc được là brand của tôi — không phải template “frontend developer” generic.
Điểm xuất phát: fork, rồi sở hữu brand
Codebase sớm kế thừa cấu trúc từ một portfolio open-source mạnh (registry component, MDX, layout screen-line). Cố ý — tôi muốn pattern App Router đã kiểm chứng, không phải SPA trống.
Phần rebuild là về ownership:
- Identity River (biến thể tên, timezone Hà Nội, badge affiliate, mascot)
- Case study tôn trọng NDA mà không trống trải
- Copy song ngữ dịch thật, không dump máy
- Tương tác đúng craft (mascot, duck, command menu) mà không chặn LCP
Stack giữ lại
- Next.js App Router — routing, metadata, SSG cho blog/case study
- Tailwind CSS + shadcn/ui — primitive nhất quán
- next-intl — prefix
as-needed(/EN,/vi/...tiếng Việt) - MDX — bài viết là file, không CMS cho site solo
- Upstash Redis (fallback JSON local) — visitors không cần DB riêng
- GSAP / Motion — motion đáng có; vẫn tốn CWV trên mobile nếu load sớm
Docker cho site này nằm ở Ship Next.js với Docker mà không lộ secrets.
Song ngữ mà không tự bắn vào chân
Tiếng Việt với tôi không phải “nice to have” — là một nửa audience. Khó không phải dịch string; khó là canonical + hreflang thống nhất.
Bài học quan trọng:
createPageMetadata()phải đưa locale request vàoalternates.canonicalvàopenGraph.url. Hardcode locale mặc định khiến/vi/*giống bản sao tiếng Anh.- Sitemap chỉ nên thêm URL
/visau khi canonical đã đúng. - Body đã dịch đủ mà title/description vẫn tiếng Anh — vẫn làm yếu SERP tiếng Việt.
Chi tiết routing: Xây site Next.js song ngữ với next-intl.
Case study dưới NDA
Hầu hết việc đã ship thuộc về khách. URL live và screenshot brand thường không được phép. Recruiter vẫn cần bằng chứng craft.
Thỏa hiệp trên site:
- Tiêu đề generic (loại dự án + domain), không tên khách
- UI mock ẩn danh kiểu wireframe thay vì ảnh đánh cắp
- Sơ đồ architecture, không map hệ thống có brand
- Metric lab (LCP / Performance) ghi rõ là lab, không bịa
- Notice dự án private + đường tới /contact
Yếu hơn case study public có URL live — và tôi nói thẳng trên trang. Trung thực hơn logo giả.
Xem cấu trúc tại /projects.
River Bot không API key
Ban đầu chat gắn AI SDK với OpenAI. Với portfolio cá nhân: key, rate limit, và failure mode trả 503.
Bản ship hiện tại là FAQ / intent bot local: chấm keyword trên projects, stack, experience, hire, contact — lấy từ cùng data module với UI. Stream text thuần, trả lời EN/VI, từ chối off-topic.
Tradeoff: không sáng tạo câu trả lời mới. Lợi: không vendor lock-in, không lộ key, bot đúng brand.
Cá tính: mascot, duck, pixel mode
Portfolio chỉ liệt kê skill dễ quên. Tôi thêm:
- Mascot theo cursor trên header profile
- Duck follower tùy chọn (desktop, tôn trọng reduced-motion)
- Konami mở pixel mode
- Phòng bí mật /pond nếu cứ chọc duck
Đó là tín hiệu brand — và cũng là chi phí JS/CSS. Trên lab mobile chúng cạnh tranh analytics/motion trên main thread. Tôi coi là progressive enhancement, không phải nội dung chính.
Con số lab (nói thẳng)
Audit SEO/performance domain này (Lighthouse lab, chưa cấu hình CrUX) báo homepage mobile khoảng 44/100, LCP ~5.1s, TBT cao — chủ yếu do analytics SDK nặng và chunk motion trên critical path. Desktop ổn (~90).
Câu khó đăng trên site của chính mình. Đó cũng là ý của nhật ký rebuild: đo rồi cắt.
Hướng remediation (đang làm):
- Defer / tree-shake module analytics không dùng
- Code-split motion khỏi first paint
- Bớt preload font trang trí
- Avatar đúng kích thước hiển thị, không 400×400 cho box ~120px
CWV sâu hơn: Tối ưu Next.js cho Core Web Vitals.
Recruiter mode
Không phải ai cũng muốn scroll hết. /hire (và /?mode=hire) là snapshot một màn: availability, stack, ba case study, CTA contact/resume. Command menu nhảy tới nhanh.
Làm lại thì tôi sẽ khác
- Ship visual proof sớm hơn — mock ẩn danh nên đi cùng case study đầu, không đợi audit gọi “projects chỉ chữ.”
- Coi locale metadata là feature, không phải helper viết sau.
- Budget JS cho personality như budget ảnh — mascot vui thật; TBT cũng thật.
- Ít bài nhưng sâu thay vì stub mỏng không rank / không được cite.
Tiếp theo
Site này là lab sống: đào sâu vài bài kỹ thuật, giữ case study trung thực với NDA, và hạ CWV mobile đến khi có field data (CrUX) thay lab-only.
Nếu bạn đang rebuild portfolio, bắt đầu bằng một case study sắc và canonical đúng — không phải hero animation nữa. Hoặc liên hệ nếu muốn trao đổi rebuild frontend Next.js / WordPress.
Tham chiếu: Next.js App Router, next-intl, Tailwind CSS, web.dev vitals.