3.2 Next.js核心概念
Next.js 是 React 的全栈框架,让你能快速构建现代化的 Web 应用。它解决了传统 React 开发中的很多痛点。
学习重点:理解 Next.js 的核心概念,特别是文件系统路由、服务端渲染、静态生成等特性。
为什么选择 Next.js?
Next.js 的优势
| 特性 | 传统 React | Next.js | 说明 |
|---|---|---|---|
| 路由 | 需要额外配置 | 文件系统自动路由 | 文件即路由 |
| SEO | 客户端渲染,SEO差 | 服务端渲染,SEO友好 | 搜索引擎优化 |
| 性能 | 首屏加载慢 | 自动代码分割,性能好 | 用户体验 |
| 部署 | 配置复杂 | 一键部署 | Vercel 等平台支持 |
| API | 需要后端服务 | 内置 API 路由 | 全栈开发 |
AI 编程优势:Next.js 的约定优于配置理念,让 AI 更容易理解和生成符合规范的代码。
Next.js 项目结构
基础项目结构
my-nextjs-app/
├── app/ # App Router (Next.js 13+)
│ ├── globals.css # 全局样式
│ ├── layout.tsx # 根布局
│ ├── page.tsx # 首页
│ ├── loading.tsx # 加载组件
│ ├── error.tsx # 错误页面
│ └── about/ # 路由文件夹
│ └── page.tsx # /about 页面
├── public/ # 静态资源
│ ├── images/
│ └── favicon.ico
├── components/ # 组件文件夹
├── lib/ # 工具函数
├── styles/ # 样式文件
├── package.json
└── next.config.js # Next.js 配置App Router vs Pages Router
App Router (Next.js 13+)
特点:
- 基于文件系统的路由
- 支持嵌套布局
- 服务端组件默认
- 并行路由和拦截路由
文件结构:
app/
├── layout.tsx # 根布局
├── page.tsx # 首页
├── loading.tsx # 加载状态
├── error.tsx # 错误处理
├── about/
│ ├── layout.tsx # about 布局
│ └── page.tsx # /about 页面
└── api/
└── users/
└── route.ts # /api/users APIPages Router (传统)
特点:
- 基于文件的路由
- 客户端组件默认
- 需要手动配置布局
- 成熟稳定,生态丰富
文件结构:
pages/
├── _app.tsx # 根组件
├── _document.tsx # 文档组件
├── index.tsx # 首页
├── about.tsx # /about 页面
└── api/
└── users.ts # /api/users API页面和路由
基础路由创建
创建步骤
- 创建文件
app/
├── about/
│ └── page.tsx # 创建 about 页面
├── contact/
│ └── page.tsx # 创建 contact 页面
└── blog/
└── [slug]/
└── page.tsx # 动态路由页面- 编写页面组件
// app/about/page.tsx
export default function AboutPage() {
return (
<div>
<h1>关于我们</h1>
<p>这里是关于页面的内容...</p>
</div>
);
}- 动态路由参数
// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
return (
<div>
<h1>博客文章:{params.slug}</h1>
<p>这里是文章内容...</p>
</div>
);
}布局系统
// app/layout.tsx - 根布局
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="zh-CN">
<body>
<header>
<nav>全局导航</nav>
</header>
<main>{children}</main>
<footer>全局页脚</footer>
</body>
</html>
);
}
// app/about/layout.tsx - about 页面布局
export default function AboutLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<section>
<aside>关于页面侧边栏</aside>
<article>{children}</article>
</section>
);
}数据获取方式
服务端组件 vs 客户端组件
服务端组件
特点:
- 在服务器端渲染
- 直接访问数据库
- 不能使用交互功能
- 性能更好
// 默认为服务端组件
async function getUserData() {
const res = await fetch('https://api.example.com/users');
return res.json();
}
export default async function UserList() {
const users = await getUserData();
return (
<div>
<h1>用户列表</h1>
{users.map(user => (
<div key={user.id}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
))}
</div>
);
}客户端组件
特点:
- 在浏览器端运行
- 支持交互功能
- 可以使用 hooks
- 需要 ‘use client’ 指令
'use client'; // 声明为客户端组件
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>当前计数:{count}</p>
<button onClick={() => setCount(count + 1)}>
增加
</button>
</div>
);
}数据获取方法
// 静态生成 - 构建时获取数据
async function getStaticPosts() {
const posts = await fetch('https://api.example.com/posts');
return posts.json();
}
export default async function BlogPage() {
const posts = await getStaticPosts();
return (
<div>
<h1>博客列表</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
// 动态渲染 - 请求时获取数据
async function getDynamicPost(id: string) {
const res = await fetch(`https://api.example.com/posts/${id}`, {
cache: 'no-store' // 禁用缓存
});
return res.json();
}
export default async function PostPage({ params }: { params: { id: string } }) {
const post = await getDynamicPost(params.id);
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}API 路由
创建 API 端点
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
// GET 请求处理
export async function GET(request: NextRequest) {
try {
// 从数据库获取用户数据
const users = [
{ id: 1, name: '张三', email: '[email protected]' },
{ id: 2, name: '李四', email: '[email protected]' }
];
return NextResponse.json({ users });
} catch (error) {
return NextResponse.json(
{ error: '获取用户失败' },
{ status: 500 }
);
}
}
// POST 请求处理
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { name, email } = body;
// 验证输入
if (!name || !email) {
return NextResponse.json(
{ error: '姓名和邮箱不能为空' },
{ status: 400 }
);
}
// 创建新用户(示例)
const newUser = {
id: Date.now(),
name,
email,
createdAt: new Date().toISOString()
};
return NextResponse.json(
{ message: '用户创建成功', user: newUser },
{ status: 201 }
);
} catch (error) {
return NextResponse.json(
{ error: '创建用户失败' },
{ status: 500 }
);
}
}动态 API 路由
// app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const userId = params.id;
// 从数据库获取特定用户
const user = { id: userId, name: '用户' + userId };
if (!user) {
return NextResponse.json(
{ error: '用户不存在' },
{ status: 404 }
);
}
return NextResponse.json({ user });
} catch (error) {
return NextResponse.json(
{ error: '获取用户失败' },
{ status: 500 }
);
}
}样式解决方案
全局样式
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--primary-color: #3b82f6;
--secondary-color: #64748b;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
}Tailwind CSS 集成
// 安装 Tailwind CSS
// npm install -D tailwindcss postcss autoprefixer
// npx tailwindcss init -p
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
primary: '#3b82f6',
},
},
},
plugins: [],
};
// 在组件中使用
export default function HomePage() {
return (
<div className="min-h-screen bg-gray-50">
<header className="bg-white shadow-sm">
<nav className="container mx-auto px-4 py-4">
<h1 className="text-2xl font-bold text-primary">
我的网站
</h1>
</nav>
</header>
<main className="container mx-auto px-4 py-8">
<section className="text-center">
<h2 className="text-4xl font-bold mb-4">
欢迎来到我的网站
</h2>
<p className="text-gray-600 mb-8">
这里是网站的主要内容
</p>
<button className="bg-primary text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition-colors">
开始使用
</button>
</section>
</main>
</div>
);
}CSS Modules
// components/Button.module.css
.button {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.primary {
background-color: #3b82f6;
color: white;
}
.primary:hover {
background-color: #2563eb;
}
.secondary {
background-color: #f3f4f6;
color: #374151;
}
.secondary:hover {
background-color: #e5e7eb;
}
// components/Button.tsx
import styles from './Button.module.css';
interface ButtonProps {
variant?: 'primary' | 'secondary';
children: React.ReactNode;
onClick?: () => void;
}
export default function Button({ variant = 'primary', children, onClick }: ButtonProps) {
return (
<button
className={`${styles.button} ${styles[variant]}`}
onClick={onClick}
>
{children}
</button>
);
}性能优化
代码分割
// 自动代码分割
import dynamic from 'next/dynamic';
// 动态导入组件
const DynamicComponent = dynamic(() => import('../components/HeavyComponent'), {
loading: () => <p>加载中...</p>,
ssr: false // 仅在客户端渲染
});
export default function HomePage() {
return (
<div>
<h1>首页</h1>
<DynamicComponent />
</div>
);
}
// 按需导入
function HomePage() {
const [showModal, setShowModal] = useState(false);
const Modal = useMemo(() =>
dynamic(() => import('../components/Modal')), []
);
return (
<div>
<h1>首页</h1>
<button onClick={() => setShowModal(true)}>
打开模态框
</button>
{showModal && <Modal />}
</div>
);
}图片优化
import Image from 'next/image';
export default function ImageExample() {
return (
<div>
{/* 优化的图片 */}
<Image
src="/hero-image.jpg"
alt="Hero 图片"
width={800}
height={600}
priority // 重要图片优先加载
placeholder="blur" // 模糊占位符
className="rounded-lg"
/>
{/* 响应式图片 */}
<Image
src="/responsive-image.jpg"
alt="响应式图片"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
style={{ objectFit: 'cover' }}
className="rounded-lg"
/>
</div>
);
}部署配置
环境变量
# .env.local - 本地环境变量
DATABASE_URL="postgresql://username:password@localhost:5432/mydb"
NEXTAUTH_SECRET="your-secret-key"
NEXTAUTH_URL="http://localhost:3000"
# .env.production - 生产环境变量
DATABASE_URL="postgresql://username:password@host:5432/productiondb"
NEXTAUTH_SECRET="production-secret"
NEXTAUTH_URL="https://your-domain.com"构建配置
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true, // 启用 App Router
},
images: {
domains: ['example.com', 'cdn.example.com'], // 允许的图片域名
},
env: {
CUSTOM_KEY: process.env.CUSTOM_KEY,
},
async redirects() {
return [
{
source: '/old-path',
destination: '/new-path',
permanent: true,
},
];
},
async rewrites() {
return [
{
source: '/blog/:slug',
destination: '/posts/:slug',
},
];
},
};
module.exports = nextConfig;AI 编程中的 Next.js
AI 生成 Next.js 代码的技巧:
你是一位 Next.js 专家,请帮我创建一个博客应用:
项目要求:
1. 使用 Next.js 13+ App Router
2. 集成 Tailwind CSS
3. 包含文章列表和详情页面
4. 支持文章搜索功能
5. 响应式设计
6. 优化 SEO 和性能
技术栈:
- Next.js 13+ with TypeScript
- Tailwind CSS
- 服务端组件优先
- 静态生成 + 增量静态再生
请提供:
1. 完整的项目结构
2. 核心页面代码
3. API 路由实现
4. 部署配置下一步:掌握了 Next.js 基础后,继续学习 React组件与JSX,深入了解 React 的核心概念。
Last updated on