远浅
理解他人,内省自己。

如何让你的 Nextjs SSR 应用更快的响应~

远浅发表于: 2020-11-12 14:11分类: 技术

最近在折腾了一把我的 SSR 网站加速,顺带把公司的网站也加速了一把。

未改进之前 image.png

改进之后 image.png

效果很明显,服务端耗时是原来的的 1/20。

以下是我在这个过程中采取的措施。

服务端请求使用内网服务地址

众所周知,一个请求的流程是 DNS Lookup、TCP connect、TLS Handshake、Server Processing、Content Transfer。

由于我们的 SSR 应用服务端发出的请求全是在内网,所以不再需要 HTTPS ,减少了 TLS 握手的时间。

合并服务端异步请求

服务端可能会请求多个接口数据进行服务端渲染,如果依次获取数据会造成阻塞。

最优解是使用Promise.all 进行合并请求,一次同时发送多个请求。

移除不必要的服务端请求

很多人都会有一个误区,SSR 渲染全部数据一定要在服务端获取。

其实这是不必要的。

在服务端过多的请求数据会增加服务的不稳定性,一定要弄清做 SSR 渲染的目的,而不是为了 SSR 而 SSR。

在我的理解里,为了 SEO 或者 可视区内的渲染才需要做 SSR 。

移除全局的服务端状态注入

之前的状态管理方案使用了服务端注入,因此每次 SSR 渲染其实是需要请求一次全局状态相关的接口,然后再请求业务相关接口。

这样的流程存在较大的不合理之处,本来不需要状态的页面也被强制注入了状态,增加了耗时。

因此, 我移除了服务端相关状态注入并且改用在 _app.tsx 文件的 useEffect 中处理和用户相关的状态。

本次优化中,我把状态管理 Mobx 迁移成了 Hox。

自定义 Server 中使用缓存

我的服务并没有使用官方自带的服务器,而是自定义了一个 Server 方便我进行扩展。

SSR 服务渲染的原理大概就是服务端生成客户端能运行的页面。

在移除全局服务端状态管理后,实际上每一个用户获得的无状态页面都应当是相同的。

于是我们就可以通过缓存生成的页面来加速访问。

官方的 Examples 里有一个使用 cacheable-response 进行页面缓存的例子可以进行参考。

保险起见,我使用了 Redis 作为缓存存储而不是默认的内存存储。

此外,请注意服务端渲染中和用户 Cookies 有强相关的页面,请务必使用 Middleware 处理避免缓存。

// 简单案例
export const OptionSSRCache = (ssr: (opt: any) => any, force?: boolean) => (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  // 没有token 或者 强制缓存 直接取缓存
  if (force || !req?.cookies?.[TOKEN_NAME]) {
    res.setHeader("X-Powered-By-Render-Type", "cache-render");
    // 缓存实例
    return ssr({ req, res });
  }
  // 存在token 服务端渲染 
  if (req?.cookies?.[TOKEN_NAME]) {
    res.setHeader("X-Powered-By-Render-Type", "ssr-render");
    return next();
  }
};

移除 SSR 自带的组件跳转,添加 prerender,使用 a 标签。

SSR 渲染页面成功之后,在浏览器端就变成一个 SPA 应用。

使用内置的 next/link 之类的前端路由组件跳转,前端会使用发起 ajax 请求数据,渲染下一个页面。

但是这 不够快

我们的目标的链接如果已经有了缓存,直接使用 a 标签是最快的方式。

问题就来了,此时目标链接可能没有缓存,那么我们怎么样让目标存在缓存呢?

prerender 预先资源加载。

示例代码如下

// 动态生成 link prerender
 import { useRouter } from "next/router";
import { CSSProperties, useEffect } from "react";

interface ActiveLinkIProps {
  children: JSX.Element;
  href: string;
  activeCSS?: CSSProperties;
  defautCSS?: CSSProperties;
}
export default function PrerenderLink(props: ActiveLinkIProps): JSX.Element {
  const router = useRouter();
  const css =
    router.pathname === props.href
      ? {
          ...props.defautCSS,
          ...props.activeCSS,
        }
      : props.defautCSS;

  useEffect(() => {
    if (document.getElementById(props.href)) {
      return;
    }
    // 创建预渲染
    const d = document.createElement("link");
    d.setAttribute("rel", "prerender");
    d.id = props.href;
    d.href = props.href;
    // 插入预渲染
    const s = document.getElementsByTagName("script")[0];
    s?.parentNode?.insertBefore(d, s);
  }, [props.href]);
  return (
    <>
      <a href={props.href} style={css}>
        {props.children}
      </a>
    </>
  );
}

此时浏览器会在合适的时间后台加载目标链接,当用户打开链接时已经存在了缓存,效率极大提升。 image.png

相关链接

赠人玫瑰, 手有余香。🌹
打赏
特别鸣谢
感谢以下用户对本文的支持与鼓励
加载打赏用户中
发表评论
评论列表
评论努力加载中