使用Next.js降低往返时间(RTT)的完整指南

本文详细介绍了如何使用Next.js的各种功能来减少往返时间(RTT),包括服务端渲染、React服务端组件、图像优化和缓存技术,从而显著提升网页加载性能。

如何使用Next.js降低往返时间(RTT)

你是否曾想过,为什么有些网站几乎立即加载,而另一些网站即使你的网络连接很快,也会让你盯着空白屏幕?在某些情况下,你的网速可能不是问题。这通常是因为往返时间(RTT),即浏览器向服务器发送请求并获取响应所需的时间。

互联网依赖于物理基础设施:光纤电缆、卫星和数据中心通常位于数千公里之外。网络请求以高速传输,但仍受光速限制(约300,000公里/秒)。例如,从尼日利亚拉各斯到美国旧金山服务器的网络请求行程超过12,000公里,在理想条件下单次往返需要约150-200毫秒。将其乘以典型网页发出的20-30个请求(用于HTML、CSS、图像、API等),这些毫秒很快累积成数秒的延迟,然后页面才能完全加载。

在本文中,我们将详细解释什么是往返时间(RTT),为什么它是Web性能中最被忽视的因素之一,以及如何使用Next.js最小化RTT次数,使你的应用程序感觉快速和响应迅速。你将学习服务端渲染(SSR)、React服务端组件(RSC)、图像优化和缓存等功能如何协同工作来减少网页中的往返时间。

你将学习的内容

  • 什么是往返时间(RTT)?
  • 距离如何增加往返时间
  • 往返时间如何影响Web性能
  • 为什么客户端渲染感觉更慢
  • 如何使用Next.js减少往返时间
  • Next.js渲染方法之间的权衡
  • 何时使用每种渲染方法

什么是往返时间(RTT)?

当你访问网站时,浏览器会向服务器发出网络请求。服务器处理请求,然后发送响应回来。往返时间是此旅程的完整持续时间(以毫秒为单位),包括:

  • 旅行时间:网络请求到达服务器所需的时间
  • 处理时间:服务器处理请求所需的时间
  • 返回时间:响应返回浏览器所需的时间

距离如何增加往返时间

往返时间在很大程度上取决于客户端和服务器之间的物理距离。例如:

  • 尼日利亚拉各斯的用户向约5,000公里外的伦敦服务器发出网络请求,往返时间可能为100-150毫秒
  • 约12,000公里外的旧金山服务器可能将往返时间推至200-300毫秒
  • 服务器越远,往返时间越高

往返时间如何影响Web性能

现代网页需要多个网络请求才能完全加载。想象加载一个电子商务产品页面需要:

  • 1个HTML网络请求(约200毫秒)
  • 5个CSS/JavaScript网络请求(约1,000毫秒)
  • 10个图像网络请求(约2,000毫秒)
  • 4个通过API获取产品数据的网络请求(约800毫秒)

这表明产品页面需要20个网络请求才能完全加载,这大约是4秒的网络延迟。

当页面加载时间从1秒增加到3秒时,跳出概率增加32%(Google/SOASTA Research, 2017)。这意味着大约三分之一的访问者在页面甚至加载之前就离开了。

为什么客户端渲染感觉更慢

在客户端渲染应用程序中,每个请求都会增加往返时间,而React应用程序中的传统客户端渲染(CSR)会增加这种情况:

  • 浏览器下载最小的HTML外壳和大型JavaScript包
  • JavaScript运行以获取数据并渲染UI,需要额外的网络请求
  • 每个API调用都会增加另一个RTT,延迟首次内容绘制(FCP)

首次内容绘制(FCP)测量从用户首次导航到页面到页面的任何内容(如文本、图像、<svg><canvas>元素)呈现在屏幕上的时间。

在CSR应用程序中,FCP被延迟,因为浏览器在JavaScript完成加载、解析和执行构建UI所需的代码之前无法显示任何有意义的内容。常规的CSR应用程序可能需要5到10个网络往返才能获取渲染UI所需的所有资源,这很容易增加几秒钟的延迟。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// pages/index.js (CSR)
import { useState, useEffect } from "react"

