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
如果你还没有部署过,可以直接照抄下面的流程:
- 把项目推到 GitHub(不会的话,把「怎么推到 GitHub”这个问题丢给 AI)
- 访问 Vercel ,用 GitHub 登录
- 点击 「New Project” → 选择你的仓库 → 一路 Next
- 等几分钟,拿到形如
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'],
},
};学习总结
项目收获
通过这个项目,你掌握了:
- Next.js App Router:现代 React 框架的使用
- TypeScript:类型安全的开发体验
- Tailwind CSS:实用优先的样式系统
- 响应式设计:多设备适配能力
- 主题系统:深色模式实现
- 组件化开发:可复用的组件设计
- SEO 优化:搜索引擎友好
- 部署流程:从开发到上线的完整流程
扩展方向
项目扩展建议:
- 添加博客功能:使用 MDX 支持文章发布
- 集成 CMS:支持 Strapi、Contentful 等内容管理
- 添加联系表单:使用 Formspree 或 Resend
- 多语言支持:使用 next-intl 国际化
- 数据库集成:添加用户评论、项目管理等功能
- 性能监控:集成 Vercel Analytics、Google Analytics
- CI/CD 流程:GitHub Actions 自动化部署
继续学习
下一步学习路径:
- 第 4 章:后端开发与数据处理 - 学习服务器端开发
- 第 5 章:实战项目开发 - 更复杂的项目实战
- 第 6 章:部署上线与运营 - 生产环境部署
- 第 7 章:商业化与出海 - 产品化和全球化
- 第 8 章:持续成长与进阶 - 长期发展规划
恭喜完成第三章学习! 🎉
你已经掌握了前端开发的核心技能,能够独立创建现代化的网站应用。现在你具备了:
- ✅ 完整的前端技术栈知识
- ✅ 实际项目开发经验
- ✅ 响应式设计能力
- ✅ 性能优化和 SEO 技能
- ✅ 部署上线经验
准备好进入下一阶段的学习了吗?接下来就去探索后端开发和数据处理的世界。
下一步:开始学习 第 4 章:后端开发与数据处理,探索服务器端编程的精彩世界。
Last updated on