2分钟理解 Next.js:终极 React 框架!

可用语言 :

Next.js 将 React 转变为一套完整的现代 Web 开发工具套件。其最新版本带来了革命性的功能,改变了我们构建 Web 应用的方式。让我们来看看为什么 Next.js 变得如此不可或缺!

Next.js 15 的亮点 🎯

  • App Router - 基于文件系统的现代路由系统
  • Server Components - 默认服务器端渲染的 React 组件
  • Server Actions - 直接从组件中进行服务器端变更
  • Streaming - 渐进式页面加载实现流畅的用户体验
  • 智能缓存 - 自动数据缓存
  • 零配置 - 内置优化,开箱即用

快速开始 ⚙️

bash
1# 创建新项目
2npx create-next-app@latest my-app --typescript --tailwind --app
3
4# 启动开发服务器
5cd my-app
6npm run dev

现代化架构 📂

App Router 引入了更直观的项目结构:

plaintext
1my-app/
2├── app/
3│ ├── layout.tsx # 根布局
4│ ├── page.tsx # 主页 (/)
5│ ├── loading.tsx # 全局加载
6│ ├── error.tsx # 错误处理
7│ ├── not-found.tsx # 404页面
8│ ├── blog/
9│ │ ├── page.tsx # /blog
10│ │ └── [slug]/
11│ │ └── page.tsx # /blog/:slug
12│ └── api/
13│ └── route.ts # API路由
14├── components/
15│ ├── server/ # 服务器组件
16│ ├── client/ # 客户端组件
17│ └── ui/ # UI组件
18└── lib/
19 └── actions.ts # 服务器操作

Server Components ⚡️

组件默认在服务器端渲染以获得更好的性能:

typescript
1// app/blog/page.tsx
2import { PostList } from '@/components/server/PostList';
3import { Suspense } from 'react';
4
5export default async function BlogPage() {
6 return (
7 <main className="container mx-auto px-4 py-8">
8 <h1 className="text-4xl font-bold mb-8">我们的博客</h1>
9 <Suspense fallback={<p>文章加载中...</p>}>
10 <PostList />
11 </Suspense>
12 </main>
13 );
14}
15
16// components/server/PostList.tsx
17async function getPosts() {
18 const posts = await db.post.findMany();
19 return posts;
20}
21
22export async function PostList() {
23 const posts = await getPosts();
24
25 return (
26 <div className="grid gap-6 md:grid-cols-2">
27 {posts.map(post => (
28 <article key={post.id} className="p-4 rounded-lg border">
29 <h2 className="text-xl font-semibold">{post.title}</h2>
30 <p className="mt-2 text-gray-600">{post.excerpt}</p>
31 </article>
32 ))}
33 </div>
34 );
35}

Client Components 🔄

需要交互性的组件:

typescript
1// components/client/SearchBar.tsx
2'use client';
3
4import { useState } from 'react';
5import { useRouter } from 'next/navigation';
6
7export function SearchBar() {
8 const [query, setQuery] = useState('');
9 const router = useRouter();
10
11 function handleSearch(e: React.FormEvent) {
12 e.preventDefault();
13 router.push(`/search?q=${encodeURIComponent(query)}`);
14 }
15
16 return (
17 <form onSubmit={handleSearch} className="flex gap-2">
18 <input
19 type="search"
20 value={query}
21 onChange={(e) => setQuery(e.target.value)}
22 placeholder="搜索..."
23 className="px-4 py-2 rounded border"
24 />
25 <button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded">
26 搜索
27 </button>
28 </form>
29 );
30}

Server Actions ⚡️

直接从组件中进行服务器端变更:

typescript
1// lib/actions.ts
2'use server';
3
4import { revalidatePath } from 'next/cache';
5import { redirect } from 'next/navigation';
6import { z } from 'zod';
7
8const PostSchema = z.object({
9 title: z.string().min(3, "标题至少需要3个字符"),
10 content: z.string().min(10, "内容至少需要10个字符")
11});
12
13export async function createPost(formData: FormData) {
14 const data = {
15 title: formData.get('title'),
16 content: formData.get('content')
17 };
18
19 const result = PostSchema.safeParse(data);
20
21 if (!result.success) {
22 return { error: result.error.flatten() };
23 }
24
25 const post = await db.post.create({
26 data: result.data
27 });
28
29 revalidatePath('/blog');
30 redirect('/blog');
31}
32
33// app/blog/new/page.tsx
34import { createPost } from '@/lib/actions';
35
36export default function NewPostPage() {
37 return (
38 <form action={createPost} className="space-y-4 max-w-lg mx-auto">
39 <div>
40 <label htmlFor="title" className="block text-sm font-medium">
41 标题
42 </label>
43 <input
44 type="text"
45 name="title"
46 id="title"
47 required
48 className="mt-1 block w-full rounded-md border-gray-300"
49 />
50 </div>
51 <div>
52 <label htmlFor="content" className="block text-sm font-medium">
53 内容
54 </label>
55 <textarea
56 name="content"
57 id="content"
58 required
59 rows={5}
60 className="mt-1 block w-full rounded-md border-gray-300"
61 />
62 </div>
63 <button
64 type="submit"
65 className="w-full py-2 px-4 bg-blue-500 text-white rounded-md"
66 >
67 发布
68 </button>
69 </form>
70 );
71}

