Next.js 深入解析

Next.js 深入解析

一、数据获取与渲染策略

1. getStaticPaths - SSG 动态路径生成

用于生成静态页面的动态路由路径。

// 示例:pages/posts/[id].js
export async function getStaticPaths() {
  return {
    paths: [
      // 1. 基础参数形式 pages/posts/[id].js → posts/1
      { params: { id: '1' } },
      // 2. 嵌套动态路由 pages/[category]/[slug].js → tech/nextjs-guide
      { params: { category: 'tech', slug: 'nextjs-guide' } },
      // 3. 可选catch-all路由 pages/[...slug].js → docs/nextjs/api
      { params: { slug: ['docs', 'nextjs', 'api'] } },
    ],
    // fallback 的三个选项(必选其一)
    fallback: false,    // 选项1: 只生成指定路径,其他路径返回404
    fallback: true,     // 选项2: 增量生成,显示加载状态降级方案
    fallback: 'blocking' // 选项3: 增量生成,阻塞等待生成完成
  };
}

参数说明

  • 输入参数一般为空,context 可能包含 locales(支持的语言列表)和 defaultLocale(默认语言),但在 Pages Router 中不常用。

2. getStaticProps - SSG 页面数据获取

用于在构建时获取页面所需的数据。

export async function getStaticProps(context) {
  // context.params 包含动态路由参数
  const { params } = context;
  
  return {
    props: {
      // 数据会被注入到页面组件
    },
    revalidate: 60, // ISR 关键:60秒后重新生成
    notFound: false, // 如果为true,返回404页面
    redirect: {
      destination: '/',
      permanent: false, // 临时重定向用307,永久重定向用308
    }
  };
}

重要特性

  • propsnotFoundredirect 三选一返回
  • revalidate 是 ISR(增量静态再生)的关键
  • SEO 友好:永久重定向用 308,临时用 307
  • 用户体验:notFound 比显示错误页更干净

参数来源

  • getStaticPaths 返回的动态路由参数
  • 全局注入的参数(国际化、预览模式等)

3. getServerSideProps - SSR 实时数据获取

每次请求时获取最新数据。

export async function getServerSideProps({ req, res, params, query, locale }) {
  // 参数说明:
  // req → HTTP 请求对象(包含 cookie、header 等)
  // res → HTTP 响应对象(可设置 header、cookie 等)
  // params.id → 来自 URL 路由:/user/123
  // query → 来自 URL 查询:/user/123?mode=compact
  // locale → 来自 Accept-Language header 或 URL
  
  return {
    props: {
      // 数据会被注入到页面组件
    }
    // 注意:没有 revalidate 参数
  };
}

二、渲染策略对比

策略 核心代码特征 使用场景
SSG getStaticProps()revalidate 静态内容页
ISR getStaticProps() + revalidate 频繁更新内容
SSR getServerSideProps() 个性化/实时数据
CSR useEffect() / useSWR() 用户交互数据

SSG和SSR方案都是在服务端就生成了完整html,所以需要客户端加载后进行水合,
而dynamic 的逻辑是React先渲染loading占位符,当动态组件的JS包下载完成后,React会重新渲染该位置为真实组件,没有水合(直接就是可交互),这一点更像是CSR的方案。

三、动态导入与代码分割

import dynamic from 'next/dynamic';

const DynamicComponent = dynamic(
  () => import('../components/Hello'),
  {
    ssr: true, // 默认值,表示服务端加载
    loading: () => <p>Loading...</p>
  }
);

重要特性

  • ssr 参数默认为 true,表示服务端加载
  • 主要作用是代码分割:dynamic 包裹的组件会单独打包,按需加载
  • 与常规 import 不同:常规 import 的组件会被包含在主包中
  • 更像是 CSR 方案:React 先渲染 loading 占位符,JS 包下载完成后重新渲染为真实组件,没有水合过程

dynamic中ssr参数默认为true,当是默认值时,表示服务端加载,此时貌似和不使用dynamic没什么区别,
其主要作用是代码分割,正常import的组件会被默认包含在主包当中,而用dynamic包裹的import组件会单独打包,按需加载。

四、优化功能

1. Image 优化

  • 自动图片优化(尺寸、格式、懒加载)
  • 支持 WebP 等现代格式
  • 防止布局偏移

2. Font 优化

  • 自动字体优化和预加载
  • 减少布局偏移