export default function Home() {
  const [products, setProducts] = useState([])

  useEffect(() => {
    // 页面加载后获取数据
    fetch("https://api.example.com/products")
      .then((res) => res.json())
      .then((data) => setProducts(data))
  }, [])

  return (
    <div>
      <h1>Products</h1>
      {products.length ? (
        products.map((product) => <p key={product.id}>{product.name}</p>)
      ) : (
        <p>Loading...</p>
      )}
    </div>
  )
}

在上面的代码中,当Home组件挂载时,它将状态初始化为空数组。然后useEffect钩子运行一次以发出API请求。当请求进行时,“Loading…“消息显示在屏幕上。一旦请求成功完成,React使用获取的数据更新状态,并重新渲染UI以显示产品。此过程引入了额外的往返,进一步延迟了FCP。

如何使用Next.js减少往返时间

你无法完全消除往返时间。数据仍然必须通过网络传输。Next.js所做的是减少这些网络请求发生的频率以及每个请求携带的数据量。它通过多种技术实现这一点,例如服务端渲染(SSR)、React服务端组件(RSC)、图像优化以及缓存或静态渲染。

服务端渲染(SSR)

与传统的React.js应用程序(浏览器处理大部分工作,如获取静态文件、JavaScript和渲染页面所需的数据)不同,服务器生成整个HTML,获取数据,渲染页面,并在单个往返时间内将其发送到浏览器。

优势:

  • 更少的往返次数:由于数据获取和渲染在服务器上进行,浏览器在一个往返时间内接收即可显示的页面
  • 改进的首次内容绘制:低往返时间意味着内容几乎立即显示在页面上
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// pages/index.js (SSR)
export async function getServerSideProps() {
  // 在服务器上获取数据
  const res = await fetch('https://api.example.com/products');
  const products = await res.json();

  return {
    props: { products }, // 将数据传递给页面
  };
}

export default function Home({ products }) {
  return (
    <div>
      <h1>Products</h1>
      {products.map((product) => (
        <p key={product.id}>{product.name}</p>
      ))}
    </div>
  );
}

在上面的代码中,getServerSideProps完全在服务器上运行。当用户访问或刷新页面时,调用getServerSideProps()从外部API获取产品数据。然后获取的数据在服务器上预渲染,意味着产品列表包含在发送到浏览器显示的HTML中。这消除了CSR中看到的额外往返,并改善了FCP,因为用户在页面加载时立即看到有意义的内容。

React服务端组件(RSC)

服务端渲染是一种整个页面在服务器上生成的技术。但是想象一下,如果只有页面的某些部分要在服务器上渲染,而其他部分要在客户端渲染?

React服务端组件允许在服务器和客户端之间划分渲染。

例如,ProductList组件可以在服务器上渲染,而SearchInput组件在客户端渲染以管理用户交互。

优势: RSC减少了整体往返时间(RTT),也提高了页面首次内容绘制。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// app/ProductList.jsx (服务端组件)
async function ProductList() {
  // 在服务器上获取数据
  const res = await fetch('https://api.example.com/products');
  const products = await res.json();

  return (
    <div>
      <h1>Products</h1>
      {products.map((product) => (
        <p key={product.id}>{product.name}</p>
      ))}
      <ClientSearch /> {/* 客户端组件 */}
    </div>
  );
}

export default ProductList;

在上面的代码中,ProductList是一个服务端组件,其中ClientSearch组件作为子组件。ClientSearch在浏览器中渲染,而ProductList的其余部分在服务器上渲染。当页面加载时,服务器运行fetch()检索产品数据,并在服务器上渲染产品列表的完整HTML,而ClientSearch在客户端渲染以处理用户交互。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// components/ClientSearch.jsx (客户端组件)
'use client';

import { useState } from 'react';

export default function ClientSearch() {
  const [query, setQuery] = useState('');

  return (
    <input
      type="text"
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Search products..."
    />
  );
}

上面的ClientSearch组件处理用户交互,例如使用useState更新搜索输入。它标有’use client’,因此完全在客户端运行。

