返回博客
项目
2024年1月12日
4 分钟阅读

个人作品集网站开发实战

个人作品集网站开发实战

作为一名开发者,拥有一个专业的个人作品集网站是展示技能和吸引机会的重要工具。本文将详细记录我开发个人作品集网站的完整过程。

项目背景与目标

为什么需要作品集网站?

在当今竞争激烈的技术领域,一个优秀的作品集网站能够:

  • 展示技术能力和项目经验
  • 建立个人品牌和在线形象
  • 吸引潜在雇主或客户
  • 记录和分享技术学习历程

项目目标

  1. 功能目标

    • 展示个人信息和技能
    • 项目作品展示
    • 技术博客系统
    • 联系方式和社交媒体链接
  2. 技术目标

    • 使用现代Web技术栈
    • 优秀的性能表现
    • 完整的SEO优化
    • 响应式设计

技术选型决策

前端框架:Next.js 14

选择Next.js的原因:

  • 全栈能力: 支持SSR、SSG、API routes
  • 性能优异: 自动代码分割、图片优化
  • SEO友好: 服务端渲染支持
  • 开发体验: 优秀的开发工具和热重载

样式方���:Tailwind CSS

CSS
/* 传统CSS写法 */
.button {
  background-color: #3b82f6;
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 0.375rem;
  border: none;
  cursor: pointer;
}

.button:hover {
  background-color: #2563eb;
}
React JSX
/* Tailwind CSS写法 */
<button className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">
  Click me
</button>

Tailwind的优势:

  • 快速开发
  • 一致的设计系统
  • 优秀的响应式支持
  • 自动清除未使用的样式

内容管理:Markdown + Gray Matter

选择基于文件的内容管理而非CMS:

  • 版本控制友好
  • 开发者友好的编写体验
  • 无额外服务依赖
  • 完全可控的数据结构

设计理念与实现

视觉设计

色彩方案

选择了温暖的粉色作为主色调:

CSS
:root {
  --color-primary: #ec4899;
  --color-primary-light: #f9a8d4;
  --color-primary-dark: #be185d;
}

布局设计

采用简洁的卡片式布局:

  • 清晰的信息层级
  • 充足的留白空间
  • 一致的圆角和阴影
  • 平滑的过渡动画

核心功能实现

1. 响应式导航

React TSX
function Navigation() {
  const [isOpen, setIsOpen] = useState(false)
  
  return (
    <nav className="sticky top-0 bg-white/80 backdrop-blur-md">
      <div className="max-w-6xl mx-auto px-8">
        <div className="flex justify-between items-center py-4">
          <Link href="/" className="text-2xl font-bold">
            Q1Hang
          </Link>
          
          {/* Desktop Menu */}
          <div className="hidden md:flex space-x-8">
            <NavLink href="/">Home</NavLink>
            <NavLink href="/blog">Blog</NavLink>
          </div>
          
          {/* Mobile Menu Toggle */}
          <button 
            onClick={() => setIsOpen(!isOpen)}
            className="md:hidden"
          >
            {isOpen ? <X /> : <Menu />}
          </button>
        </div>
      </div>
    </nav>
  )
}

2. 项目展示卡片

React TSX
function ProjectCard({ project }: { project: Project }) {
  return (
    <div className="bg-pink-50 border border-pink-200 rounded-2xl p-6 h-full flex flex-col hover:border-pink-300 hover:shadow-lg transition-all duration-200">
      <h3 className="text-xl font-bold mb-3 text-black">
        {project.title}
      </h3>
      
      <p className="text-gray-600 flex-grow min-h-[80px] mb-4">
        {project.description}
      </p>
      
      <div className="flex flex-wrap gap-2 mb-4">
        {project.tech.map((tech) => (
          <span key={tech} className="bg-white px-3 py-1 rounded-full text-sm">
            {tech}
          </span>
        ))}
      </div>
      
      <a
        href={project.github}
        className="bg-pink-500 hover:bg-pink-600 text-white px-4 py-2 rounded-lg font-medium transition-colors mt-auto"
      >
        View Project
      </a>
    </div>
  )
}

3. 博客系统

文章数据结构:

TypeScript
interface Post {
  slug: string
  title: string
  date: string
  category: 'tech' | 'life' | 'projects'
  description: string
  tags: string[]
  content: string
  readingTime: number
  featured?: boolean
}

文章解析逻辑:

TypeScript
export function getAllPosts(): Post[] {
  const categories = ['tech', 'life', 'projects']
  const allPosts: Post[] = []

  categories.forEach(category => {
    const categoryPath = path.join(postsDirectory, category)
    if (!fs.existsSync(categoryPath)) return

    const files = fs.readdirSync(categoryPath)
    
    files.forEach(fileName => {
      if (!fileName.endsWith('.md')) return

      const slug = fileName.replace(/\.md$/, '')
      const fullPath = path.join(categoryPath, fileName)
      const fileContents = fs.readFileSync(fullPath, 'utf8')
      const { data, content } = matter(fileContents)

      allPosts.push({
        slug,
        title: data.title,
        date: data.date,
        category: category as Post['category'],
        description: data.description,
        tags: data.tags || [],
        content,
        readingTime: calculateReadingTime(content),
        featured: data.featured || false
      })
    })
  })

  return allPosts.sort((a, b) => 
    new Date(b.date).getTime() - new Date(a.date).getTime()
  )
}

