Skip to Content

3.3 综合项目实战(极简版)

现在不再继续讲新知识,只做一件事:
把你手上的 Next.js 项目变成一个可以分享给朋友的个人小站。

目标尽量简单:

  • 只做 1 个首页
  • 再加 1–2 个简单子页面(例如「关于我」、「联系我」)
  • 能上线,能在手机和电脑上打开

项目目标: 一个「能用就行」的个人页

页面大致长这样:

  • 顶部一个标题:「我是 XXX」
  • 中间 2–3 段话: 你在做什么 / 想学什么 / 想做什么项目 +- 底部 1–2 个按钮: 链接到你的社交账号或邮箱

你可以把它当成:

  • 向朋友解释「我在学 AI 编程」的说明书
  • 后面做项目时的统一入口(把项目链接挂在这里)

三步法: 做 → 改 → 上线

第 1 步: 让 AI 生成基础页面

在你已经跑起来的 Next.js 项目里,复制当前 app/page.tsx,发给 AI,说:

请帮我把这个首页改成一个极简的个人介绍页,要求: - 居中布局,适配电脑和手机 - 顶部是大标题"我是 XXX" - 中间有三段话: 我是谁 / 我为什么学 AI 编程 / 我接下来想做什么项目 - 底部有两个按钮: 一个跳转到 /projects, 一个是 mailto 邮箱链接 请直接给我完整的 page.tsx 代码。

把返回的代码粘回去,保存,在浏览器确认样式符合预期。

第 2 步: 补一个简单的项目页

让 AI 帮你在 app/projects/page.tsx 里加一个最简单的列表:

请为我的 Next.js 项目创建一个 `app/projects/page.tsx` 文件, 展示 3 个项目: - "第一个网页" (描述: 用 AI 生成的静态页面) - "个人主页" (描述: 现在这个站) - "未来想做的产品" (描述: 先留一个空位) 用卡片或列表都可以,重点是简单清晰、适配手机。

根据你的实际项目情况,把文字改成真实内容。

第 3 步: 部署到 Vercel

如果你还没有部署过,可以直接照抄下面的流程:

  1. 把项目推到 GitHub(不会的话,把「怎么推到 GitHub”这个问题丢给 AI)
  2. 访问 Vercel ,用 GitHub 登录
  3. 点击 「New Project” → 选择你的仓库 → 一路 Next
  4. 等几分钟,拿到形如 https://xxx.vercel.app 的网址

部署过程中如果报错,复制整个错误日志给 AI,说:

这是我在 Vercel 部署 Next.js 项目时的完整错误日志, 请按"问题原因 → 我应该具体改什么 → 改完如何重新部署"的格式教我。

最简单的验收标准

可以用这几个问题自查:

  • 这个网站有没有一个你愿意发给朋友的 URL?
  • 在手机和电脑上都能正常浏览吗?
  • 你能自己定位并修改页面上的任意一段文案吗?
  • 你知道大致在哪改颜色/间距(哪怕是用 AI 告诉你)?

如果你想再往前迈一步,可以尝试:

  • 给站点加一个「学习进度」小区域,展示你已经完成了第几章
  • 给每个项目加一个简单的「我在这里踩过哪些坑」说明

做到这里,第 3 章对零基础用户的任务就完成了。
接下来,可以进入第 4 章,简单感受一下「数据从哪来、怎么自动化」。

// lib/types.ts export interface PersonalInfo { name: string; title: string; bio: string; avatar: string; email: string; location: string; social: { github: string; linkedin: string; twitter: string; }; } export interface Skill { category: string; items: { name: string; level: number; // 1-5 icon: string; }[]; } export interface Project { id: string; title: string; description: string; image: string; tech: string[]; links: { demo?: string; github?: string; }; featured: boolean; category: 'web' | 'mobile' | 'tool' | 'other'; } export interface ThemeContextType { theme: 'light' | 'dark'; toggleTheme: () => void; }

2. 主题系统

