banner
AgedCoffee

AgedCoffee

next 頁面性能優化

開啟 gzip 或者 brotli 壓縮#

一般如果動靜分離已經將靜態資源上傳到了 CDN,正常 CDN 都是開啟了以上的壓縮方案的
如果沒有開啟需要開啟相關能力

圖片壓縮#

儘量使用 webp 格式圖片和壓縮圖片,可以減少圖片的請求大小,從而減少加載時間,提升性能。

如果是部署於 vercel 平台的服務圖片默認開啟相關的優化邏輯

圖片懶加載#

圖片懶加載是一種常見的提升網頁性能的技術,它會等到圖片進入視窗再開始加載它們,可以避免一開始加載過多的資源。

<Image loading="lazy" src={renderItem.img} alt="" fill  />

添加 dns-prefetch、preconnect meta 標籤#

DNS 預獲取 (dns-prefetch) 和預連接 (preconnect) 是兩種讓瀏覽器提前進行域名解析和建立 TCP 握手的技術
在加載一個新的頁面時,瀏覽器需要花費一定的時間來解析域名和建立 TCP 握手。我們可以通過添加 dns-prefetch 和 preconnect meta 標籤來預先完成這些操作,從而顯著提高頁面加載速度

<Head>
  <link rel="dns-prefetch" href="//example.com" />
  <link rel="preconnect" href="//example.com" />
</Head>

自部署 next 服務的自定義圖片 loader 優化#

下面的代碼展示了一個自定義的圖片 loader,它可以將圖片優化的功能外包到另一個服務。

'use client'

import React, { ComponentProps } from 'react';
import Image from 'next/image';

export const CustomLoaderImg = (props: ComponentProps<typeof Image>) => {
  return (
    <Image
      {...props}
      loader={({ src, width, quality }) => {
        // 確認一個可用的圖片優化服務之後替換
        return `https://optimize-img.com?url=${src}?w=${width}&q=${quality || 75}`;
      }}
    />
  );
};

部分邏輯組件和渲染內容獨立於服務端渲染#

有些組件作為用戶交互之後才需要展示的內容,例如預留好插槽的 Modal 組件,類似這樣的組件沒有伺服器端渲染的必要,可以考慮修改他的實現為僅客戶端渲染。同樣,如果你正在渲染和時間有關的內容,由於伺服器和客戶端的時間可能存在微小差異,也可能導致錯誤。

這可以通過以下方法實行:

  1. 使用 next 提供的 dynamic
import dynamic from 'next/dynamic';

//僅在客戶端動態導入並渲染 Modal 組件
const DynamicModal = dynamic(
  () => import('../components/Modal'),
  { ssr: false }  // 這將禁用伺服器端渲染(SSR)
);

// 僅在客戶端動態導入並渲染 Time 相關組件
const DynamicTime = dynamic(
  () => import('../components/Time'),
  { ssr: false }  // 這將禁用伺服器端渲染(SSR)
);
  1. useEffect 中動態引入
import React, { useState, useEffect } from 'react';

function YourComponent() {
  const [DynamicComponent, setDynamicComponent] = useState(null);

  useEffect(() => {
    import('../components/Modal').then((Modal) => {
      setDynamicComponent(() => Modal.default); // 注意 Modal.default
    });
  }, []);

  if (!DynamicComponent) return <p>Loading...</p>;

  return <DynamicComponent />;
}
  1. 首幀之後才實際渲染最終內容
import React, { useState, useEffect } from 'react';
import Modal from '../components/Modal';

function YourComponent() {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    // 在 useEffect 中設置標誌表示現在是在客戶端了
    setIsClient(true);
  }, []);

  if (!isClient) return <p>Loading...</p>;

  return <Modal />;
}

打包靜態資源上傳到 CDN 的插件#

以下代碼展示了如何將打包後的靜態文件上傳到 CDN,這樣可以顯著減少伺服器的負載,同時由於 CDN 通常都具備很好的地理分佈和加載優化,所以使用 CDN 可以進一步提升用戶的加載體驗。

/* eslint-disable react-func/max-lines-per-function */
/* eslint-disable max-depth */
const request = require('request');

function getCdnUrlPrefix(options) {
  return `${options.packageVersion}/${options?.isEnvProduction ? 'prod' : 'dev'}`;
}

function UploadCDN(options) {
  this.isEnvProduction = options?.isEnvProduction ?? false;
  this.options = options;
}

function upload(options) {
  return new Promise((resolve, reject) => {
    request(options, function (error, response) {
      if (error) reject(error);
      resolve();
    });
  });
}
UploadCDN.prototype.apply = function (compiler) {
  // eslint-disable-next-line react-func/max-lines-per-function
  compiler.hooks.emit.tapAsync('UploadCDN', async (compilation, callback) => {
    // formData list
    let optionsMap = {};
    let commonOptions = {
      method: 'POST',
      url: 'https://your-cdn-serverice/api/v1/files',
      headers: {
        module: this.options.module,
        _dir: '/',
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      formData: {},
    };
    for (let fileName in compilation.assets) {
      if (!fileName.endsWith('map')) {
        const parts = fileName.split('/');
        const filename = parts.pop();
        const _dir = `${getCdnUrlPrefix(this.options)}/_next/${parts.join('/')}`;
        const formData = {
          value: compilation.assets[fileName].source(),
          options: {
            filename,
            contentType: null,
          },
        };
        if (!optionsMap[_dir]) {
          optionsMap[_dir] = {
            ...JSON.parse(JSON.stringify(commonOptions)),
          };
          optionsMap[_dir].headers._dir = _dir;
        }
        optionsMap[_dir].formData[filename] = formData;
      }
    }

    const optionsArr = Object.keys(optionsMap).map(key => optionsMap[key]);
    optionsArr.forEach(async options => await upload(options));
    callback();
  });
};

module.exports = {
  UploadCDN,
};
// next.config.js
// ...
  assetPrefix: isProd ? `https://your-cdn-serverice/${yourModule}/${moduleName}/${packageVersion}/prod` : undefined,
  webpack: (config, context) => {
    isProd &&
      config.plugins.push(
        new UploadCDN({
          module: moduleName,
          packageVersion,
          isEnvProduction: isProd,
        }),
      );

    return config;
// ...

推薦閱讀#

Next.js 官方文檔
Google - Web 性能優化指南

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。