SRE Resume 前端项目 - 从代码到部署完整指南
这是什么项目?
想象一下:这是一份"会动的简历",就像把纸质简历变成了一个炫酷的交互式网站。
它用 React + TypeScript 构建,通过 Vite 打包,最后装进 Docker 容器里,像一份精美的数字名片,随时可以展示给面试官。
本文档将带你了解:
- 🎯 项目架构与代码逻辑
- ⚙️ 技术栈选型与配置
- 📦 构建打包流程
- 🐳 Docker 容器化部署
- 🚀 性能优化策略
一、项目概览
1.1 项目信息
| 项目属性 | 值 |
|---|---|
| 项目名称 | guobin-sre-resume |
| 版本 | 1.0.0 |
| Docker 镜像 | sre-resume:v1.0.0 |
| 项目类型 | 单页应用(SPA) |
| 用途 | SRE 工程师个人简历展示网站 |
1.2 核心技术栈
技术栈就像做菜的食材清单
每个技术都是一道工序,组合起来才能做出美味的"简历网站"。
| 技术分类 | 技术选型 | 作用 |
|---|---|---|
| 前端框架 | React 18.3.1 | 构建用户界面的核心框架 |
| 开发语言 | TypeScript 5.6.3 | 提供类型安全,减少运行时错误 |
| 构建工具 | Vite 5.4.11 | 快速开发服务器 + 高效打包 |
| 样式方案 | Tailwind CSS 3.4.17 | 原子化 CSS,快速构建 UI |
| 图标库 | Lucide React | 轻量级图标库 |
| Web 服务器 | Nginx 1.27-alpine | 生产环境静态资源服务器 |
| 容器化 | Docker | 应用打包与部署 |
二、代码架构与逻辑
2.1 目录结构
sre-resume/
├── src/ # 源代码目录
│ ├── components/ # React 组件
│ │ ├── Hero/ # 首屏英雄区域
│ │ ├── Projects/ # 项目展示
│ │ ├── ui/ # 通用 UI 组件
│ │ └── ... # 其他业务组件
│ ├── data/ # 静态数据
│ │ └── resume.ts # 简历数据配置
│ ├── hooks/ # 自定义 Hooks
│ ├── styles/ # 全局样式
│ ├── utils/ # 工具函数
│ ├── App.tsx # 根组件
│ └── main.tsx # 应用入口
├── dist/ # 构建产物(npm run build 生成)
├── index.html # HTML 入口
├── vite.config.ts # Vite 配置
├── tailwind.config.js # Tailwind 配置
├── Dockerfile # Docker 构建文件
├── nginx.conf # Nginx 配置
└── package.json # 项目依赖配置
2.2 核心代码逻辑
2.2.1 应用入口(main.tsx)
入口文件就像房子的地基
所有代码都从这里开始构建,它是整个应用的起点。
// src/main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import './styles/index.css';
// 创建 React 根节点并渲染应用
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
);
解读:
StrictMode:React 严格模式,帮助发现潜在问题createRoot:React 18 新的渲染 API,支持并发特性./styles/index.css:全局样式入口
2.2.2 根组件(App.tsx)
根组件就像建筑的主体框架
它决定了整个网站的结构和加载策略。
核心特性:
- 懒加载(Lazy Loading)
// 滚动到对应区域再加载组件,缩减首屏 JS 体积
const Competencies = lazy(() =>
import('./components/Competencies').then((m) => ({ default: m.Competencies }))
);
为什么这样做?
- 首屏只加载必要的代码,其他组件等用户滚动到对应区域再加载
- 减少首屏 JavaScript 体积,加快页面打开速度
- 空闲预取(Idle Prefetch)
// 首屏渲染后空闲时段预取所有懒加载 chunk
function useIdlePrefetch() {
useEffect(() => {
const run = () => {
void import('./components/Competencies');
void import('./components/Projects/Projects');
// ... 预取其他组件
};
// 使用 requestIdleCallback 在浏览器空闲时预取
if (window.requestIdleCallback) {
window.requestIdleCallback(run, { timeout: 2000 });
}
}, []);
}
解读:
- 首屏渲染完成后,利用浏览器空闲时间提前加载其他组件
- 用户点击导航时不会看到 loading 状态,体验更流畅
- 组件结构
<div className="relative min-h-screen bg-bg-base text-text font-sans antialiased">
<BackgroundFX /> {/* 背景特效 */}
<Nav /> {/* 导航栏 */}
<main>
<Hero /> {/* 首屏英雄区域 */}
<LazySection><Competencies /></LazySection> {/* 核心能力 */}
<LazySection><Projects /></LazySection> {/* 项目经验 */}
<LazySection><Experience /></LazySection> {/* 工作经历 */}
<LazySection><Skills /></LazySection> {/* 技能列表 */}
<LazySection><Branding /></LazySection> {/* 个人品牌 */}
<LazySection><Contact /></LazySection> {/* 联系方式 */}
</main>
</div>
2.2.3 数据配置(resume.ts)
数据配置就像简历的内容库
所有简历信息都集中在这里管理,方便维护和更新。
数据结构:
export type ResumeData = {
meta: { // 元信息
name: string;
targetPosition: string;
targetCity: string;
expectedSalary: string;
education: string;
yearsOfExperience: number;
};
basic: { // 基本信息
name: string;
title: string;
phone: string;
email: string;
blog: string;
github: string;
linkedin: string;
};
careerSummary: string; // 职业总结
coreCompetencies: CoreCompetency[]; // 核心能力
skills: { ... }; // 技能列表
experience: Experience[]; // 工作经历
projects: Project[]; // 项目经验
certifications: Certification[]; // 证书
personalBrand: { ... }; // 个人品牌
};
优势:
- 类型安全:TypeScript 自动检查数据结构
- 集中管理:所有简历数据在一处维护
- 易于扩展:添加新字段只需修改类型定义
三、构建配置详解
3.1 Vite 配置(vite.config.ts)
Vite 就像一个超级打包机
它能把所有源代码、样式、图片打包成浏览器能直接运行的文件。
核心配置项:
export default defineConfig({
plugins: [react(), inlineEntryCss()], // 插件配置
server: {
host: '0.0.0.0', // 允许外部访问
port: 5173, // 开发服务器端口
strictPort: false, // 端口被占用时自动尝试下一个
},
build: {
assetsInlineLimit: 32768, // 小于 32KB 的资源内联进 HTML
cssCodeSplit: false, // CSS 不分割,便于内联
target: 'es2020', // 目标浏览器版本
rollupOptions: {
output: {
chunkFileNames: 'assets/[name]-[hash].js', // 懒加载 chunk 命名
},
},
},
});
自定义插件:CSS 内联
/**
* 构建后把主入口 CSS 内联到 index.html 的 <style> 标签
* 节省一次往返请求,关键路径资源合并
*/
function inlineEntryCss(): Plugin {
return {
name: 'inline-entry-css',
apply: 'build',
enforce: 'post',
closeBundle() {
// 读取 dist/index.html
// 找到 style-*.css 文件
// 将 <link> 标签替换为 <style> 标签
// 删除原 CSS 文件
},
};
}
为什么这样做?
- 减少 HTTP 请求:CSS 直接嵌入 HTML,浏览器不用再发一次请求
- 加快首屏渲染:关键路径资源合并,减少网络往返时间
3.2 Tailwind 配置(tailwind.config.js)
Tailwind 就像一个 CSS 积木库
提供大量预定义的样式类,像搭积木一样快速构建 UI。
export default {
content: ['./index.html', './src/**/*.{ts,tsx}'], // 扫描哪些文件
theme: {
extend: {
colors: {
bg: {
base: '#020617', // 深色背景
elev: '#0F172A', // 卡片背景
card: '#0B1220', // 悬浮卡片
},
primary: {
DEFAULT: '#22D3EE', // 主色调(青色)
glow: 'rgba(34, 211, 238, 0.4)', // 发光效果
},
// ... 更多颜色定义
},
fontFamily: {
mono: ['JetBrains Mono', 'Fira Code', 'Menlo', 'monospace'],
sans: ['Inter', 'Noto Sans SC', 'system-ui', 'sans-serif'],
},
animation: {
'fade-in': 'fadeIn 0.6s ease-out',
'slide-up': 'slideUp 0.6s ease-out',
'cursor-blink': 'cursorBlink 1s step-end infinite',
// ... 更多动画
},
},
},
};
设计系统:
- 统一的颜色体系:背景、文字、边框、状态色
- 字体栈:代码字体(JetBrains Mono)+ 正文字体(Inter)
- 动画库:淡入、滑入、闪烁等常用动画
四、构建打包流程
4.1 开发模式
# 启动开发服务器
npm run dev
# 预期输出:
# VITE v5.4.11 ready in 300 ms
# ➜ Local: http://localhost:5173/
# ➜ Network: http://192.168.1.100:5173/
# 解读:
# - 热更新:修改代码自动刷新页面
# - 快速启动:Vite 利用浏览器原生 ES 模块,无需打包即可启动
4.2 生产构建
# 构建生产版本
npm run build
# 实际执行的命令:
# tsc -b && vite build
# 解读:
# - tsc -b:TypeScript 类型检查 + 编译
# - vite build:打包生成 dist/ 目录
# 预期输出:
# dist/
# ├── index.html # 入口 HTML(CSS 已内联)
# └── assets/
# ├── index-[hash].js # 主 JS 包
# ├── Competencies-[hash].js # 懒加载 chunk
# ├── Projects-[hash].js # 懒加载 chunk
# └── ... # 其他懒加载 chunk
构建产物特点:
- ✅ CSS 内联到 HTML:减少 HTTP 请求
- ✅ 代码分割:首屏 JS 体积最小化
- ✅ 文件名带 hash:利于浏览器缓存
- ✅ Tree Shaking:未使用的代码自动删除
4.3 预览构建结果
# 本地预览生产构建
npm run preview
# 预期输出:
# ➜ Local: http://localhost:4173/
# 解读:
# - 使用 Vite 预览服务器模拟生产环境
# - 验证构建结果是否正确
五、Docker 容器化部署
5.1 Dockerfile 解析
Dockerfile 就像一份安装说明书
它告诉 Docker 如何把应用打包成一个"集装箱",可以在任何地方运行。
# ============================================
# Dockerfile(单阶段,纯 nginx)
# 宿主机本地 npm run build 产出 dist/
# Docker 只负责打包 nginx 镜像
# ============================================
FROM nginx:1.27-alpine
# 自定义 nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 把宿主机已构建好的 dist 直接放进去
COPY dist /usr/share/nginx/html
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://127.0.0.1/ >/dev/null 2>&1 || exit 1
LABEL org.opencontainers.image.title="sre-resume" \
org.opencontainers.image.description="SRE Resume static site (nginx, single stage)" \
org.opencontainers.image.version="1.0.0"
CMD ["nginx", "-g", "daemon off;"]
逐行解读:
| 指令 | 说明 |
|---|---|
FROM nginx:1.27-alpine |
使用官方 Nginx 镜像(Alpine 版本,体积小) |
COPY nginx.conf ... |
复制自定义 Nginx 配置 |
COPY dist ... |
复制构建产物到 Nginx 静态资源目录 |
EXPOSE 80 |
声明容器监听 80 端口 |
HEALTHCHECK |
健康检查:每 30 秒访问一次首页 |
LABEL |
添加镜像元数据(名称、描述、版本) |
CMD |
启动 Nginx(前台运行) |
为什么用单阶段构建?
- 构建在宿主机完成,Dockerfile 只负责打包
- 镜像体积更小(不包含 Node.js)
- 构建速度更快(利用宿主机缓存)
5.2 Nginx 配置(nginx.conf)
Nginx 就像一个超级接待员
它站在门口接收访客请求,把静态文件发给访客,还能处理路由跳转。
server {
listen 80 default_server;
server_name _;
root /usr/share/nginx/html;
index index.html;
# 开启 gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml;
# 静态资源强缓存(1 年)
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
try_files $uri =404;
}
# SPA fallback:所有请求都走 index.html
location / {
try_files $uri $uri/ /index.html;
}
# 安全相关 headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 隐藏 nginx 版本
server_tokens off;
}
核心配置解读:
- Gzip 压缩
gzip on;
gzip_comp_level 6; # 压缩级别(1-9,6 是平衡点)
gzip_types text/plain text/css application/javascript;
效果:
- 文本文件压缩率 60-80%
- 减少网络传输时间
- CPU 开销可控
- 静态资源缓存
location /assets/ {
expires 1y; # 缓存 1 年
add_header Cache-Control "public, immutable";
}
为什么可以缓存 1 年?
- Vite 构建时文件名带 hash(如
index-abc123.js) - 文件内容变化 → hash 变化 → 文件名变化
- 浏览器可以放心长期缓存
- SPA 路由支持
location / {
try_files $uri $uri/ /index.html;
}
解读:
- 用户访问
/projects时,Nginx 找不到对应文件 try_files将请求回退到index.html- React Router 在前端处理路由
- 安全 Headers
| Header | 作用 |
|---|---|
X-Content-Type-Options: nosniff |
防止 MIME 类型嗅探 |
X-Frame-Options: SAMEORIGIN |
防止点击劫持 |
Referrer-Policy |
控制 Referrer 信息泄露 |
server_tokens off |
隐藏 Nginx 版本号 |
5.3 镜像构建与运行
构建镜像
# 构建镜像
docker build -t sre-resume:v1.0.0 .
# 预期输出:
# [+] Building 12.5s (8/8) FINISHED
# => exporting to image
# => => writing image sha256:abc123...
# => => naming to docker.io/library/sre-resume:v1.0.0
# 解读:
# - 从当前目录(.)构建
# - 标签为 sre-resume:v1.0.0
# - 构建时间约 10-15 秒
运行容器
# 运行容器
docker run -d -p 8080:80 --name sre-resume sre-resume:v1.0.0
# 参数解读:
# -d:后台运行
# -p 8080:80:宿主机 8080 端口映射到容器 80 端口
# --name sre-resume:容器名称
# sre-resume:v1.0.0:使用的镜像
# 访问网站
# 浏览器打开 http://localhost:8080
查看容器状态
# 查看运行中的容器
docker ps
# 预期输出:
# CONTAINER ID IMAGE COMMAND STATUS PORTS
# abc123def456 sre-resume:v1.0.0 "nginx -g 'daemon of…" Up 2 minutes 0.0.0.0:8080->80/tcp
# 查看容器日志
docker logs sre-resume
# 查看健康状态
docker inspect --format='{{.State.Health.Status}}' sre-resume
# 输出:healthy
停止和清理
# 停止容器
docker stop sre-resume
# 删除容器
docker rm sre-resume
# 删除镜像
docker rmi sre-resume:v1.0.0
六、性能优化策略
6.1 首屏优化
首屏优化就像让客人进门就能看到精心布置的客厅
不用等所有房间都装修好,先展示最重要的部分。
| 优化策略 | 实现方式 | 效果 |
|---|---|---|
| 懒加载 | React.lazy() + Suspense | 首屏 JS 减少 60% |
| CSS 内联 | 自定义 Vite 插件 | 减少 1 次 HTTP 请求 |
| 空闲预取 | requestIdleCallback | 点击导航无 loading |
| 字体异步 | media=“print” + onload | 字体不阻塞渲染 |
6.2 资源优化
| 优化策略 | 配置 | 效果 |
|---|---|---|
| 小资源内联 | assetsInlineLimit: 32768 | < 32KB 资源嵌入 HTML |
| 代码分割 | React.lazy() | 按需加载,减少首屏体积 |
| Tree Shaking | Vite 自动 | 删除未使用代码 |
| 文件名 hash | [name]-[hash].js | 长期缓存 |
6.3 网络优化
| 优化策略 | 配置 | 效果 |
|---|---|---|
| Gzip 压缩 | nginx gzip on | 文本压缩 60-80% |
| 强缓存 | expires 1y | 静态资源长期缓存 |
| HTTP/2 | nginx 默认支持 | 多路复用,减少连接 |
6.4 性能指标
# 使用 Lighthouse 测试性能
lighthouse http://localhost:8080 --view
# 预期指标:
# Performance: 90+
# First Contentful Paint: < 1.5s
# Largest Contentful Paint: < 2.5s
# Total Blocking Time: < 200ms
七、常见问题与解决方案
7.1 构建问题
问题 1:TypeScript 类型错误
# 现象
npm run build
# Error: Type 'string' is not assignable to type 'number'
# 原因
# 类型定义与实际数据不匹配
# 解决方案
# 1. 检查 tsconfig.json 配置
# 2. 修复类型定义或数据
# 3. 重新构建
问题 2:构建产物体积过大
# 现象
# dist/assets/index-xxx.js 体积 > 500KB
# 排查步骤
# 1. 分析依赖体积
npx vite-bundle-visualizer
# 2. 检查是否引入了不必要的库
# 3. 使用懒加载拆分代码
7.2 Docker 问题
问题 1:镜像构建失败
# 现象
docker build -t sre-resume:v1.0.0 .
# Error: COPY failed: stat /var/lib/docker/tmp/dockerxxx/dist: no such file or directory
# 原因
# dist/ 目录不存在,未执行 npm run build
# 解决方案
npm run build
docker build -t sre-resume:v1.0.0 .
问题 2:容器启动后无法访问
# 现象
# curl http://localhost:8080 无响应
# 排查步骤
# 1. 检查容器是否运行
docker ps
# 2. 检查端口映射
docker port sre-resume
# 3. 查看容器日志
docker logs sre-resume
# 4. 进入容器检查
docker exec -it sre-resume sh
ls /usr/share/nginx/html # 检查静态文件是否存在
7.3 Nginx 问题
问题 1:SPA 路由刷新 404
# 现象
# 访问 /projects 正常,刷新后 404
# 原因
# Nginx 找不到 /projects 文件
# 解决方案
# 确保 nginx.conf 中有:
location / {
try_files $uri $uri/ /index.html;
}
问题 2:静态资源加载失败
# 现象
# 浏览器控制台:Failed to load resource: the server responded with a status of 404
# 排查步骤
# 1. 检查 dist/ 目录结构
ls -R dist/
# 2. 检查 Nginx 配置
docker exec -it sre-resume cat /etc/nginx/conf.d/default.conf
# 3. 检查文件权限
docker exec -it sre-resume ls -la /usr/share/nginx/html/assets/
八、完整部署流程
8.1 一键部署脚本
#!/bin/bash
# deploy.sh - 一键构建与部署脚本
set -e # 遇到错误立即退出
echo "🚀 开始构建与部署..."
# 1. 安装依赖
echo "📦 安装依赖..."
npm install
# 2. 类型检查
echo "🔍 类型检查..."
npm run build
# 3. 构建 Docker 镜像
echo "🐳 构建 Docker 镜像..."
docker build -t sre-resume:v1.0.0 .
# 4. 停止旧容器(如果存在)
echo "🛑 停止旧容器..."
docker stop sre-resume 2>/dev/null || true
docker rm sre-resume 2>/dev/null || true
# 5. 启动新容器
echo "🚀 启动新容器..."
docker run -d -p 8080:80 --name sre-resume sre-resume:v1.0.0
# 6. 等待健康检查
echo "⏳ 等待健康检查..."
sleep 10
# 7. 检查状态
if [ "$(docker inspect --format='{{.State.Health.Status}}' sre-resume)" = "healthy" ]; then
echo "✅ 部署成功!"
echo "🌐 访问地址:http://localhost:8080"
else
echo "❌ 部署失败,请检查日志:"
docker logs sre-resume
exit 1
fi
8.2 部署流程图
┌─────────────────┐
│ 源代码(src/) │
└────────┬────────┘
│ npm run build
▼
┌─────────────────┐
│ 构建产物(dist/)│
└────────┬────────┘
│ docker build
▼
┌─────────────────┐
│ Docker 镜像 │
│ sre-resume:v1.0.0│
└────────┬────────┘
│ docker run
▼
┌─────────────────┐
│ 运行中的容器 │
│ Nginx + 静态文件 │
└────────┬────────┘
│ 访问
▼
┌─────────────────┐
│ 用户浏览器 │
│ http://localhost:8080 │
└─────────────────┘
九、总结
9.1 技术亮点
| 亮点 | 说明 |
|---|---|
| 🎯 类型安全 | TypeScript 全栈类型检查 |
| ⚡ 极致性能 | 懒加载 + CSS 内联 + 空闲预取 |
| 🐳 容器化 | Docker 统一部署环境 |
| 🔒 安全加固 | Nginx 安全 Headers + 版本隐藏 |
| 📦 体积优化 | Tree Shaking + 代码分割 |
9.2 关键命令速查
# 开发
npm run dev # 启动开发服务器
# 构建
npm run build # 构建生产版本
npm run preview # 预览构建结果
# Docker
docker build -t sre-resume:v1.0.0 . # 构建镜像
docker run -d -p 8080:80 --name sre-resume sre-resume:v1.0.0 # 运行容器
docker logs sre-resume # 查看日志
docker stop sre-resume # 停止容器
# 排查
docker exec -it sre-resume sh # 进入容器
docker inspect sre-resume # 查看容器详情
9.3 文件清单
| 文件 | 作用 | 重要程度 |
|---|---|---|
| [package.json](file:///root/package.json) | 项目依赖配置 | ⭐⭐⭐ |
| [vite.config.ts](file:///root/vite.config.ts) | 构建配置 | ⭐⭐⭐ |
| [tailwind.config.js](file:///root/tailwind.config.js) | 样式配置 | ⭐⭐ |
| [Dockerfile](file:///root/Dockerfile) | Docker 构建文件 | ⭐⭐⭐ |
| [nginx.conf](file:///root/nginx.conf) | Nginx 配置 | ⭐⭐⭐ |
| [src/App.tsx](file:///root/src/App.tsx) | 根组件 | ⭐⭐⭐ |
| [src/data/resume.ts](file:///root/src/data/resume.ts) | 简历数据 | ⭐⭐ |
十、扩展阅读
10.1 相关文档
10.2 学习路径
基础 → React → TypeScript → Vite → Tailwind → Docker → Nginx
文档版本: v1.0.0
最后更新: 2026-06-08
作者: 郭斌(SRE Engineer)
评论区