Next.js 14 完整指南:App Router 深度实践
嗨,我是欧阳瑞。今天来聊聊 Next.js 14,特别是全新的 App Router。作为一个全栈开发者,我最近把几个项目都迁移到了 App Router,体验真的很棒!
为什么升级到 App Router?
Next.js 14 的 App Router 带来了很多令人兴奋的特性:
- 服务端组件(Server Components)默认启用
- 内置数据获取(fetch 自动缓存)
- 简化的路由系统
- 更好的 TypeScript 支持
项目初始化
npx create-next-app@14.0.0 my-app --typescript --tailwind --app cd my-appApp Router 核心概念
1. 路由结构
App Router 采用文件系统路由,目录结构就是路由结构:
app/ ├── layout.tsx # 根布局 ├── page.tsx # 首页 ├── about/ │ └── page.tsx # /about ├── blog/ │ ├── [slug]/ │ │ └── page.tsx # /blog/xxx │ └── page.tsx # /blog � └── layout.tsx # blog 目录的布局2. 服务端组件 vs 客户端组件
服务端组件(默认):
// app/page.tsx - 默认是服务端组件 async function fetchPosts() { const res = await fetch('https://api.example.com/posts', { cache: 'force-cache', // 默认缓存 }); return res.json(); } export default async function Home() { const posts = await fetchPosts(); return ( <div> {posts.map((post) => ( <div key={post.id}>{post.title}</div> ))} </div> ); }客户端组件:需要显式声明
// app/components/InteractiveComponent.tsx 'use client'; import { useState } from 'react'; export default function InteractiveComponent() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(c => c + 1)}> Clicked {count} times </button> ); }3. Layout 布局系统
根布局app/layout.tsx:
import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; import './globals.css'; const inter = Inter({ subsets: ['latin'] }); export const metadata: Metadata = { title: 'My App', description: 'A Next.js 14 App', }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="zh-CN"> <body className={inter.className}> <header className="bg-cyan-500 text-white p-4"> <h1 className="text-xl">My Awesome App</h1> </header> {children} </body> </html> ); }4. 数据获取策略
Next.js 14 提供了多种数据缓存策略:
// 静态生成(默认) const res = await fetch('https://api.example.com/data', { cache: 'force-cache', }); // 动态获取(每次请求都重新获取) const res = await fetch('https://api.example.com/data', { cache: 'no-store', }); // 增量静态重新生成 const res = await fetch('https://api.example.com/data', { next: { revalidate: 60 }, // 60秒重新验证 });5. 动态路由
创建app/blog/[slug]/page.tsx:
interface Params { slug: string; } async function getPost(slug: string) { const res = await fetch(`https://api.example.com/posts/${slug}`); return res.json(); } export default async function PostPage({ params }: { params: Params }) { const post = await getPost(params.slug); return ( <article> <h1 className="text-3xl font-bold mb-4">{post.title}</h1> <p className="text-gray-600">{post.content}</p> </article> ); } // 生成静态路径(可选) export async function generateStaticParams() { const posts = await fetch('https://api.example.com/posts').then(res => res.json()); return posts.map((post) => ({ slug: post.slug, })); }6. 错误处理
创建app/blog/[slug]/error.tsx:
'use client'; export default function Error({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { return ( <div className="text-red-500"> <p>Something went wrong: {error.message}</p> <button onClick={() => reset()}>Try again</button> </div> ); }7. 加载状态
创建app/blog/loading.tsx:
export default function Loading() { return ( <div className="animate-pulse"> <div className="h-8 bg-cyan-200 w-32 mb-4" /> <div className="h-4 bg-cyan-100 w-full mb-2" /> <div className="h-4 bg-cyan-100 w-3/4" /> </div> ); }8. 服务端 Actions
在app/actions.ts中定义:
'use server'; export async function createPost(formData: FormData) { const title = formData.get('title') as string; const content = formData.get('content') as string; const res = await fetch('https://api.example.com/posts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, content }), }); if (!res.ok) { throw new Error('Failed to create post'); } return res.json(); }在组件中使用:
'use client'; import { createPost } from '@/app/actions'; export default function CreatePostForm() { async function handleSubmit(formData: FormData) { await createPost(formData); // 成功后的处理 } return ( <form action={handleSubmit}> <input type="text" name="title" placeholder="Title" /> <textarea name="content" placeholder="Content" /> <button type="submit">Create</button> </form> ); }性能优化技巧
1. 图片优化
import Image from 'next/image'; export default function MyImage() { return ( <Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority // 首屏图片优先加载 placeholder="blur" /> ); }2. 字体优化
import { Inter, Roboto_Mono } from 'next/font/google'; const inter = Inter({ subsets: ['latin'], display: 'swap', }); const mono = Roboto_Mono({ subsets: ['latin'], display: 'swap', });3. 代码分割
Next.js 自动进行代码分割,但你也可以手动控制:
import dynamic from 'next/dynamic'; const HeavyComponent = dynamic(() => import('./HeavyComponent'), { loading: () => <p>Loading...</p>, ssr: false, // 只在客户端渲染 });部署到 Vercel
vercel一键部署,Vercel 会自动配置好所有优化。
总结
Next.js 14 的 App Router 让全栈开发变得更简单、更高效。我的鬃狮蜥 Hash 看着我用 Next.js 快速构建项目,似乎也想学习编程了——不过它现在更专注于晒太阳。
有问题欢迎留言交流,我是欧阳瑞,极客之路,永无止境!
技术栈:Next.js 14 · App Router · React · TypeScript · Tailwind CSS