侧边栏壁纸
  • 累计撰写 86 篇文章
  • 累计创建 31 个标签
  • 累计收到 21 条评论

目 录CONTENT

文章目录

Next.js 14 + Tailwind CSS + shadcn/ui:从零打造你的个人开发者作品集网站

Administrator
2026-04-06 / 0 评论 / 0 点赞 / 0 阅读 / 0 字 / 正在检测是否收录...
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

作为一个开发者,你可能花了几百个小时学习新技术,却只用几行文字介绍自己?来,用 Next.js 14 + Tailwind CSS + shadcn/ui 搭一个专业的个人作品集网站,让你的技术实力"看得见"。


一、为什么你需要个人作品集网站?

1.1 作品集 vs 简历的对比

对比维度简历作品集网站
内容形式静态文字文字 + 图片 + 视频 + 交互
展示维度扁平列表视觉化 + 项目演示 + 动效
搜索引擎可见性SEO 友好
更新频率每次求职重写持续更新
成本免费/模板域名 + 托管 ≈ ¥100/年
印象分

1.2 为什么选这套技术栈?

Next.js 14     →  React 全家桶,SSR/SSG 加持,SEO 无敌
Tailwind CSS   →  原子化 CSS,写样式像搭积木
shadcn/ui      →  Radix UI 底层,专业组件库,设计感强
Vercel         →  免费托管,一键部署,全球 CDN

二、环境准备

2.1 必要工具

# Node.js 20+ (建议使用 nvm 管理版本)
node --version  # 确保 >= 20.0.0

# pnpm (比 npm 快 2-3 倍)
npm install -g pnpm

# Git (代码管理)
git --version

2.2 创建项目

# 使用 Next.js 官方脚手架
pnpm create next-app@latest portfolio

# 配置选项:
# - TypeScript: Yes
# - ESLint: Yes
# - Tailwind CSS: Yes
# - src/ directory: Yes
# - App Router: Yes  ← 关键选项
# - Import alias: @/*

cd portfolio
pnpm dev  # 启动开发服务器

2.3 项目结构

portfolio/
├── src/
│   ├── app/                    # App Router
│   │   ├── page.tsx            # 首页
│   │   ├── layout.tsx         # 根布局
│   │   ├── globals.css         # 全局样式
│   │   └── about/
│   │       └── page.tsx        # 关于页
│   ├── components/
│   │   ├── ui/                # shadcn/ui 组件
│   │   ├── Hero.tsx           # 首屏介绍
│   │   ├── Projects.tsx       # 项目展示
│   │   ├── Skills.tsx         # 技术栈
│   │   └── Contact.tsx        # 联系方式
│   └── lib/
│       └── utils.ts           # 工具函数
├── public/                     # 静态资源
├── tailwind.config.ts          # Tailwind 配置
├── next.config.js              # Next.js 配置
└── package.json

三、安装与配置 shadcn/ui

3.1 初始化 shadcn/ui

# 在项目根目录执行
pnpm dlx shadcn@latest init

# 配置选项:
# - Style: Default
# - Base Color: Slate
# - CSS file: globals.css
# - CSS variables: Yes
# - Custom prefix: No
# - Components: .tsx
# - Utils: lib/utils.ts
# - ESLint: Yes
# - Tailwind: tailwind.config.ts
# - src/ directory: Yes

3.2 安装常用组件

# 按钮、卡片、导航栏、对话框、标签等
pnpm dlx shadcn@latest add button card navbar scroll-area badge avatar separator sheet

# 动画库 (Framer Motion)
pnpm add framer-motion

四、核心组件开发

4.1 Hero 首屏组件

// src/components/Hero.tsx
"use client";

import { motion } from "framer-motion";
import { Button } from "@/components/ui/button";
import { ArrowDown, Github, Linkedin, Mail } from "lucide-react";

