Q2: App Router vs Pages Router:为什么 Next.js 要推出 App Router?它带来了哪些根本性的变革?(基于 React

36 阅读2分钟

Next.js 面试题详细答案 - Q2

Q2: App Router vs Pages Router:为什么 Next.js 要推出 App Router?它带来了哪些根本性的变革?(基于 React Server Components、布局、流式传输等)

为什么推出 App Router

Pages Router 的局限性
  1. 客户端渲染为主

    // Pages Router - 客户端组件
    function HomePage() {
      const [data, setData] = useState(null)
      useEffect(() => {
        fetch('/api/data').then((res) => setData(res))
      }, [])
      return <div>{data}</div>
    }
    
  2. 布局嵌套复杂

    // 需要手动处理布局嵌套
    function Layout({ children }) {
      return (
        <div>
          <Header />
          {children}
          <Footer />
        </div>
      )
    }
    
  3. 数据获取模式单一

    // 只能在页面级别获取数据
    export async function getServerSideProps() {
      const data = await fetchData()
      return { props: { data } }
    }
    

App Router 的根本性变革

1. React Server Components (RSC)

核心概念:服务端组件在服务器渲染,客户端组件在浏览器渲染

// Server Component (默认)
async function BlogPost({ slug }) {
  const post = await db.posts.findBySlug(slug)
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

// Client Component (需要 'use client')
;('use client')
function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false)
  return <button onClick={() => setLiked(!liked)}>{liked ? '❤️' : '🤍'}</button>
}
2. 文件系统路由革新

App Router 结构

app/
  layout.js          // 根布局
  page.js            // 首页
  loading.js         // 全局加载状态
  error.js           // 全局错误处理
  not-found.js       // 404 页面

  blog/
    layout.js        // 博客布局
    page.js          // 博客列表
    [slug]/
      page.js        // 博客详情
      loading.js     // 博客详情加载状态

  dashboard/
    layout.js        // 仪表盘布局
    page.js          // 仪表盘首页
    settings/
      page.js        // 设置页面
3. 嵌套布局系统
// app/layout.js - 根布局
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <nav>全局导航</nav>
        {children}
        <footer>全局页脚</footer>
      </body>
    </html>
  )
}

// app/blog/layout.js - 博客布局
export default function BlogLayout({ children }) {
  return (
    <div>
      <h1>博客</h1>
      <aside>博客侧边栏</aside>
      {children}
    </div>
  )
}
4. 流式传输 (Streaming)
// app/blog/page.js
import { Suspense } from 'react'

export default function BlogPage() {
  return (
    <div>
      <h1>博客列表</h1>
      <Suspense fallback={<div>加载中...</div>}>
        <BlogList />
      </Suspense>
      <Suspense fallback={<div>推荐中...</div>}>
        <Recommendations />
      </Suspense>
    </div>
  )
}

async function BlogList() {
  const posts = await fetch('/api/posts')
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
5. 数据获取模式变革

App Router 数据获取

// 直接在组件中获取数据
async function ProductPage({ params }) {
  const product = await fetch(`/api/products/${params.id}`)
  return <div>{product.name}</div>
}

// 缓存控制
async function getData() {
  const res = await fetch('https://api.example.com/data', {
    next: { revalidate: 3600 }, // 1小时缓存
  })
  return res.json()
}
6. 特殊文件功能
// loading.js - 自动加载状态
export default function Loading() {
  return <div>加载中...</div>
}

// error.js - 错误边界
'use client'
export default function Error({ error, reset }) {
  return (
    <div>
      <h2>出错了!</h2>
      <button onClick={() => reset()}>重试</button>
    </div>
  )
}

// not-found.js - 404 页面
export default function NotFound() {
  return <h2>页面未找到</h2>
}

核心优势对比

特性Pages RouterApp Router
渲染模式客户端为主服务端优先
布局系统手动嵌套自动嵌套
数据获取getServerSideProps直接 fetch
代码分割页面级别组件级别
流式传输不支持原生支持
缓存策略简单精细化控制

迁移建议

  1. 新项目:直接使用 App Router
  2. 现有项目:逐步迁移,从新页面开始
  3. 混合使用:App Router 和 Pages Router 可以共存

总结

App Router 代表了 Next.js 的根本性范式转变:

  • 从客户端到服务端:优先使用 Server Components
  • 从页面到组件:更细粒度的控制
  • 从静态到动态:更好的流式传输支持
  • 从简单到智能:自动化的布局和缓存策略

这些变革让 Next.js 能够更好地处理现代 Web 应用的复杂需求,提供更好的性能和开发体验。