3. Script 优化

  • 优化第三方脚本加载
  • 支持 strategy 属性(beforeInteractiveafterInteractivelazyOnload

五、路由系统

1. 页面路由(Pages Router)

  • 基于文件系统的路由
  • 支持动态路由:[id].js[...slug].js
  • 支持嵌套路由

2. 浅路由(Shallow Routing)

const router = useRouter();
router.push(
  '/products?page=2',
  undefined, 
  { shallow: true }  // 关键参数
);

使用场景:筛选排序、分页、标签页切换等

  • 普通路由跳转:改变 URL → 重新获取数据 → 重新渲染组件
  • 浅路由跳转:改变 URL → 直接更新 URL → 保持当前组件状态

浅路由最适合那些只需要更新查询参数的场景,而页面内容可以通过客户端逻辑更新
目前主流使用app router已不常用

3. API 路由

  • pages/api/ 目录下的文件可作为后端接口
  • 接收 reqres 参数
  • 让 Next.js 成为全栈框架

API 路由让 Next.js 从一个前端框架变成了全栈框架,是 Next.js 最重要的特性之一,
简单来说就是一个page下的api文件夹中的文件,都可以作为后端的接口直接访问,参数是req和res。

4. 中间件

  • 旧版本:在 pages/ 目录创建 _middleware.js 文件
  • 新版本推荐:在项目根目录创建 middleware.js 文件

六、App Router(现代方案)

更现代化的方案,更推荐使用。核心性能优势是按需水合。有use client声明就水合,没有就不触发水合。

1. 数据获取简化

  • getStaticProps → 直接使用 async 组件函数(默认 SSG,会缓存数据)
  • getStaticPathsgenerateStaticParams
    export async function generateStaticParams() {
      return [{ id: '1' }, { id: '2' }];
    }
    export const dynamicParams = true; // 相当于 fallback: true
    
  • getServerSideProps → 使用以下方式之一:
    import { unstable_noStore } from 'next/cache';
    export const dynamic = 'force-dynamic'; // 强制动态渲染
    // 或使用 cache: 'no-store'
    // 或使用动态函数(如 cookies(), headers())
    

2. Metadata API

  • 用于更好的 SEO
  • generateMetadata 函数可生成动态元数据

3. ISR 实现

export const revalidate = 60; // 每60秒重新验证
// 或
fetch(url, { next: { revalidate: 60 } });

4. 客户端组件

  • 需要在文件头部显式添加 'use client' 指令
  • 默认部分水合:服务器组件不水合,客户端组件水合

5. 动态导入优化

dynamic(..., { ssr: false }) // 主要用来延迟加载

水合条件

  1. 组件标记为 'use client'(需要交互)
  2. 组件在服务端渲染了 HTML(ssr: true 或默认)

无水合情况

  1. 服务器组件(无 'use client'
  2. 客户端组件但 ssr: false
  3. 纯 CSR 组件(无服务端渲染)

6. 流式渲染

  • App Router 自动支持流式渲染
  • 需要使用 <Suspense> 包裹异步内容
  • 每个 <Suspense> 边界独立流式传输

7. Server Actions

  • 使用 'use server' 声明的函数
  • 底层是网络通信,但封装为类似本地函数调用的体验
  • 简化表单处理,无需创建复杂 API 路由

Server Actions的底层是一次网络通信,但Next.js把它封装成了看似“本地函数调用”的体验。
所以才可以直接使用 'use server'声明之后,其他组件直接调用这个函数就可以了。
既然nextjs中前端代码和后端逻辑在同一个项目中,所以对于简单的表单,省去了创建复杂api的逻辑,直接调函数。

8. 其他特性

  • 中间件:在项目根目录的 middleware.js 文件中定义,用于身份验证、国际化、重定向等
  • 路由组:用 ( ) 包裹文件夹名,用于组织文件夹结构
  • Cookie/Headers:在服务端组件中通过 cookies()headers() 函数获取请求信息
  • 拦截路由:通过 (.) 前缀实现同级目录拦截,常用于模态框
  • 客户端导航
    • usePathname:获取当前路径(根路径是 "/"
    • useSearchParams:获取查询参数
    • useRouter:切换路由
    • 注意:必须在客户端组件('use client')中使用,且 usePathnameuseSearchParams 会导致动态渲染

七、总结

开发建议

  1. 需要交互的组件:使用 'use client'
  2. 需要延迟加载的组件:使用 dynamic
  3. 需要定期更新的组件:使用 revalidate
  4. 简化数据获取:考虑使用 useSWR 进行状态管理

策略选择

  • 简单静态页面:使用 App Router 的默认 SSG
  • 需要实时数据:使用 dynamic = 'force-dynamic'unstable_noStore()
  • 频繁更新内容:使用 ISR(revalidate
  • 用户交互数据:使用 CSR(useEffect/useSWR
posted @ 2025-12-26 15:47  Allis  阅读(0)  评论(0)    收藏  举报