前端开发··2 阅读·预计 15 分钟

Next.js App Router 实战:让你的 React 项目快到飞起的 10 个核心技巧

如果你还在用 Pages Router,是时候升级了。Next.js 13 引入的 App Router 不仅仅是一个路由器的升级,它代表了一种全新的 React 开发范式。本文将从实战角度出发,分享 10 个让项目性能飙升的核心技巧。

1. 理解 Server Components vs Client Components

这是 App Router 最核心的概念,也是最容易踩坑的地方。

// 服务端组件 - 默认类型,不打包到客户端
async function ServerComponent() {
  const data = await fetchData(); // 直接在服务器执行
  return <div>{data.title}</div>;
}

// 客户端组件 - 需要交互时使用
'use client';
function ClientComponent() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

原则:默认使用 Server Components,只在真正需要客户端交互时才加 'use client'。这能显著减少 JS bundle 大小。

2. 巧用 streaming 实现即时加载

传统 SSR 是等全部数据准备好才返回页面,现在你可以流式返回:

// app/page.tsx - 并行加载数据,但流式返回
import { Suspense } from 'react';
import { ProductList, ProductReviews } from './components';

export default function Page() {
  return (
    <main>
      <h1>商品详情</h1>
      <Suspense fallback={<ProductSkeleton />}>
        <ProductList />
      </Suspense>
      <Suspense fallback={<ReviewsSkeleton />}>
        <ProductReviews />
      </Suspense>
    </main>
  );
}

关键点:两个组件可以并行加载,各自独立流式渲染,用户不用等待所有数据都准备好才能看到内容。

3. 深度利用 React Cache 减少重复请求

同一个数据在不同组件要用?用 React Cache 缓存起来:

import { cache } from 'react';

export const getUser = cache(async (userId: string) => {
  const res = await fetch(\`/api/users/\${userId}\`);
  return res.json();
});

// 在多个组件中使用,只发一次请求
function UserProfile({ userId }: { userId: string }) {
  const user = getUser(userId); // 首次调用,发请求
  // ...
}

function UserAvatar({ userId }: { userId: string }) {
  const user = getUser(userId); // 使用缓存,不再发请求
  // ...
}

4. Route Handler 的正确打开方式

App Router 的 API 路由改成了 Route Handler,放在 app/api/ 目录下:

// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const page = searchParams.get('page') || '1';
  
  const posts = await fetchPosts(+page);
  
  return NextResponse.json({ posts });
}

export async function POST(request: NextRequest) {
  const body = await request.json();
  const post = await createPost(body);
  
  return NextResponse.json(post, { status: 201 });
}

实战 tip:Route Handler 支持 Web Standard Request/Response,用起来非常自然。

5. 充分利用 Next.js Image 组件

图片优化是性能大头,Next.js 内置的 Image 组件帮你搞定一切:

import Image from 'next/image';

function OptimizedImage() {
  return (
    <Image
      src="/product.jpg"
      alt="产品图"
      width={800}
      height={600}
      // 自动生成 WebP/AVIF根据浏览器选择最优格式
      // 自动懒加载
      // 防止布局偏移CLSplaceholder="blur"
      blurDataURL="data:image/jpeg;base64,..." // 模糊占位图
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
    />
  );
}

实测:一张 500KB 的 PNG 用 Image 组件后可能只有 30KB。

6. 使用 Server Actions 替代 API 调用

还在写 API 路由?Server Actions 让前后端调用更简洁:

// app/actions.ts
'use server';

export async function createPost(formData: FormData) {
  const title = formData.get('title');
  const content = formData.get('content');
  
  // 直接操作数据库,不需要 API 路由
  const post = await db.post.create({
    title: title as string,
    content: content as string
  });
  
  return post;
}
// app/page.tsx - 客户端调用
'use client';
import { createPost } from './actions';

function PostForm() {
  async function handleSubmit(formData: FormData) {
    'use server'; // 服务端执行
    await createPost(formData);
  }
  
  return (
    <form action={handleSubmit}>
      <input name="title" />
      <textarea name="content" />
      <button type="submit">发布</button>
    </form>
  );
}

7. 掌握 layout.tsx 的正确用法

Layout 是 App Router 最强大的特性之一:

// app/layout.tsx - 根布局
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <nav>导航栏</nav>
        {children}
        <footer>页脚</footer>
      </body>
    </html>
  );
}

// app/dashboard/layout.tsx - 仪表盘布局
export default function DashboardLayout({ 
  children,
  sidebar 
}: { 
  children: React.ReactNode;
  sidebar: React.ReactNode;
}) {
  return (
    <div className="dashboard">
      {sidebar}
      <main>{children}</main>
    </div>
  );
}

关键理解:Layout 不会在导航时重新渲染,只渲染变化的 children。这对保持状态、减少重排很重要。

8. 正确使用 middleware 做权限控制

Middleware 是请求到达页面之前的守门员:

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const token = request.cookies.get('token');
  
  // 未登录且访问受保护路由
  if (!token && !request.nextUrl.pathname.startsWith('/login')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  
  // 已登录访问登录页
  if (token && request.nextUrl.pathname.startsWith('/login')) {
    return NextResponse.redirect(new URL('/dashboard', request.url));
  }
  
  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

优势:在页面渲染之前就完成权限判断,比客户端跳转体验好一百倍。

9. 预渲染策略:generateStaticParams

动态路由也能静态生成:

// app/posts/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getAllPosts();
  
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

// 或者增量静态生成
export const dynamicParams = true; // 允许生成新页面

export async function generateStaticParams() {
  // 只预渲染热门文章,其他按需生成
  const popularPosts = await getPopularPosts();
  return popularPosts.map((post) => ({ slug: post.slug }));
}

这样热门文章在构建时就生成好,访问时直接返回 HTML,速度飞快。

10. 监控和优化 Core Web Vitals

用 Next.js 的 Analytics 监控性能:

// app/components/Analytics.tsx
'use client';

import { Analytics } from '@vercel/analytics/react';

export function AnalyticsWrapper() {
  return <Analytics />;
}

// app/layout.tsx
import { AnalyticsWrapper } from './components/Analytics';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        {children}
        <AnalyticsWrapper />
      </body>
    </html>
  );
}

同时在 next.config.js 中配置实验性功能:

module.exports = {
  experimental: {
    optimizePackageImports: ['lucide-react', 'radix-ui'],
  },
};

总结

App Router 带来的不只是一个新的路由系统,而是一种新的思维模式:

  1. 默认服务端:能服务端处理的就不要发到客户端
  2. 流式渲染:不要让用户等待所有资源
  3. 缓存为王:充分利用 Next.js 的缓存机制
  4. 零客户端 JS:能用 HTML/CSS 解决的就不要用 JS

掌握这些技巧,你的 React 项目性能会提升显著。在实际项目中,建议先从简单场景开始尝试,逐步迁移,积累经验后再大规模应用。

记住:性能优化是一个持续的过程,不要为了优化而优化,用户体验才是最终目标。

0 评论

评论区

登录 后参与评论