export function Hero() {
  return (
    <section className="min-h-screen flex items-center justify-center px-6">
      <div className="text-center max-w-3xl">
        {/* 头像 */}
        <motion.div
          initial={{ scale: 0, opacity: 0 }}
          animate={{ scale: 1, opacity: 1 }}
          transition={{ duration: 0.5 }}
          className="mb-8"
        >
          <div className="w-32 h-32 mx-auto rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-4xl font-bold text-white">
            你
          </div>
        </motion.div>

        {/* 标题 */}
        <motion.h1
          initial={{ y: 20, opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          transition={{ delay: 0.2 }}
          className="text-4xl md:text-6xl font-bold mb-4"
        >
          Hi, I'm <span className="text-gradient">你的名字</span>
        </motion.h1>

        {/* 副标题 */}
        <motion.p
          initial={{ y: 20, opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          transition={{ delay: 0.3 }}
          className="text-xl text-muted-foreground mb-8"
        >
          全栈开发者 | 开源贡献者 | 技术写作者
        </motion.p>

        {/* 社交链接 */}
        <motion.div
          initial={{ y: 20, opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          transition={{ delay: 0.4 }}
          className="flex gap-4 justify-center mb-12"
        >
          <Button variant="outline" size="icon" asChild>
            <a href="https://github.com/yourname" target="_blank" rel="noopener noreferrer">
              <Github className="w-5 h-5" />
            </a>
          </Button>
          <Button variant="outline" size="icon" asChild>
            <a href="https://linkedin.com/in/yourname" target="_blank" rel="noopener noreferrer">
              <Linkedin className="w-5 h-5" />
            </a>
          </Button>
          <Button variant="outline" size="icon" asChild>
            <a href="mailto:yourname@email.com">
              <Mail className="w-5 h-5" />
            </a>
          </Button>
        </motion.div>

        {/* CTA 按钮 */}
        <motion.div
          initial={{ y: 20, opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          transition={{ delay: 0.5 }}
          className="flex gap-4 justify-center"
        >
          <Button size="lg" asChild>
            <a href="#projects">查看项目</a>
          </Button>
          <Button size="lg" variant="secondary" asChild>
            <a href="/resume.pdf">下载简历</a>
          </Button>
        </motion.div>

        {/* 滚动提示 */}
        <motion.div
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          transition={{ delay: 1 }}
          className="absolute bottom-8 left-1/2 -translate-x-1/2"
        >
          <motion.div
            animate={{ y: [0, 10, 0] }}
            transition={{ repeat: Infinity, duration: 2 }}
          >
            <ArrowDown className="w-6 h-6 text-muted-foreground" />
          </motion.div>
        </motion.div>
      </div>
    </section>
  );
}

4.2 项目展示组件

// src/components/Projects.tsx
"use client";

import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { ExternalLink, Github } from "lucide-react";
import { motion } from "framer-motion";

const projects = [
  {
    title: "电商微服务系统",
    description: "基于 Spring Cloud Alibaba 的分布式电商平台,支持高并发秒杀、分布式事务、灰度发布",
    tags: ["Spring Boot", "Nacos", "Sentinel", "MySQL", "Redis"],
    github: "https://github.com/yourname/ecommerce",
    demo: "https://demo.92yangyi.top",
    image: "/projects/ecommerce.jpg",
  },
  {
    title: "AI 知识库问答系统",
    description: "RAG 应用实战,集成 DeepSeek 大模型,支持文档上传、智能检索、溯源引用",
    tags: ["Next.js", "LangChain", "Milvus", "DeepSeek", "Tailwind"],
    github: "https://github.com/yourname/rag-chatbot",
    demo: "https://chat.92yangyi.top",
    image: "/projects/rag.jpg",
  },
  {
    title: "开源组件库",
    description: "封装了 20+ 常用业务组件,配套完整文档和 Demo,开源 Star 1000+",
    tags: ["React", "TypeScript", "Storybook", "Vitest"],
    github: "https://github.com/yourname/awesome-ui",
    demo: "https://ui.92yangyi.top",
    image: "/projects/ui.jpg",
  },
];

export function Projects() {
  return (
    <section id="projects" className="py-24 px-6">
      <div className="max-w-6xl mx-auto">
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true }}
          className="text-center mb-16"
        >
          <h2 className="text-3xl md:text-4xl font-bold mb-4">Featured Projects</h2>
          <p className="text-muted-foreground">以下是一些我参与或主导的项目,持续更新中...</p>
        </motion.div>

        <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
          {projects.map((project, index) => (
            <motion.div
              key={project.title}
              initial={{ opacity: 0, y: 20 }}
              whileInView={{ opacity: 1, y: 0 }}
              viewport={{ once: true }}
              transition={{ delay: index * 0.1 }}
            >
              <Card className="h-full flex flex-col hover:shadow-lg transition-shadow">
                {/* 项目图片 */}
                <div className="aspect-video bg-gradient-to-br from-blue-100 to-purple-100 rounded-t-lg flex items-center justify-center">
                  <span className="text-6xl">🚀</span>
                </div>

                <CardHeader>
                  <CardTitle>{project.title}</CardTitle>
                  <CardDescription>{project.description}</CardDescription>
                </CardHeader>

                <CardContent className="flex-grow">
                  <div className="flex flex-wrap gap-2">
                    {project.tags.map((tag) => (
                      <Badge key={tag} variant="secondary" className="text-xs">
                        {tag}
                      </Badge>
                    ))}
                  </div>
                </CardContent>

                <CardFooter className="gap-2">
                  <Button variant="outline" size="sm" asChild>
                    <a href={project.github} target="_blank" rel="noopener noreferrer">
                      <Github className="w-4 h-4 mr-2" />
                      源码
                    </a>
                  </Button>
                  <Button size="sm" asChild>
                    <a href={project.demo} target="_blank" rel="noopener noreferrer">
                      <ExternalLink className="w-4 h-4 mr-2" />
                      演示
                    </a>
                  </Button>
                </CardFooter>
              </Card>
            </motion.div>
          ))}
        </div>
      </div>
    </section>
  );
}

4.3 技术栈展示组件

// src/components/Skills.tsx
"use client";

import { motion } from "framer-motion";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";

const skillCategories = [
  {
    title: "后端开发",
    icon: "⚙️",
    skills: [
      { name: "Java / Spring Boot", level: 95 },
      { name: "Go / Gin", level: 70 },
      { name: "Python / FastAPI", level: 75 },
      { name: "数据库 / Redis", level: 85 },
    ],
  },
  {
    title: "前端开发",
    icon: "🎨",
    skills: [
      { name: "React / Next.js", level: 85 },
      { name: "TypeScript", level: 88 },
      { name: "Tailwind CSS", level: 90 },
      { name: "Vue 3", level: 75 },
    ],
  },
  {
    title: "DevOps",
    icon: "☁️",
    skills: [
      { name: "Docker / K8s", level: 80 },
      { name: "CI/CD (Jenkins/GitHub)", level: 85 },
      { name: "Linux", level: 85 },
      { name: "云服务 (AWS/阿里云)", level: 75 },
    ],
  },
];

export function Skills() {
  return (
    <section id="skills" className="py-24 px-6 bg-muted/50">
      <div className="max-w-6xl mx-auto">
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true }}
          className="text-center mb-16"
        >
          <h2 className="text-3xl md:text-4xl font-bold mb-4">技术栈</h2>
          <p className="text-muted-foreground">持续学习,与时俱进</p>
        </motion.div>

        <div className="grid md:grid-cols-3 gap-6">
          {skillCategories.map((category, catIndex) => (
            <motion.div
              key={category.title}
              initial={{ opacity: 0, y: 20 }}
              whileInView={{ opacity: 1, y: 0 }}
              viewport={{ once: true }}
              transition={{ delay: catIndex * 0.1 }}
            >
              <Card>
                <CardHeader>
                  <CardTitle className="flex items-center gap-2">
                    <span>{category.icon}</span>
                    {category.title}
                  </CardTitle>
                </CardHeader>
                <CardContent className="space-y-4">
                  {category.skills.map((skill, skillIndex) => (
                    <div key={skill.name}>
                      <div className="flex justify-between mb-1">
                        <span className="text-sm">{skill.name}</span>
                        <span className="text-sm text-muted-foreground">{skill.level}%</span>
                      </div>
                      <div className="h-2 bg-secondary rounded-full overflow-hidden">
                        <motion.div
                          className="h-full bg-primary rounded-full"
                          initial={{ width: 0 }}
                          whileInView={{ width: `${skill.level}%` }}
                          viewport={{ once: true }}
                          transition={{ duration: 0.8, delay: skillIndex * 0.1 }}
                        />
                      </div>
                    </div>
                  ))}
                </CardContent>
              </Card>
            </motion.div>
          ))}
        </div>
      </div>
    </section>
  );
}

4.4 联系方式组件

// src/components/Contact.tsx
"use client";

import { motion } from "framer-motion";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Mail, Github, MapPin } from "lucide-react";

export function Contact() {
  return (
    <section id="contact" className="py-24 px-6">
      <div className="max-w-4xl mx-auto">
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true }}
          className="text-center mb-16"
        >
          <h2 className="text-3xl md:text-4xl font-bold mb-4">联系我</h2>
          <p className="text-muted-foreground">有项目合作或技术交流?欢迎联系我</p>
        </motion.div>

        <div className="grid md:grid-cols-2 gap-8">
          {/* 联系信息 */}
          <motion.div
            initial={{ opacity: 0, x: -20 }}
            whileInView={{ opacity: 1, x: 0 }}
            viewport={{ once: true }}
            className="space-y-6"
          >
            <Card>
              <CardContent className="pt-6">
                <div className="flex items-center gap-4 mb-4">
                  <div className="p-3 bg-primary/10 rounded-lg">
                    <Mail className="w-6 h-6 text-primary" />
                  </div>
                  <div>
                    <p className="text-sm text-muted-foreground">邮箱</p>
                    <p className="font-medium">yourname@email.com</p>
                  </div>
                </div>

                <div className="flex items-center gap-4 mb-4">
                  <div className="p-3 bg-primary/10 rounded-lg">
                    <Github className="w-6 h-6 text-primary" />
                  </div>
                  <div>
                    <p className="text-sm text-muted-foreground">GitHub</p>
                    <p className="font-medium">@yourname</p>
                  </div>
                </div>

                <div className="flex items-center gap-4">
                  <div className="p-3 bg-primary/10 rounded-lg">
                    <MapPin className="w-6 h-6 text-primary" />
                  </div>
                  <div>
                    <p className="text-sm text-muted-foreground">位置</p>
                    <p className="font-medium">中国 · 上海</p>
                  </div>
                </div>
              </CardContent>
            </Card>
          </motion.div>

          {/* 联系表单 */}
          <motion.div
            initial={{ opacity: 0, x: 20 }}
            whileInView={{ opacity: 1, x: 0 }}
            viewport={{ once: true }}
          >
            <Card>
              <CardHeader>
                <CardTitle>发送消息</CardTitle>
              </CardHeader>
              <CardContent>
                <form className="space-y-4">
                  <div className="grid grid-cols-2 gap-4">
                    <div className="space-y-2">
                      <label htmlFor="name" className="text-sm">姓名</label>
                      <Input id="name" placeholder="你的名字" />
                    </div>
                    <div className="space-y-2">
                      <label htmlFor="email" className="text-sm">邮箱</label>
                      <Input id="email" type="email" placeholder="your@email.com" />
                    </div>
                  </div>
                  <div className="space-y-2">
                    <label htmlFor="message" className="text-sm">留言</label>
                    <Textarea id="message" placeholder="想和我说些什么..." className="min-h-[120px]" />
                  </div>
                  <Button className="w-full">发送消息</Button>
                </form>
              </CardContent>
            </Card>
          </motion.div>
        </div>
      </div>
    </section>
  );
}

五、导航栏组件

// src/components/Navbar.tsx
"use client";

import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Button } from "@/components/ui/button";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { Menu, Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";

const navItems = [
  { href: "#home", label: "首页" },
  { href: "#projects", label: "项目" },
  { href: "#skills", label: "技能" },
  { href: "#contact", label: "联系" },
];

export function Navbar() {
  const [scrolled, setScrolled] = useState(false);
  const { theme, setTheme } = useTheme();

  useEffect(() => {
    const handleScroll = () => {
      setScrolled(window.scrollY > 50);
    };
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  return (
    <motion.header
      initial={{ y: -100 }}
      animate={{ y: 0 }}
      className={`fixed top-0 left-0 right-0 z-50 transition-colors ${
        scrolled ? "bg-background/80 backdrop-blur-md border-b" : "bg-transparent"
      }`}
    >
      <div className="max-w-6xl mx-auto px-6 h-16 flex items-center justify-between">
        {/* Logo */}
        <a href="#home" className="font-bold text-xl">
          Your<span className="text-primary">Logo</span>
        </a>

        {/* Desktop Navigation */}
        <nav className="hidden md:flex items-center gap-8">
          {navItems.map((item) => (
            <a
              key={item.href}
              href={item.href}
              className="text-sm font-medium hover:text-primary transition-colors"
            >
              {item.label}
            </a>
          ))}
        </nav>

        {/* Right Section */}
        <div className="flex items-center gap-2">
          <Button
            variant="ghost"
            size="icon"
            onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
          >
            <Sun className="w-5 h-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
            <Moon className="absolute w-5 h-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
          </Button>

          {/* Mobile Menu */}
          <Sheet>
            <SheetTrigger asChild className="md:hidden">
              <Button variant="ghost" size="icon">
                <Menu className="w-5 h-5" />
              </Button>
            </SheetTrigger>
            <SheetContent>
              <nav className="flex flex-col gap-4 mt-8">
                {navItems.map((item) => (
                  <a
                    key={item.href}
                    href={item.href}
                    className="text-lg font-medium hover:text-primary transition-colors"
                  >
                    {item.label}
                  </a>
                ))}
              </nav>
            </SheetContent>
          </Sheet>
        </div>
      </div>
    </motion.header>
  );
}

六、主题切换配置

6.1 安装 next-themes

pnpm add next-themes

6.2 根布局配置

// src/app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
import { Navbar } from "@/components/Navbar";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "你的名字 | 全栈开发者",
  description: "专注于全栈开发、微服务架构、AI 应用的技术博客",
  keywords: ["开发者", "全栈", "Java", "React", "Next.js"],
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="zh-CN" suppressHydrationWarning>
      <body className={inter.className}>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          <Navbar />
          <main>{children}</main>
        </ThemeProvider>
      </body>
    </html>
  );
}

七、首页组装

// src/app/page.tsx
import { Hero } from "@/components/Hero";
import { Projects } from "@/components/Projects";
import { Skills } from "@/components/Skills";
import { Contact } from "@/components/Contact";

export default function Home() {
  return (
    <>
      <Hero />
      <Projects />
      <Skills />
      <Contact />
    </>
  );
}

八、Tailwind 配置优化

// tailwind.config.ts
import type { Config } from "tailwindcss";

const config: Config = {
  darkMode: ["class"],
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      // 自定义渐变色
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic": "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
      // 动画
      animation: {
        "fade-in": "fadeIn 0.5s ease-in-out",
        "slide-up": "slideUp 0.5s ease-out",
      },
      keyframes: {
        fadeIn: {
          "0%": { opacity: "0" },
          "100%": { opacity: "1" },
        },
        slideUp: {
          "0%": { transform: "translateY(20px)", opacity: "0" },
          "100%": { transform: "translateY(0)", opacity: "1" },
        },
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
};

export default config;

九、部署到 Vercel

9.1 GitHub 推送

# 初始化 Git
git init
git add .
git commit -m "feat: 作品集网站 v1.0"

# 创建 GitHub 仓库并推送
gh repo create portfolio --public --push

9.2 Vercel 部署

方式一:命令行部署

# 安装 Vercel CLI
pnpm add -g vercel

# 登录并部署
cd portfolio
vercel

# 生产环境部署
vercel --prod

方式二:网页部署

  1. 访问 vercel.com
  2. 点击 "New Project"
  3. 导入你的 GitHub 仓库
  4. 点击 "Deploy"
  5. 自动构建并部署完成!

9.3 自定义域名(可选)

Vercel → 项目 → Settings → Domains → 添加你的域名
DNS 解析指向 Vercel 提供的 CNAME 记录

十、SEO 优化

10.1 metadata 配置

// src/app/layout.tsx
export const metadata: Metadata = {
  title: {
    default: "你的名字 | 全栈开发者",
    template: "%s | 你的名字",
  },
  description: "专注于全栈开发、微服务架构、AI 应用的技术博客",
  keywords: ["开发者", "全栈", "Java", "React", "Next.js", "技术博客"],
  authors: [{ name: "你的名字" }],
  creator: "你的名字",
  openGraph: {
    type: "website",
    locale: "zh_CN",
    url: "https://yourname.vercel.app",
    siteName: "你的名字",
    title: "你的名字 | 全栈开发者",
    description: "专注于全栈开发、微服务架构、AI 应用的技术博客",
  },
  twitter: {
    card: "summary_large_image",
    title: "你的名字 | 全栈开发者",
    description: "专注于全栈开发、微服务架构、AI 应用的技术博客",
  },
};

10.2 sitemap 生成

pnpm add next-sitemap
// next-sitemap.config.js
/** @type {import('next-sitemap').IConfig} */
module.exports = {
  siteUrl: process.env.SITE_URL || "https://yourname.vercel.app",
  generateRobotsTxt: true,
};

十一、效果预览

部署完成后,你的作品集网站将具备以下特性:

特性实现
响应式设计✅ 手机、平板、桌面完美适配
暗色模式✅ 一键切换,自动跟随系统
流畅动画✅ Framer Motion 赋能
SEO 友好✅ SSR + metadata 优化
快速加载✅ Vercel 全球 CDN
免费托管✅ Vercel Hobby 计划
自定义域名✅ 支持

十二、后续优化建议

12.1 功能增强

  • 📝 添加博客功能(Next.js + MDX)
  • 📊 添加访客统计(Vercel Analytics / Umami)
  • 📬 添加邮件订阅(Resend / Mailchimp)
  • 🤖 添加 AI 对话(DeepSeek API)

12.2 性能优化

# Lighthouse 性能检查
# - Core Web Vitals 全部绿标
# - LCP < 2.5s
# - FID < 100ms
# - CLS < 0.1

十三、完整代码获取

所有源码已开源,欢迎 Star:

GitHub: https://github.com/yourname/portfolio
在线演示: https://yourname.vercel.app

十四、结语

一个好的作品集网站,就是你 24/7 在线的"技术名片"。用 Next.js 14 + Tailwind CSS + shadcn/ui,你可以在一个周末内完成一个专业级的个人网站。

下一步行动

  1. 按照本教程,从零搭建你的作品集
  2. 替换为你的真实项目和信息
  3. 部署到 Vercel
  4. 在社交媒体分享你的作品

期待看到你的作品集!


💬 评论区互动:你在搭建作品集时遇到过哪些问题?欢迎留言交流!

0

评论区