YAN Portfolio là trang portfolio cá nhân của Nguyễn Đặng Trường An (Yami An), xây bằng React 18 + Vite. Toàn bộ nội dung tập trung trong src/portfolio.js và các file JSON snapshot dưới src/shared/opensource/; component chỉ nhận dữ liệu qua props và render lại — muốn đổi nội dung thì sửa data, không sửa component.
- Portfolio trực tuyến: https://yamiannephilim.com/
- Hồ sơ/CV: Google Drive
- Liên hệ: GitHub · LinkedIn · Facebook · X/Twitter · Gmail
- React 18 +
react-dom18 (createRoot), react-router-dom 6 vớiHashRouter(route dạng/#/...). - Vite 7 (
@vitejs/plugin-react) làm build tool/dev server, thay cho Create React App; một plugin nội bộ (jsAsJsxtrongvite.config.js) cho phép JSX tồn tại trong file.jsđể không phải đổi tên hàng loạt. styled-components6 cho theme (ThemeProvider,GlobalStyles);styletron-react+styletron-engine-atomic+baseuicho riêng accordion ở trang Experience.react-awesome-reveal+@emotion/reactcho animation cuộn trang (thay choreact-reveal, không còn tương thích React 18).react-bootstrap2 +bootstrap5 cho tooltip/overlay.react-helmet-async(HelmetProvider) cho meta tag SEO và JSON-LD Person.chart.js4 +react-chartjs-25 cho biểu đồ đóng góp open-source.Vitest+@testing-library/react+jsdomcho test; ESLint cấu hình độc lập (.eslintrc.cjs, không còn dùng presetreact-app).- Font Awesome vendor sẵn trong
src/assests/font-awesome; icon/ảnh kỹ năng nằm trongpublic/skills.
Toàn bộ component đã chuyển thành function component (không còn class component).
index.html # Entry HTML của Vite (nằm ở repo root, không phải public/)
vite.config.js # Cấu hình Vite: base "./", outDir "build", plugin jsAsJsx, Vitest
src/
index.js # Entry point: Styletron + BaseUI + HelmetProvider, createRoot(...).render
App.js # Root app: ThemeProvider + GlobalStyles, truyền theme xuống Main
global.js # GlobalStyles dùng chung (box-sizing, màu nền/chữ theo theme)
portfolio.js # Nguồn dữ liệu nội dung chính của portfolio
theme.js # 14 bảng theme màu, chosenTheme chọn theme đang dùng
containers/Main.js # HashRouter + khai báo route, chọn landing theo settings.isSplash
components/ # Header, card, chart, social, footer, pageLayout... (đều là function component)
components/pageLayout/ # PageLayout dùng chung cho các trang route (Header + nội dung + TopButton)
containers/ # Section tái sử dụng cho Home/Open Source (greeting, skills, charts...)
pages/ # Màn hình route-level (home, education, experience, projects, opensource, contact, splash, errors)
shared/opensource/*.json # Snapshot organizations, pull_requests, issues, projects
assests/ # images, fonts, font-awesome vendor (tên cố ý sai chính tả, không đổi thành "assets")
src/assests/font-awesome là vendor/minified asset, bị loại khỏi ESLint (ignorePatterns trong .eslintrc.cjs) — không chỉnh tay hay thêm comment nội bộ. Các file source tự viết mở đầu bằng comment tóm tắt tiếng Việt (// Tóm tắt: hoặc khối /* ... */); CSS/SCSS tự viết cũng có header mô tả vai trò stylesheet.
src/index.jstạo Styletron engine, bọcStyletronProvider→BaseProvider(BaseUI) →HelmetProvider, rồicreateRoot(...).render(<App />).AppbọcThemeProvider(styled-components) +GlobalStyles, truyềnchosenThemexuốngMain.Main(containers/Main.js) dùngHashRouter+Routes(react-router-dom 6); landing route chọn theosettings.isSplash(đang tắt —"/"render thẳng Home); còn lại là route cho Home, Education, Experience, Projects, Open Source, Contact vàError404bắt mọi path không khớp.- Các trang dùng
PageLayout(Header + nội dung +TopButton) và lấy dữ liệu từportfolio.jshoặc snapshot trongsrc/shared/opensource. SeoHeadersinh meta tag và JSON-LD Person quareact-helmet-async.
- Thông tin cá nhân, social links, SEO, kỹ năng, chứng chỉ, kinh nghiệm, project header và contact nằm trong
src/portfolio.js. - Danh sách project trên trang Projects lấy từ
src/shared/opensource/projects.json. - Trang Open Source dùng
organizations.json,pull_requests.json,issues.jsonđể render logo tổ chức, chart và card đóng góp. Các file này là snapshot tĩnh, import lúc build — không gọi API lúc runtime. - Theme hiện tại là
blueThemetrong số 14 palette khai báo ởsrc/theme.js; đổichosenThemeđể reskin toàn bộ site. settings.isSplash = false: route/render thẳng Home. Đặttrueđể dùng màn splash làm landing.
Mỗi skill trong portfolio.js chọn một trong hai chế độ hiển thị:
fontAwesomeClassname→ icon Iconify, nạp qua script CDN khai báo trongindex.html.imageSrc→ ảnh PNG runtime từpublic/skills/, nạp theo đường dẫn${import.meta.env.BASE_URL}skills/<tên-file>?v=${skillAssetVersion}(dùngimport.meta.env.BASE_URLcủa Vite, không cònprocess.env.PUBLIC_URLcủa CRA).
nginx phục vụ /skills/ với header no-cache, nên mỗi khi thêm/đổi PNG skill phải bump hằng số skillAssetVersion trong SoftwareSkill.js để bust cache.
npm install
npm run dev # Vite dev server, mặc định cổng 3000
npm run lint # ESLint trên src/**/*.js (loại trừ vendor font-awesome)
npm test # vitest run — chạy test một lần
npm run test:watch # vitest ở chế độ watch
npm run build # vite build -> thư mục build/
npm run preview # Preview bản build production cục bộ
npm run deploy # predeploy chạy build, sau đó gh-pages publish build/ lên branch gh-pagesChỉ có src/App.test.js (smoke test render App), nên npm test xanh không đồng nghĩa với coverage đầy đủ.
Không còn npm start / react-scripts — dự án đã rời Create React App, dùng Vite làm build tool.
Asset nhị phân (svg, png, jpg, gif, woff, woff2, ttf, eot, ico) được theo dõi bằng Git LFS theo khai báo trong .gitattributes. Sau khi clone repo, chạy:
git lfs install
git lfs pullBuild Docker chạy scripts/verify-lfs-assets.js và fail nếu còn asset nào là LFS pointer chưa resolve.
Dockerfilebuild nhiều stage: stagebuilddùngnode:22-alpine, cài dependency bằngnpm ci, verify LFS rồinpm run build; stage runtime dùngnginx:1.27-alpinecopybuild/vào/usr/share/nginx/htmlvà serve trên cổng80theonginx.conf(SPA fallbacktry_files ... /index.html,/skills/setno-cache, các asset tĩnh khác cache 7 ngày).Jenkinsfile: build Docker image → push lên Docker Hub (yamiannephilim/portfolio) → dừng/xóa container cũ → chạy container mới trên networkyan, kèm thông báo Telegram ở mỗi bước.- Ngoài Docker,
npm run deploypublishbuild/lên branchgh-pagesđể host qua GitHub Pages.
- ESLint bỏ qua
src/assests/font-awesome(vendor/minified bundle) — cấu hình trong.eslintignore/ignorePatternscủa.eslintrc.cjs. base: "./"+build.outDir: "build"trongvite.config.jslà phần thực sự điều khiển đường dẫn asset tương đối và thư mục output (Vite không đọc fieldhomepagecủa CRA).package.jsonvẫn còn"homepage": "."như một field thừa từ thời CRA — không dựa vào nó, và nếu dọn thì phải kiểm tra cả hai chỗ nhất quán.overridestrongpackage.jsonghim version một số dependency transitive để vá lỗ hổng bảo mật — không gỡ khi không cần thiết.src/serviceWorker.js(từ thời CRA, luôn ở trạng thái unregister) đã bị xóa hẳn khi migrate sang Vite — không thêm lại service worker vì sẽ phá vỡ chiến lược no-cache/deploy hiện tại.