// components/ui/ThemeToggle.tsx 'use client'; import { createContext, useContext, useEffect, useState } from 'react'; const ThemeContext = createContext<ThemeContextType | undefined>(undefined); export function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState<'light' | 'dark'>('light'); useEffect(() => { const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null; if (savedTheme) { setTheme(savedTheme); } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) { setTheme('dark'); } }, []); useEffect(() => { localStorage.setItem('theme', theme); if (theme === 'dark') { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } }, [theme]); const toggleTheme = () => { setTheme(prev => prev === 'light' ? 'dark' : 'light'); }; return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); } export function useTheme() { const context = useContext(ThemeContext); if (context === undefined) { throw new Error('useTheme must be used within a ThemeProvider'); } return context; } // components/ui/ThemeToggle.tsx (continued) export function ThemeToggle() { const { theme, toggleTheme } = useTheme(); return ( <button onClick={toggleTheme} className="p-2 rounded-lg bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors" aria-label="切换主题" > {theme === 'light' ? ( <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /> </svg> ) : ( <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /> </svg> )} </button> ); }

3. 根布局

// app/layout.tsx import { Inter } from 'next/font/google'; import './globals.css'; import { ThemeProvider } from '@/components/ui/ThemeToggle'; const inter = Inter({ subsets: ['latin'] }); export const metadata = { title: '张三 - 全栈开发工程师', description: '张三的个人作品集,展示技能和项目经验', keywords: ['全栈开发', 'React', 'Next.js', 'TypeScript'], authors: [{ name: '张三' }], openGraph: { title: '张三 - 全栈开发工程师', description: '张三的个人作品集', type: 'website', locale: 'zh_CN', }, }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="zh-CN" suppressHydrationWarning> <body className={`${inter.className} min-h-screen bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100`}> <ThemeProvider> {children} </ThemeProvider> </body> </html> ); }

4. 导航栏

// components/layout/Navigation.tsx 'use client'; import { useState, useEffect } from 'react'; import { useTheme } from '@/components/ui/ThemeToggle'; import { ThemeToggle } from '@/components/ui/Button'; export default function Navigation() { const [isScrolled, setIsScrolled] = useState(false); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const { theme } = useTheme(); useEffect(() => { const handleScroll = () => { setIsScrolled(window.scrollY > 10); }; window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, []); const navLinks = [ { href: '#about', label: '关于' }, { href: '#skills', label: '技能' }, { href: '#projects', label: '项目' }, { href: '#contact', label: '联系' }, ]; return ( <nav className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${ isScrolled ? 'bg-white/80 dark:bg-gray-900/80 backdrop-blur-md shadow-lg' : 'bg-transparent' }`}> <div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="flex justify-between items-center h-16"> {/* Logo */} <div className="flex-shrink-0"> <a href="#" className="text-xl font-bold text-primary-600"> 张三 </a> </div> {/* Desktop Navigation */} <div className="hidden md:flex items-center space-x-8"> {navLinks.map((link) => ( <a key={link.href} href={link.href} className="text-gray-700 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" > {link.label} </a> ))} <ThemeToggle /> </div> {/* Mobile menu button */} <div className="md:hidden flex items-center space-x-2"> <ThemeToggle /> <button onClick={() => setMobileMenuOpen(!mobileMenuOpen)} className="p-2 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800" > <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24"> {mobileMenuOpen ? ( <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> ) : ( <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /> )} </svg> </button> </div> </div> {/* Mobile Navigation */} {mobileMenuOpen && ( <div className="md:hidden"> <div className="px-2 pt-2 pb-3 space-y-1 bg-white dark:bg-gray-900 rounded-lg shadow-lg"> {navLinks.map((link) => ( <a key={link.href} href={link.href} className="block px-3 py-2 text-gray-700 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400 rounded-md transition-colors" onClick={() => setMobileMenuOpen(false)} > {link.label} </a> ))} </div> </div> )} </div> </nav> ); }

5. Hero 区域