现代 API 路由 🛠️

typescript
1// app/api/posts/route.ts
2import { NextResponse } from 'next/server';
3import { z } from 'zod';
4
5const PostSchema = z.object({
6 title: z.string().min(3),
7 content: z.string().min(10),
8});
9
10export async function GET(request: Request) {
11 const { searchParams } = new URL(request.url);
12 const query = searchParams.get('q');
13
14 const posts = await db.post.findMany({
15 where: query
16 ? {
17 OR: [
18 { title: { contains: query } },
19 { content: { contains: query } },
20 ],
21 }
22 : undefined,
23 });
24
25 return NextResponse.json({ posts });
26}
27
28export async function POST(request: Request) {
29 try {
30 const body = await request.json();
31 const result = PostSchema.safeParse(body);
32
33 if (!result.success) {
34 return NextResponse.json(
35 { error: result.error.flatten() },
36 { status: 400 },
37 );
38 }
39
40 const post = await db.post.create({
41 data: result.data,
42 });
43
44 return NextResponse.json(post, { status: 201 });
45 } catch (error) {
46 return NextResponse.json({ error: '服务器错误' }, { status: 500 });
47 }
48}

优化和最佳实践 🚀

1. Suspense 流式传输

typescript
1import { Suspense } from 'react';
2import { PostList } from '@/components/server/PostList';
3import { SidebarNav } from '@/components/server/SidebarNav';
4import { Loading } from '@/components/ui/Loading';
5
6export default function BlogLayout({
7 children
8}: {
9 children: React.ReactNode
10}) {
11 return (
12 <div className="flex gap-8">
13 <Suspense fallback={<Loading />}>
14 <SidebarNav />
15 </Suspense>
16 <main className="flex-1">
17 <Suspense fallback={<Loading />}>
18 {children}
19 </Suspense>
20 </main>
21 <aside className="w-64">
22 <Suspense fallback={<Loading />}>
23 <PostList type="recent" />
24 </Suspense>
25 </aside>
26 </div>
27 );
28}

2. 错误处理

typescript
1// app/error.tsx
2'use client';
3
4export default function Error({
5 error,
6 reset
7}: {
8 error: Error & { digest?: string };
9 reset: () => void;
10}) {
11 return (
12 <div className="flex flex-col items-center justify-center min-h-[400px]">
13 <h2 className="text-2xl font-bold mb-4">
14 发生错误!
15 </h2>
16 <button
17 onClick={() => reset()}
18 className="px-4 py-2 bg-blue-500 text-white rounded"
19 >
20 重试
21 </button>
22 </div>
23 );
24}

3. 优化的图片加载

typescript
1import Image from 'next/image';
2
3export function Avatar({ src, alt }: { src: string; alt: string }) {
4 return (
5 <div className="relative w-10 h-10">
6 <Image
7 src={src}
8 alt={alt}
9 fill
10 sizes="40px"
11 className="rounded-full object-cover"
12 priority
13 />
14 </div>
15 );
16}

深入学习 🎈

总结 ✨

Next.js 15 在 React Web 开发中代表了重大进步:

  • 服务器优先:默认服务器端组件实现更好的性能
  • 简单性:基于文件的直观架构
  • 性能:自动优化和流式传输
  • 改进的开发体验:Server Actions 和现代 API 路由

App Router、Server Components 和 Server Actions 的组合为构建现代、高性能和可维护的 Web 应用提供了坚实的基础。


现在你已经了解了 Next.js 14 的基础知识,你已经准备好创建现代和高性能的 Web 应用了!请查看官方文档以深入了解每个概念。祝开发愉快!🚀

分享这篇文章


Sébastien Timoner

Sébastien TIMONER

首席开发工程师
定制开发专家
Aix-en-Provence, France

作为 Web 开发和技术团队管理专家,我专注于创建和优化高性能数字解决方案。通过对 React.js、Node.js、TypeScript、Symfony 和 IoT 领域的 Zephyr OS 等现代技术的深入掌握,我确保在 offroadLabs 中为各行业企业的复杂 SaaS 和 IoT 项目从设计到生产的成功。

offroadLabs,我提供定制开发服务,结合技术专长和协作方法。无论是创建创新的 SaaS 解决方案、使用 Zephyr OS 开发 IoT 系统、现代化现有应用程序还是支持团队的专业成长,我都致力于提供稳健且高效的解决方案,适应每个项目的具体需求。

我可以在艾克斯普罗旺斯周边或完全远程工作。