图像优化

当图像未优化时,会对往返时间RTT产生负面影响,因为较大的文件需要更长的时间从服务器传输到浏览器。

Next.js Image组件自动优化图像:

  • 调整大小:根据用户的设备调整图像大小
  • 压缩:使用WebP等新格式显著缩小文件大小
  • 延迟加载:仅当图像进入用户的视口时才加载,这减少了初始请求的数量
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// pages/index.js
import Image from 'next/image';

export default function Home() {
  return (
    <div>
      <h1>Welcome to Our Store</h1>
      <Image
        src="/product.jpg"
        alt="Product Image"
        width={500}
        height={300}
      />
    </div>
  );
}

在上面的代码中,页面使用Next.js内置的Image组件渲染优化后的图像。当页面加载时,Next.js优化图像调整大小、延迟加载等。这意味着浏览器只会下载适合设备的正确图像大小。

缓存和静态渲染

使用SSR和服务端组件,如果服务器必须在每个请求上处理数据,往返时间仍然可能很高。Next.js通过静态站点生成(SSG)和增量静态再生(ISR)解决了这个问题。

工作原理:

  • 静态站点生成:页面在构建时预渲染,从CDN缓存并作为静态HTML交付
  • 增量静态再生:页面预渲染但可以在间隔后在后台重新生成,例如每60秒
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// app/page.jsx
export const revalidate = 60; // 每60秒重新生成页面

export default async function Home() {
  // 在服务器上获取数据
  const res = await fetch("https://api.example.com/products", {
    next: { revalidate: 60 }, 
  });
  const products = await res.json();

  return (
    <div>
      <h1>Products</h1>
      {products.map((product) => (
        <p key={product.id}>{product.name}</p>
      ))}
    </div>
  );
}

在上面的代码中,页面使用增量静态再生(ISR)。revalidate = 60选项是每60秒重新生成页面。当用户访问页面时,服务器立即提供预渲染的HTML。fetch()内的next: { revalidate: 60 }意味着数据被缓存60秒。60秒后,下一个请求将触发服务器重新生成新数据。

Next.js渲染方法之间的权衡

使用服务端渲染(SSR),浏览器仅在一次往返中获得完整的渲染页面。另一方面,这也可能导致服务器负载增加和高TTFB。TTFB(到第一个字节的时间)是用户看到内容显示在浏览器上所需的持续时间。

使用增量静态再生(ISR),页面预渲染并缓存,从而从服务器获得即时响应。页面将根据固定周期(例如每60秒)重新生成。这种方法的缺点是用户可能在更新之前看到旧内容。

在服务端组件中,渲染在服务器上进行,只有交互部分在客户端管理。通过这种方式,仍然保持服务端渲染,同时允许客户端交互。唯一的缺点是开发人员在决定在服务器上运行什么和在客户端运行什么时需要非常具体。

何时使用每种渲染方法

服务端渲染(SSR)应用于频繁更新的页面,例如仪表板、用户配置文件等。SSR保证用户始终看到最新数据。

至于增量静态再生(ISR),它应用于变化不频繁的页面,例如产品列表、营销页面或博客。

当你希望页面的一部分在服务器上渲染,而某些部分在客户端运行时,使用服务端组件。例如,需要用户交互(如搜索输入或过滤器)的页面,而数据获取和渲染在服务器上进行。

结论

往返时间(RTT)是页面加载缓慢背后的隐藏因素之一。每个网络请求都会增加一次往返,这些网络延迟随着浏览器获取脚本、图像和数据文件等多种资源而累积。Next.js通过最小化首次内容绘制之前需要完成的网络请求数量来解决这个问题。

  • 服务端渲染(SSR)和React服务端组件(RSC)将数据获取和渲染转移到服务器,减少了客户端请求
  • 图像优化减小了图像大小,并使用CDN从附近的服务器更快地交付内容
  • 缓存和静态渲染无需服务器进一步处理即可立即提供预生成的页面

使用这些技术,你可以构建加载更快、感觉更响应的Web应用程序,即使对于远离你的源服务器或网络较慢的用户也是如此。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计