// components/sections/Hero.tsx import { personalInfo } from '@/lib/data'; export default function Hero() { return ( <section id="hero" className="min-h-screen flex items-center justify-center px-4 pt-16"> <div className="max-w-4xl mx-auto text-center"> {/* 头像 */} <div className="mb-8"> <img src={personalInfo.avatar} alt={personalInfo.name} className="w-32 h-32 sm:w-40 sm:h-40 rounded-full mx-auto shadow-xl ring-4 ring-white dark:ring-gray-900" /> </div> {/* 标题和职位 */} <h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-4"> <span className="bg-gradient-to-r from-primary-600 to-secondary-600 bg-clip-text text-transparent"> {personalInfo.name} </span> </h1> <p className="text-xl sm:text-2xl text-gray-600 dark:text-gray-400 mb-6"> {personalInfo.title} </p> {/* 简介 */} <p className="text-lg text-gray-700 dark:text-gray-300 max-w-2xl mx-auto mb-8"> {personalInfo.bio} </p> {/* CTA 按钮 */} <div className="flex flex-col sm:flex-row gap-4 justify-center mb-8"> <a href="#contact" className="px-8 py-3 bg-primary-600 text-white font-semibold rounded-lg hover:bg-primary-700 transition-colors shadow-lg" > 联系我 </a> <a href="#projects" className="px-8 py-3 border-2 border-primary-600 text-primary-600 dark:text-primary-400 font-semibold rounded-lg hover:bg-primary-50 dark:hover:bg-primary-900/20 transition-colors" > 查看作品 </a> </div> {/* 社交链接 */} <div className="flex justify-center space-x-6"> <a href={personalInfo.social.github} target="_blank" rel="noopener noreferrer" className="text-gray-600 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" > <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"> <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957.266 1.783.693 2.231 1.236.426.634.953 1.653.953 2.717 0 1.765-1.342 3.213-1.342 3.213 1.06 1.064 3.044 1.807 5.097 1.677 1.823-.563 3.223-1.78 3.726-1.77.426.727.269 1.28.526 1.653.267.777 0 1.803-.279 2.748-.525.947.765 1.824 1.791 2.748 1.805.946.026 1.653-.229 2.224-.627zm-4.76-1.153c-1.089-.696-1.784-1.532-1.784-2.017 0-.348.208-.664.537-.878-.445-.526-1.534-1.342-1.784-2.017-.214-.426-.329-.976-.329-1.653 0-.946.693-1.533 1.784-2.017.485-.215 1.04-.329 1.653-.329 1.613 0 2.917.548 3.874 1.534.957.986 2.261 1.534 3.874 1.534.613 0 1.168-.114 1.653-.329.957-.696 1.784-1.071 1.784-2.017 0-.348-.208-.664-.537-.878z"/> </svg> </a> <a href={personalInfo.social.linkedin} target="_blank" rel="noopener noreferrer" className="text-gray-600 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" > <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"> <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 1.445-2.493 0-2.627 1.445-2.627 1.445-2.136 0-3.037 1.709-3.037 3.037v5.569h-3.554v-14.81h3.554v2.406c.481-.747 1.693-2.406 3.688-2.406 2.698 0 4.345 1.784 4.345 5.896v9.314z"/> </svg> </a> <a href={personalInfo.social.twitter} target="_blank" rel="noopener noreferrer" className="text-gray-600 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" > <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"> <path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 00-4.594-1.064 4.918 4.918 0 00-2.212-5.658l2.212 1.561a4.918 4.918 0 003.996 1.965 4.916 4.916 0 002.212-5.658l2.212 1.561a4.918 4.918 0 01-2.212 5.658 4.916 4.916 0 00-3.996-1.965 4.918 4.918 0 004.594 1.064 10 10 0 002.825-.775 4.936 4.936 0 005.727 3.965 10 10 0 004.575 2.524c-.637-4.012-2.719-7.43-5.727-9.091l2.212 1.561A4.936 4.936 0 0123.953 4.57z"/> </svg> </a> </div> </div> </section> ); }

数据管理

数据文件

