Next.js

Next.js 15 新特性详解:App Router 完全指南

作者:君知博客团队
34 分钟阅读
#Next.js#React#App Router#服务端组件#性能优化

Next.js 15 新特性详解:App Router 完全指南

Next.js 15 带来了革命性的 App Router 架构,彻底改变了我们构建 React 应用的方式。本文将深入探讨 App Router 的核心概念和最佳实践。

什么是 App Router?

App Router 是 Next.js 13 引入并在 15 版本中完善的新路由系统。它基于 React Server Components,提供了更强大的功能和更好的性能。

主要特性

  1. 服务端组件(Server Components)

    • 默认情况下,所有组件都是服务端组件
    • 减少客户端 JavaScript 体积
    • 直接访问后端资源(数据库、文件系统等)
  2. 客户端组件(Client Components)

    • 使用 'use client' 指令声明
    • 支持交互和状态管理
    • 可以使用 React Hooks
  3. 嵌套布局(Nested Layouts)

    • 支持多层嵌套的布局结构
    • 布局在导航时保持状态
    • 提升用户体验

服务端组件 vs 客户端组件

何时使用服务端组件?

// app/blog/page.tsx
// 默认是服务端组件,无需声明

import { getPosts } from '@/lib/posts'

export default async function BlogPage() {
  // 可以直接在组件中进行数据获取
  const posts = await getPosts()

  return (
    <div>
      <h1>博客文章</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  )
}

适用场景:

  • 数据获取
  • 访问后端资源
  • 保护敏感信息(API 密钥等)
  • 减少客户端 JavaScript

何时使用客户端组件?

// components/Counter.tsx
'use client'

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>
        增加
      </button>
    </div>
  )
}

适用场景:

  • 交互功能(点击、输入等)
  • 使用 React Hooks(useState、useEffect 等)
  • 使用浏览器 API
  • 使用第三方交互库

数据获取最佳实践

1. 服务端数据获取

// app/posts/[id]/page.tsx
async function getPost(id: string) {
  const res = await fetch(`https://api.example.com/posts/${id}`, {
    // Next.js 扩展的 fetch 选项
    next: { revalidate: 3600 } // 每小时重新验证
  })
  return res.json()
}

export default async function PostPage({ params }: { params: { id: string } }) {
  const post = await getPost(params.id)

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  )
}

2. 并行数据获取

// 同时获取多个数据源
async function getData() {
  const [posts, categories, tags] = await Promise.all([
    getPosts(),
    getCategories(),
    getTags(),
  ])

  return { posts, categories, tags }
}

3. 流式渲染(Streaming)

import { Suspense } from 'react'

export default function Page() {
  return (
    <div>
      <h1>我的页面</h1>

      {/* 立即显示,不等待数据 */}
      <Suspense fallback={<div>加载中...</div>}>
        <SlowComponent />
      </Suspense>
    </div>
  )
}

路由和导航

1. 文件系统路由

app/
├── page.tsx              # /
├── about/
│   └── page.tsx          # /about
├── blog/
│   ├── page.tsx          # /blog
│   └── [slug]/
│       └── page.tsx      # /blog/:slug
└── api/
    └── posts/
        └── route.ts      # /api/posts

2. 动态路由

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getPosts()

  return posts.map((post) => ({
    slug: post.slug,
  }))
}

export default async function PostPage({
  params,
}: {
  params: { slug: string }
}) {
  const post = await getPost(params.slug)
  return <article>{/* ... */}</article>
}

3. 编程式导航

'use client'

import { useRouter } from 'next/navigation'

export default function MyComponent() {
  const router = useRouter()

  return (
    <button onClick={() => router.push('/blog')}>
      前往博客
    </button>
  )
}

性能优化技巧

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. 字体优化

// app/layout.tsx
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export default function RootLayout({ children }) {
  return (
    <html lang="zh-CN" className={inter.className}>
      <body>{children}</body>
    </html>
  )
}

3. 代码分割

// 动态导入,减少初始加载体积
import dynamic from 'next/dynamic'

const HeavyComponent = dynamic(() => import('@/components/HeavyComponent'), {
  loading: () => <p>加载中...</p>,
  ssr: false, // 禁用服务端渲染
})

SEO 优化

1. 元数据配置

// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'

export async function generateMetadata({
  params,
}: {
  params: { slug: string }
}): Promise<Metadata> {
  const post = await getPost(params.slug)

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
  }
}

2. 结构化数据

export default function PostPage({ post }) {
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'BlogPosting',
    headline: post.title,
    datePublished: post.date,
    author: {
      '@type': 'Person',
      name: post.author,
    },
  }

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <article>{/* ... */}</article>
    </>
  )
}

常见问题和解决方案

1. "use client" 边界问题

问题: 不知道在哪里添加 'use client'

解决方案:

  • 从叶子组件开始添加
  • 只在需要交互的组件中使用
  • 尽可能保持服务端组件

2. 数据获取错误处理

// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'

export default async function PostPage({ params }) {
  const post = await getPost(params.slug)

  if (!post) {
    notFound() // 显示 404 页面
  }

  return <article>{/* ... */}</article>
}

3. 环境变量使用

// 服务端组件 - 可以使用所有环境变量
const apiKey = process.env.API_KEY

// 客户端组件 - 只能使用 NEXT_PUBLIC_ 前缀的变量
const publicKey = process.env.NEXT_PUBLIC_API_KEY

总结

Next.js 15 的 App Router 为我们提供了强大的工具来构建高性能的 React 应用。关键要点:

  1. 默认使用服务端组件,只在需要时使用客户端组件
  2. 充分利用流式渲染,提升用户体验
  3. 合理使用缓存策略,优化性能
  4. 重视 SEO 配置,提升搜索排名

通过掌握这些概念和最佳实践,你将能够构建出快速、可维护的 Next.js 应用。

相关资源


标签: #Next.js #React #AppRouter #服务端组件 #性能优化

发布日期: 2025-02-10

作者: 君知博客团队