性能优化策略

1. 图片优化

使用Next.js的Image组件:

React TSX
import Image from 'next/image'

<Image
  src="/avatar.jpg"
  alt="Profile"
  width={256}
  height={256}
  className="rounded-full"
  priority
/>

优化效果:

  • 自动WebP格式转换
  • 响应式图片生成
  • 懒加载和预加载
  • 自动尺寸优化

2. 代码分割

Next.js自动实现的优化:

  • 页面级代码分割
  • 动态导入支持
  • 共享代码提取
  • 预取关键资源

3. 静态生成

博客文章使用SSG:

TypeScript
export async function generateStaticParams() {
  const posts = getAllPosts()
  return posts.map((post) => ({
    slug: post.slug,
  }))
}

export async function generateMetadata({ params }: Props) {
  const post = getPostBySlug(params.slug)
  
  return {
    title: `${post.title} - Q1Hang`,
    description: post.description,
    openGraph: {
      title: post.title,
      description: post.description,
      type: 'article',
    }
  }
}

SEO优化实践

1. 元数据管理

TypeScript
// 全局元数据
export const metadata: Metadata = {
  title: 'Q1Hang - Full Stack Developer',
  description: 'Passionate developer interested in AI and modern web technologies',
  keywords: ['Next.js', 'React', 'TypeScript', 'Full Stack'],
  authors: [{ name: 'Q1Hang' }],
  openGraph: {
    type: 'website',
    locale: 'zh_CN',
    url: 'https://your-domain.com',
    siteName: 'Q1Hang Portfolio',
  }
}

2. 结构化数据

为博客文章添加JSON-LD:

TypeScript
function generateArticleSchema(post: Post) {
  return {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": post.title,
    "description": post.description,
    "author": {
      "@type": "Person",
      "name": "Q1Hang"
    },
    "datePublished": post.date,
    "keywords": post.tags.join(', ')
  }
}

3. 网站地图生成

TypeScript
// app/sitemap.ts
export default function sitemap(): MetadataRoute.Sitemap {
  const posts = getAllPosts()
  
  const postUrls = posts.map((post) => ({
    url: `https://your-domain.com/blog/${post.slug}`,
    lastModified: new Date(post.date),
    changeFrequency: 'monthly' as const,
    priority: 0.8,
  }))

  return [
    {
      url: 'https://your-domain.com',
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 1,
    },
    {
      url: 'https://your-domain.com/blog',
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 0.9,
    },
    ...postUrls,
  ]
}

部署与运维

Vercel部署流程

  1. 连接GitHub仓库

    • 自动检测Next.js项目
    • 配置构建和部署设置
  2. 环境变量配置

    Bash
    NEXT_PUBLIC_SITE_URL=https://your-domain.com
    
  3. 自定义域名

    • DNS配置
    • SSL证书自动管理

性能监控

使用Vercel Analytics:

  • 页面性能指标
  • 用户行为分析
  • Core Web Vitals监控

项目成果与反思

技术成果

  • ✅ 完全响应式设计
  • ✅ 优秀的性能表现 (Lighthouse 95+)
  • ✅ 完整的SEO优化
  • ✅ 现代化的开发体验

性能数据

  • 首屏加载时间: < 1.5s
  • 累积布局偏移: < 0.1
  • 最大内容绘制: < 2.5s
  • 首次输入延迟: < 100ms

经验总结

技术层面

  1. 框架选择很重要: Next.js的全栈能力大大简化了开发
  2. 性能优化要从设计开始: 而不是事后补救
  3. 类型安全的价值: TypeScript帮助避免了很多潜在问题

设计层面

  1. 简洁胜过复杂: 用户更关注内容而非炫技
  2. 一致性原则: 统一的设计语言提升用户体验
  3. 移动优先: 大部分用户通过移动设备访问

后续优化计划

功能扩展

  • 博客文章搜索功能
  • 标签页面和标签云
  • 文章阅读进度指示器
  • 深色模式支持

技术优化

  • 更细粒度的代码分割
  • Service Worker缓存策略
  • 图片懒加载优化
  • 微交互动画增强

结语

这个作品集网站项目不仅是技术能力的展示,更是一个持续学习和优化的过程。通过实际项目的驱动,我深入掌握了现代Web开发的最佳实践,也更好地理解了用户体验设计的重要性。

对于想要建设自己作品集网站的开发者,我的建议是:

  1. 先规划,后开发
  2. 重视用户体验
  3. 持续优化和迭代
  4. 记录和分享经验

技术在变化,但优质内容和良好体验的追求是永恒的。希望这个项目能够为其他开发者提供一些参考和启发。