// lib/data.ts export const personalInfo = { name: '张三', title: '全栈开发工程师', bio: '热爱编程,专注于 Web 开发和用户体验。5 年开发经验,熟练掌握 React、Next.js、Node.js 等技术栈。善于将复杂的需求转化为简洁、高效的代码解决方案。', avatar: '/images/avatar.jpg', email: '[email protected]', location: '北京, 中国', social: { github: 'https://github.com/zhangsan', linkedin: 'https://linkedin.com/in/zhangsan', twitter: 'https://twitter.com/zhangsan' } }; export const skills = [ { category: '前端开发', items: [ { name: 'React', level: 5, icon: '⚛️' }, { name: 'Next.js', level: 5, icon: '▲' }, { name: 'TypeScript', level: 4, icon: '📘' }, { name: 'Tailwind CSS', level: 5, icon: '🎨' }, { name: 'JavaScript', level: 5, icon: '🟨' } ] }, { category: '后端开发', items: [ { name: 'Node.js', level: 4, icon: '🟢' }, { name: 'Express', level: 4, icon: '🚂' }, { name: 'PostgreSQL', level: 3, icon: '🐘' }, { name: 'MongoDB', level: 3, icon: '🍃' }, { name: 'REST API', level: 4, icon: '🔌' } ] }, { category: '开发工具', items: [ { name: 'Git', level: 5, icon: '📦' }, { name: 'VS Code', level: 5, icon: '💻' }, { name: 'Docker', level: 3, icon: '🐳' }, { name: 'Figma', level: 4, icon: '🎨' }, { name: 'Vercel', level: 5, icon: '▲' } ] } ]; export const projects: Project[] = [ { id: '1', title: 'AI 编程助手', description: '基于 AI 的编程助手平台,帮助开发者提高编程效率,支持多种编程语言和框架。', image: '/images/projects/ai-assistant.png', tech: ['React', 'Next.js', 'TypeScript', 'OpenAI API'], links: { demo: 'https://ai-assistant.vercel.app', github: 'https://github.com/zhangsan/ai-assistant' }, featured: true, category: 'web' }, { id: '2', title: '任务管理应用', description: '功能完整的任务管理工具,支持任务分类、优先级设置、团队协作等功能。', image: '/images/projects/task-manager.png', tech: ['React Native', 'TypeScript', 'Firebase', 'Redux Toolkit'], links: { demo: 'https://task-manager.app', github: 'https://github.com/zhangsan/task-manager' }, featured: true, category: 'mobile' }, { id: '3', title: '电商后台管理系统', description: '功能完善的电商后台管理平台,包含商品管理、订单处理、数据分析等功能。', image: '/images/projects/ecommerce-admin.png', tech: ['Vue.js', 'Element Plus', 'Node.js', 'MySQL'], links: { github: 'https://github.com/zhangsan/ecommerce-admin' }, featured: false, category: 'web' }, { id: '4', title: '代码格式化工具', description: '自定义代码格式化工具,支持多种编程语言,可配置格式化规则。', image: '/images/projects/code-formatter.png', tech: ['TypeScript', 'Electron', 'Monaco Editor'], links: { github: 'https://github.com/zhangsan/code-formatter' }, featured: false, category: 'tool' } ];

首页整合

// app/page.tsx import Navigation from '@/components/layout/Navigation'; import Hero from '@/components/sections/Hero'; import About from '@/components/sections/About'; import Skills from '@/components/sections/Skills'; import Projects from '@/components/sections/Projects'; import Contact from '@/components/sections/Contact'; import Footer from '@/components/layout/Footer'; export default function HomePage() { return ( <div className="min-h-screen"> <Navigation /> <main> <Hero /> <About /> <Skills /> <Projects /> <Contact /> </main> <Footer /> </div> ); }

构建和部署

构建检查清单

部署前检查

  • 本地构建成功:npm run build
  • 所有页面正常访问
  • 响应式设计测试
  • 深色模式切换正常
  • 性能优化完成
  • SEO meta 标签完整
  • 无控制台错误

Vercel 部署

