Cách tôi xây dựng Portfolio của mình với Next.js, Contentlayer và Git Submodules

Table of Contents
Xây dựng một portfolio cá nhân cho nhà phát triển là một trong những dự án kỹ thuật đáng giá nhất mà bạn có thể thực hiện. Đó là "hộp cát" kỹ thuật của bạn: nơi để thử nghiệm các tiêu chuẩn web tiên tiến nhất, chia sẻ các hướng dẫn kỹ thuật chuyên sâu và xây dựng một sự hiện diện kỹ thuật số hiệu suất cao mà bạn hoàn toàn làm chủ.
Khi kiến trúc hóa locionic.com, tôi đã thiết lập ba ràng buộc kỹ thuật nghiêm ngặt:
- Không Cơ sở dữ liệu & Không Headless CMS: Không tốn phí hàng tháng cho cơ sở dữ liệu SaaS, không gặp sự cố ngừng hoạt động API bên ngoài và không bị khóa nhà cung cấp.
- Nội dung dưới dạng Mã: Mọi bài viết kỹ thuật, đoạn mã và hướng dẫn phải tồn tại dưới dạng Markdown/MDX thuần túy trong hệ thống kiểm soát phiên bản Git.
- Hiệu suất toàn cầu dưới một giây: Đạt 100/100 Core Web Vitals hoàn hảo, HTML tĩnh được tiền kết xuất và không có sự dịch chuyển bố cục phía máy khách.
Trong hướng dẫn này, tôi sẽ phân tích kiến trúc sản xuất chính xác đằng sau locionic.com—từ việc cô lập nội dung bằng Git submodule và lược đồ xây dựng Contentlayer đến tối ưu hóa tải trọng React Server Component (RSC).
1. Kiến trúc Nội dung dưới dạng Mã: Cô lập bằng Git Submodule
Hầu hết các nhà phát triển xây dựng blog đều lưu trữ các tệp markdown của họ trực tiếp trong kho lưu trữ ứng dụng chính hoặc kết nối với một CMS headless bên ngoài như Sanity hoặc Strapi. Cả hai phương pháp đều có những nhược điểm đáng chú ý:
- Kho lưu trữ ứng dụng nguyên khối: Khi blog của bạn mở rộng lên hơn 200 bài viết với hình ảnh và bản dịch địa phương hóa (
en,vi,ja), lịch sử git của kho mã sẽ phình to với hàng nghìn commit nội dung. - Headless CMS: Yêu cầu các cuộc gọi mạng liên tục trong quá trình xây dựng, các đường ống đồng bộ webhook phức tạp và các gói đăng ký hàng tháng.
Giải pháp Submodule
Tôi đã tách codebase thành hai kho lưu trữ riêng biệt:
- Vỏ ứng dụng (
locionic/blog-and-projects): Chứa các tuyến Next.js, kiểu dáng Tailwind, các thành phần React và các script xây dựng. - Lõi nội dung (
locionic/projectsđược gắn tạicontents/): Chỉ chứa các tệp.mdxthuần túy, các hướng dẫn kỹ thuật, các bảng cheat tương tác và các bản dịch địa phương hóa.
/home/developer/blog-and-projects/
├── app/ # Next.js 14 App Router (RSC Layouts, API routes)
├── components/ # UI components, MDX custom widgets
├── contents/ # Git Submodule pointing to locionic/projects
│ ├── blog_dev/ # 200+ Production Technical Guides (.mdx)
│ ├── cheatsheets/ # Interactive CLI & Git Reference Guides
│ └── courses/ # Multi-lesson curriculum files
└── package.json
Kiến trúc tách rời này cho phép tôi viết, chỉnh sửa và xuất bản các hướng dẫn kỹ thuật từ bất kỳ trình soạn thảo Markdown hoặc ứng dụng git di động nào mà không cần kích hoạt việc xây dựng lại ứng dụng hoặc chạm vào mã React.
2. Xử lý nội dung an toàn kiểu với Contentlayer
Để biến các tệp .mdx thô thành các đối tượng TypeScript được định kiểu mạnh mẽ trong quá trình xây dựng, trang web sử dụng Contentlayer:
// contentlayer.config.ts
import { defineDocumentType, makeSource } from 'contentlayer/source-files';
import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypePrismPlus from 'rehype-prism-plus';
export const Blog = defineDocumentType(() => ({
name: 'Blog',
filePathPattern: 'blog_dev/**/*.mdx',
contentType: 'mdx',
fields: {
title: { type: 'string', required: true },
date: { type: 'date', required: true },
summary: { type: 'string', required: true },
tags: { type: 'list', of: { type: 'string' }, default: [] },
draft: { type: 'boolean', default: false },
images: { type: 'list', of: { type: 'string' }, default: [] },
},
computedFields: {
slug: {
type: 'string',
resolve: (doc) => doc._raw.flattenedPath.replace(/^blog_dev\//, '').replace(/\/[^/]+$/, ''),
},
readingTime: {
type: 'json',
resolve: (doc) => readingTime(doc.body.raw),
},
},
}));
Contentlayer đọc mọi tệp .mdx, phân tích YAML frontmatter, xác thực kiểu và tạo các tài liệu JSON tĩnh trong .contentlayer/generated. Nếu tôi commit một bài viết thiếu summary hoặc date bị định dạng sai, quá trình xây dựng sẽ thất bại ngay lập tức trong CI với số dòng chính xác.
3. Bẫy tuần tự hóa RSC: Giảm 2MB trọng lượng trang ẩn
Một trong những lỗi hiệu suất nghiêm trọng nhất được phát hiện trong quá trình phân tích hồ sơ sản xuất liên quan đến tuần tự hóa tải trọng React Server Components (RSC).
Trong các bản dựng ban đầu, bố cục bài đăng blog của chúng tôi hiển thị một widget "Bài viết liên quan" ở cuối mỗi bài viết:
// ❌ DANGEROUS: Leaking 2MB of MDX body code into every page's RSC payload
import { allBlogs } from 'contentlayer/generated';
export default function BlogPostPage({ params }) {
const post = allBlogs.find((p) => p.slug === params.slug);
// Passing full Contentlayer objects to a Client Component!
return <PostLayout post={post} allPosts={allBlogs} />;
}
Vì allBlogs chứa mã JavaScript đã biên dịch thô (body.code) cho tất cả hơn 200 bài viết, Next.js đã tuần tự hóa toàn bộ danh mục 200 bài viết vào tải trọng JSON __NEXT_DATA__ / RSC của mỗi bài đăng blog. Một trang bài viết đơn giản đã tải xuống 2.3 MB JSON ẩn khi tải ban đầu!
Cách khắc phục: Loại bỏ phía máy chủ với coreContent()
Chúng tôi đã tái cấu trúc lớp dữ liệu để loại bỏ tất cả các trường thời gian xây dựng trước khi tuần tự hóa các thuộc tính qua ranh giới máy chủ-máy khách:
// ✅ OPTIMAL: Only ship lightweight metadata to client components
export function coreContent<T extends { body: unknown; _raw: unknown }>(content: T) {
const { body, _raw, _id, type, ...rest } = content;
return rest;
}
// Pass only 3 slim related post summaries
const relatedPosts = getRelatedPosts(post, allBlogs, 3).map(coreContent);
Tối ưu hóa duy nhất này đã giảm trọng lượng trang ban đầu từ 2.3 MB xuống còn 82 KB—giảm 96% băng thông giúp tăng vọt điểm Lighthouse trên thiết bị di động lên 99.
4. Liên kết nội bộ theo thuật toán: Vòng Hamiltonian 10 cụm
Để tránh các hình phạt "orphan" của công cụ tìm kiếm và tối đa hóa quyền hạn theo chủ đề, trang web chạy một script lý thuyết đồ thị tự động (scripts/relink_cluster_mesh.py):
- Phân cụm ngữ nghĩa TF-IDF: Phân loại tất cả 219 bài viết thành 10 cụm kỹ thuật riêng biệt (
nextjs_react,ai_llm_rag,devops_cloud,systems_wasm). - Vòng Hamiltonian 4-Chord: Tạo một vòng khép kín liên tục trong mỗi cụm, kết nối mỗi bài viết với 4 hàng xóm ngữ nghĩa gần nhất của nó.
- Đảm bảo cấu trúc liên kết: Đảm bảo rằng mỗi bài viết có in-degree và out-degree ít nhất là 4, duy trì chính xác 0 bài viết mồ côi trên toàn bộ miền.
Tóm tắt kiến trúc kỹ thuật
| Lớp kiến trúc | Công nghệ được chọn | Lý do kỹ thuật chính |
|---|---|---|
| Framework | Next.js 14 App Router | Static Site Generation (SSG) + Server Components |
| Styling | Tailwind CSS + Typography | Không có chi phí CSS-in-JS runtime |
| Content Engine | Contentlayer 0.3.4 | Xác thực kiểu thời gian xây dựng cho MDX |
| Search Engine | FlexSearch / Statically Built | Tìm kiếm phía máy khách 100% không tốn chi phí Algolia |
| Hosting & CDN | Vercel Edge Network | Bộ nhớ đệm HTTP/3 toàn cầu và kết xuất hình ảnh Edge OG |
| Analytics & Privacy | Google Analytics + CookieConsent | Cờ chấp thuận cookie tuân thủ GDPR |
Các câu hỏi thường gặp
Tìm kiếm phía máy khách hoạt động như thế nào mà không có cơ sở dữ liệu?
Trong bước hậu xây dựng, scripts/generate-search.mjs biên dịch một tệp public/search.json đã được rút gọn chứa tiêu đề, tóm tắt và thẻ của tất cả các bài viết đã xuất bản. Khi người dùng nhấn Cmd+K, modal tìm kiếm tải chỉ mục này vào bộ nhớ và tìm kiếm ngay lập tức với độ trễ dưới mili giây.
Hình ảnh xem trước OpenGraph trên mạng xã hội được tạo ra như thế nào?
Chúng tôi sử dụng tuyến Next.js Edge /api/og/[locale]/[slug]. Sử dụng @vercel/og (được hỗ trợ bởi Satori), máy chủ tạo ra đồ họa PNG động, độ phân giải cao 1200x630 chứa tiêu đề bài đăng, tên tác giả và thương hiệu một cách nhanh chóng, với bộ nhớ đệm cạnh bất biến 1 năm mạnh mẽ.
Bạn xử lý việc tô sáng cú pháp mã như thế nào?
Các khối mã trong MDX được tiền kết xuất tại thời điểm xây dựng bằng cách sử dụng rehype-prism-plus với các chủ đề CSS Prism tùy chỉnh. Điều này có nghĩa là không có JavaScript tô sáng cú pháp nào được gửi đến trình duyệt; các token cú pháp được định dạng sẵn thành các phần tử <span> HTML gốc.
Bạn cũng có thể thích
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

Lỗi Hydration trong Next.js: Cách sửa "Text Content Mismatch" & Error 418 (2026)
Hướng dẫn sửa triệt để lỗi Hydration failed (#418), Text content does not match server-rendered HTML, lỗi dark mode flash next-themes và localStorage trong Next.js.
Read more
Next.js App Router với Prisma: Thiết lập & Kết nối Pooling
Hướng dẫn Next.js + Prisma đầy đủ cho App Router, giúp ngăn chặn cạn kiệt pool kết nối toàn cục, truy vấn an toàn kiểu, đột biến server action và seed script.
Read more
suppressHydrationWarning trong Next.js: Hướng dẫn sử dụng an toàn & gỡ lỗi đầy đủ
Hướng dẫn toàn diện về suppressHydrationWarning trong Next.js: sử dụng an toàn & gỡ lỗi đầy đủ với các ví dụ thực tế đã được kiểm chứng.
Read more