本章是实战章节。读完本章,你将完成一个包含页面、API、TailwindCSS 样式的完整 Next.js 项目,并成功部署到 Vercel。
2.1 使用 create-next-app 初始化项目
前置要求
在开始之前,确保你的开发环境满足以下要求:
| 工具 | 最低版本 | 检查命令 |
|---|---|---|
| Node.js | 18.17 或更高 | node -v |
| npm / pnpm / yarn | 最新版 | npm -v |
| Git | 最新版 | git --version |
| 代码编辑器 | VS Code(推荐) | - |
推荐使用 pnpm:pnpm 比 npm 快 2-3 倍,磁盘占用更少,monorepo 支持更好。安装命令:
npm install -g pnpm
初始化项目
使用官方脚手架 create-next-app 创建项目:
npx create-next-app@latest my-nextjs-app
或使用 pnpm(推荐):
pnpm create next-app my-nextjs-app
交互式配置选项:
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
推荐选择:
| 选项 | 选择 | 原因 |
|---|---|---|
| TypeScript | ✅ Yes | 类型安全,减少运行时错误 |
| ESLint | ✅ Yes | 代码质量检查,集成 Next.js 规则 |
| Tailwind CSS | ✅ Yes | 原子化 CSS,快速开发 UI |
src/ directory | ❌ No | 简化目录结构(个人偏好) |
| App Router | ✅ Yes | 未来标准,RSC + Server Actions |
| Import alias | ✅ Yes,保持 @/* | 方便导入,如 @/components/Button |
项目初始化完成
cd my-nextjs-app
查看生成的目录结构:
my-nextjs-app/
├── app/
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── public/
│ ├── file.svg
│ ├── globe.svg
│ ├── next.svg
│ ├── vercel.svg
│ └── window.svg
├── .gitignore
├── eslint.config.mjs
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── README.md
├── tailwind.config.ts
└── tsconfig.json
2.2 TypeScript 配置与标准目录结构解析
TypeScript 配置
打开 tsconfig.json,查看默认配置:
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
关键配置解读:
| 配置项 | 值 | 说明 |
|---|---|---|
strict | true | 启用严格模式,强制类型检查 |
moduleResolution | bundler | 使用打包器解析模块(Next.js 推荐) |
paths | @/*: ["./*"] | 路径别名,@/components/Button → ./components/Button |
plugins | [{ name: "next" }] | Next.js TypeScript 插件,提供类型提示 |
推荐补充配置:
{
"compilerOptions": {
// ... 其他配置
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
}
}
标准目录结构解析
Next.js 项目的核心目录:
app/ — 应用目录(App Router)
app/
├── layout.tsx # 根布局(全局 Header、Footer)
├── page.tsx # 根页面(/)
├── globals.css # 全局样式
└── favicon.ico # 网站图标
layout.tsx:布局组件,包裹所有子路由页面page.tsx:页面组件,每个路由段一个page.tsxglobals.css:全局 CSS(Tailwind 指令在这里)
public/ — 静态资源
public/
├── file.svg
├── globe.svg
├── next.svg
├── vercel.svg
└── window.svg
- 存放图片、字体、robots.txt、sitemap.xml 等静态文件
- 引用时不需要
/public前缀:<img src="/next.svg" />
next.config.ts — Next.js 配置
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* 配置选项 */
};
export default nextConfig;
常用配置:
const nextConfig: NextConfig = {
// 启用实验性功能
experimental: {
serverActions: {
bodySizeLimit: '2mb',
},
},
// 图片优化配置
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
},
],
},
};
package.json — 依赖管理
{
"name": "my-nextjs-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"next": "^15.0.0"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"eslint": "^9",
"eslint-config-next": "^15.0.0"
}
}
核心脚本:
| 命令 | 作用 |
|---|---|
pnpm dev | 启动开发服务器(http://localhost:3000) |
pnpm build | 构建生产版本 |
pnpm start | 启动生产服务器 |
pnpm lint | 运行 ESLint 检查 |
2.3 开发模式、HMR 热更新与调试工具配置
启动开发服务器
pnpm dev
输出:
▲ Next.js 15.0.0
- Local: http://localhost:3000
- Network: http://192.168.1.100:3000
✓ Starting...
✓ Ready in 2.3s
打开浏览器访问 http://localhost:3000,你将看到 Next.js 默认首页。
HMR(Hot Module Replacement)热更新
修改 app/page.tsx:
// app/page.tsx
export default function Home() {
return (
<div className="p-8">
<h1 className="text-3xl font-bold">Hello, Next.js!</h1>
<p className="mt-4 text-gray-600">这是我的第一个 Next.js 项目。</p>
</div>
);
}
保存文件后,浏览器自动刷新,无需手动刷新页面。这就是 HMR(热模块替换)的魔力。
HMR 的工作原理:
- 你修改了
page.tsx - Next.js 检测到文件变化
- 只重新编译
page.tsx,不重新编译整个项目 - 通过 WebSocket 将新代码推送到浏览器
- 浏览器替换对应模块,保留组件状态
HMR 的优势:
- 速度快:只编译变化的文件,增量编译
- 状态保留:组件状态(如表单输入)不会丢失
- 即时反馈:修改后立即看到效果
调试工具配置
1. VS Code 推荐插件
| 插件 | 用途 |
|---|---|
| ES7+ React/Redux/React-Native snippets | React 代码片段 |
| Tailwind CSS IntelliSense | Tailwind 类名自动补全 |
| Prettier - Code formatter | 代码格式化 |
| Error Lens | 错误信息直接显示在代码行 |
| Thunder Client | API 测试(Postman 替代) |
2. Chrome DevTools
Next.js 开发模式下,Chrome DevTools 提供:
- Elements:查看 DOM 结构、Tailwind 类名
- Console:查看
console.log输出、错误信息 - Network:查看 API 请求、资源加载
- Lighthouse:性能分析、SEO 检查
- React DevTools:查看组件树、Props、Hooks(需安装扩展)
安装 React DevTools:
Chrome Web Store → 搜索 "React Developer Tools" → 安装
3. 调试 Server Components
Server Components 在服务端执行,无法使用 Chrome DevTools 调试。推荐使用 console.log:
// app/page.tsx
async function getData() {
console.log('[Server] Fetching data...');
const res = await fetch('https://api.example.com/posts');
return res.json();
}
export default async function Home() {
console.log('[Server] Rendering Home component');
const data = await getData();
return <div>{/* ... */}</div>;
}
查看终端输出:
[Server] Rendering Home component
[Server] Fetching data...
4. 调试 Client Components
Client Components 在浏览器执行,可以使用 Chrome DevTools:
// app/counter.tsx
"use client";
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
console.log('[Client] Counter rendered, count:', count);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}
打开 Chrome DevTools → Console,查看输出。
2.4 编写第一个页面:app/page.tsx
默认首页
打开 app/page.tsx,查看默认代码:
import Image from "next/image";
export default function Home() {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
app/page.tsx
</code>
.
</li>
<li>Save and see your changes instantly.</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
</main>
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org →
</a>
</footer>
</div>
);
}
简化首页
将 app/page.tsx 替换为更简洁的版本:
// app/page.tsx
export default function Home() {
return (
<main className="min-h-screen p-8 bg-gradient-to-br from-blue-50 to-indigo-100">
<div className="max-w-4xl mx-auto">
<h1 className="text-5xl font-bold text-gray-900 mb-4">
Welcome to Next.js 15
</h1>
<p className="text-xl text-gray-600 mb-8">
这是你的第一个 Next.js 项目。让我们开始构建吧!
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-white p-6 rounded-lg shadow-md">
<h2 className="text-2xl font-semibold mb-2">📚 学习</h2>
<p className="text-gray-600">
阅读 Next.js 官方文档,学习 App Router、Server Components 等核心概念。
</p>
</div>
<div className="bg-white p-6 rounded-lg shadow-md">
<h2 className="text-2xl font-semibold mb-2">🚀 构建</h2>
<p className="text-gray-600">
开始构建你的第一个全栈应用,包括页面、API、数据库。
</p>
</div>
<div className="bg-white p-6 rounded-lg shadow-md">
<h2 className="text-2xl font-semibold mb-2">🎨 设计</h2>
<p className="text-gray-600">
使用 TailwindCSS 快速构建美观的 UI 组件。
</p>
</div>
<div className="bg-white p-6 rounded-lg shadow-md">
<h2 className="text-2xl font-semibold mb-2">🌍 部署</h2>
<p className="text-gray-600">
将应用部署到 Vercel,分享给全世界。
</p>
</div>
</div>
</div>
</main>
);
}
保存后,浏览器自动刷新,显示新的首页。
创建子页面
创建 app/about/page.tsx:
// app/about/page.tsx
export default function About() {
return (
<main className="min-h-screen p-8 bg-gray-50">
<div className="max-w-4xl mx-auto">
<h1 className="text-4xl font-bold text-gray-900 mb-4">
关于我们
</h1>
<p className="text-lg text-gray-600 mb-6">
这是一个使用 Next.js 15 构建的示例项目。
</p>
<div className="bg-white p-6 rounded-lg shadow-md">
<h2 className="text-2xl font-semibold mb-4">项目特点</h2>
<ul className="space-y-2 text-gray-700">
<li>✅ 使用 App Router(推荐)</li>
<li>✅ TypeScript 类型安全</li>
<li>✅ TailwindCSS 原子化样式</li>
<li>✅ Server Components 性能优化</li>
<li>✅ Server Actions 简化后端</li>
</ul>
</div>
</div>
</main>
);
}
访问 http://localhost:3000/about,查看关于页面。
2.5 编写第一个 API:app/api/route.ts
创建 API 路由
创建 app/api/hello/route.ts:
// app/api/hello/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json({
message: 'Hello from Next.js API!',
timestamp: new Date().toISOString(),
});
}
export async function POST(request: Request) {
const body = await request.json();
return NextResponse.json({
message: 'POST request received',
data: body,
});
}
测试 API
使用浏览器访问 http://localhost:3000/api/hello,查看 GET 响应:
{
"message": "Hello from Next.js API!",
"timestamp": "2025-11-16T01:02:31.000Z"
}
使用 curl 测试 POST:
curl -X POST http://localhost:3000/api/hello \
-H "Content-Type: application/json" \
-d '{"name": "Alice"}'
响应:
{
"message": "POST request received",
"data": {
"name": "Alice"
}
}
在页面中调用 API
修改 app/page.tsx,添加 API 调用:
// app/page.tsx
"use client";
import { useState } from 'react';
export default function Home() {
const [apiResponse, setApiResponse] = useState<string>('');
const fetchHello = async () => {
const res = await fetch('/api/hello');
const data = await res.json();
setApiResponse(JSON.stringify(data, null, 2));
};
return (
<main className="min-h-screen p-8 bg-gradient-to-br from-blue-50 to-indigo-100">
<div className="max-w-4xl mx-auto">
<h1 className="text-5xl font-bold text-gray-900 mb-4">
Welcome to Next.js 15
</h1>
<button
onClick={fetchHello}
className="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition mb-4"
>
调用 API
</button>
{apiResponse && (
<pre className="bg-gray-900 text-green-400 p-4 rounded-lg overflow-x-auto">
{apiResponse}
</pre>
)}
</div>
</main>
);
}
点击按钮,查看 API 响应。
2.6 引入 TailwindCSS 并配置设计系统基础
TailwindCSS 已内置
如果你在安装时选择了 Tailwind CSS,它已经配置好了。查看相关文件:
tailwind.config.ts
import type { Config } from "tailwindcss";
export default {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
},
},
},
plugins: [],
} satisfies Config;
app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--background: #ffffff;
--foreground: #171717;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
}
扩展设计系统
修改 tailwind.config.ts,添加自定义颜色、字体、间距:
import type { Config } from "tailwindcss";
export default {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
},
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
mono: ['Fira Code', 'monospace'],
},
spacing: {
'18': '4.5rem',
'88': '22rem',
},
borderRadius: {
'xl': '1rem',
'2xl': '1.5rem',
},
},
},
plugins: [],
} satisfies Config;
使用自定义样式:
<h1 className="text-4xl font-bold text-primary-600">
自定义主题色
</h1>
<p className="font-mono text-sm bg-gray-100 p-2 rounded-xl">
等宽字体 + 圆角
</p>
创建通用组件
创建 components/Button.tsx:
// components/Button.tsx
import { ButtonHTMLAttributes } from 'react';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline';
size?: 'sm' | 'md' | 'lg';
}
export default function Button({
children,
variant = 'primary',
size = 'md',
className = '',
...props
}: ButtonProps) {
const baseStyles = 'rounded-lg font-semibold transition-colors';
const variantStyles = {
primary: 'bg-primary-600 text-white hover:bg-primary-700',
secondary: 'bg-gray-600 text-white hover:bg-gray-700',
outline: 'border-2 border-primary-600 text-primary-600 hover:bg-primary-50',
};
const sizeStyles = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
};
return (
<button
className={`${baseStyles} ${variantStyles[variant]} ${sizeStyles[size]} ${className}`}
{...props}
>
{children}
</button>
);
}
使用组件:
import Button from '@/components/Button';
<Button variant="primary" size="lg">
主要按钮
</Button>
<Button variant="outline" size="sm">
边框按钮
</Button>
2.7 一键发布到 Vercel:从本地到线上的完整流程
初始化 Git 仓库
git init
git add .
git commit -m "Initial commit: Next.js 15 project"
推送到 GitHub
- 在 GitHub 创建新仓库:https://github.com/new
- 推送代码:
git remote add origin https://github.com/your-username/my-nextjs-app.git
git branch -M main
git push -u origin main
部署到 Vercel
方式 1:通过 Vercel Dashboard
- 访问 https://vercel.com,登录账号
- 点击 “Add New Project”
- 导入 GitHub 仓库
my-nextjs-app - 配置:
- Framework Preset: Next.js
- Build Command:
pnpm build(自动检测) - Output Directory:
.next(自动检测)
- 点击 “Deploy”
- 等待 1-2 分钟,部署完成
方式 2:通过 Vercel CLI
npm i -g vercel
vercel
交互式配置:
? Set up and deploy "~\my-nextjs-app"? [Y/n] y
? Which scope do you want to deploy to? your-account
? Link to existing project? [y/N] n
? What's your project's name? my-nextjs-app
? In which directory is your code located? ./
? Want to override the settings? [y/N] n
部署完成后,Vercel 会给你一个 URL,如:
https://my-nextjs-app-xyz.vercel.app
自定义域名
- 在 Vercel Dashboard,进入项目设置
- 点击 “Domains”
- 添加域名:
myapp.com - 按照提示配置 DNS:
- 添加 A 记录:
@→76.76.21.21 - 添加 CNAME 记录:
www→cname.vercel-dns.com
- 添加 A 记录:
环境变量配置
在 Vercel Dashboard → Settings → Environment Variables,添加:
DATABASE_URL=postgresql://...
NEXT_PUBLIC_API_URL=https://api.example.com
或在本地创建 .env.local:
DATABASE_URL=postgresql://...
NEXT_PUBLIC_API_URL=https://api.example.com
注意:以
NEXT_PUBLIC_开头的环境变量会暴露到客户端,其他变量只在服务端可用。
本章小结
Key Takeaways
- 使用
create-next-app快速初始化项目,选择 TypeScript + ESLint + TailwindCSS + App Router - 目录结构清晰:
app/存放页面和布局,public/存放静态资源,components/存放组件 - 开发体验优秀:HMR 热更新、TypeScript 类型提示、Tailwind 自动补全
- 第一个页面:
app/page.tsx是根页面,app/about/page.tsx是/about页面 - 第一个 API:
app/api/hello/route.ts定义 GET / POST 等 HTTP 方法 - TailwindCSS 可定制:扩展
tailwind.config.ts添加自定义颜色、字体、间距 - Vercel 一键部署:Git 推送到 GitHub,Vercel 自动部署,支持自定义域名
下一步
在下一章,我们将深入 App Router 的目录结构,理解 Layouts、Pages、Templates 的区别,以及嵌套路由、并行路由、拦截路由等高级特性。
参考资料
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。