# 1. 安装 Vercel CLI npm i -g vercel # 2. 登录 vercel login # 3. 部署 vercel # 4. 配置自定义域名(可选) vercel domains add yourdomain.com

环境变量配置

# Vercel 环境变量 NEXT_PUBLIC_GITHUB_TOKEN=your_github_token NEXT_PUBLIC_ANALYTICS_ID=your_analytics_id

项目优化

性能优化

// next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { // 图片优化 images: { domains: ['example.com'], formats: ['image/webp', 'image/avif'], }, // 压缩 compress: true, // 实验性功能 experimental: { appDir: true, serverComponentsExternalPackages: ['@prisma/client'], }, // 安全头部 async headers() { return [ { source: '/(.*)', headers: [ { key: 'X-Content-Type-Options', value: 'nosniff', }, { key: 'X-Frame-Options', value: 'DENY', }, { key: 'X-XSS-Protection', value: '1; mode=block', }, ], }, ]; }, }; module.exports = nextConfig;

SEO 优化

// app/page.tsx (metadata) export const metadata = { title: '张三 - 全栈开发工程师 | 个人作品集', description: '张三的个人作品集,展示 React、Next.js、TypeScript 等技术栈的项目经验。5 年开发经验,专注于 Web 开发和用户体验设计。', keywords: [ '全栈开发', 'React开发', 'Next.js', 'TypeScript', '前端开发', 'Web开发', '作品集', '张三' ], authors: [{ name: '张三', url: 'https://zhangsan.dev' }], creator: '张三', publisher: '张三', robots: 'index, follow', openGraph: { title: '张三 - 全栈开发工程师', description: '张三的个人作品集,展示项目经验和技术能力', url: 'https://zhangsan.dev', siteName: '张三的作品集', locale: 'zh_CN', type: 'website', images: [ { url: 'https://zhangsan.dev/images/og-image.jpg', width: 1200, height: 630, alt: '张三 - 全栈开发工程师', }, ], }, twitter: { card: 'summary_large_image', title: '张三 - 全栈开发工程师', description: '张三的个人作品集', images: ['https://zhangsan.dev/images/og-image.jpg'], }, };

学习总结

项目收获

通过这个项目,你掌握了:

  1. Next.js App Router:现代 React 框架的使用
  2. TypeScript:类型安全的开发体验
  3. Tailwind CSS:实用优先的样式系统
  4. 响应式设计:多设备适配能力
  5. 主题系统:深色模式实现
  6. 组件化开发:可复用的组件设计
  7. SEO 优化:搜索引擎友好
  8. 部署流程:从开发到上线的完整流程

扩展方向

项目扩展建议

  1. 添加博客功能:使用 MDX 支持文章发布
  2. 集成 CMS:支持 Strapi、Contentful 等内容管理
  3. 添加联系表单:使用 Formspree 或 Resend
  4. 多语言支持:使用 next-intl 国际化
  5. 数据库集成:添加用户评论、项目管理等功能
  6. 性能监控:集成 Vercel Analytics、Google Analytics
  7. CI/CD 流程:GitHub Actions 自动化部署

继续学习

下一步学习路径

  1. 第 4 章:后端开发与数据处理 - 学习服务器端开发
  2. 第 5 章:实战项目开发 - 更复杂的项目实战
  3. 第 6 章:部署上线与运营 - 生产环境部署
  4. 第 7 章:商业化与出海 - 产品化和全球化
  5. 第 8 章:持续成长与进阶 - 长期发展规划

恭喜完成第三章学习! 🎉

你已经掌握了前端开发的核心技能,能够独立创建现代化的网站应用。现在你具备了:

  • ✅ 完整的前端技术栈知识
  • ✅ 实际项目开发经验
  • ✅ 响应式设计能力
  • ✅ 性能优化和 SEO 技能
  • ✅ 部署上线经验

准备好进入下一阶段的学习了吗?接下来就去探索后端开发和数据处理的世界。


下一步:开始学习 第 4 章:后端开发与数据处理,探索服务器端编程的精彩世界。

Last updated on