nextjs ssr數據緩存


本文使用到nextjs@10作為項目開發,使用lru-cache插件作為直出結果緩存工具。本文所述的ssr緩存效果可以在獵豹影院看到。

如果大家只想知道如何實現,可以直接跳到最后看實現源碼,當然如果大家想知道nextjs直出緩存的相關細節可以以此往下閱讀。

getInitialProps or getServerSideProps

nextjs直出本身不存在緩存功能,我們需要先拿到直出的html內容,然后將直出內容緩存在服務器中。nextjs提供了一個renderToHTML api供我們獲取直出的html,我們可以通過下面的方式來調用:

const next = require('next');
const app = next({ dev: isDev });

// 獲取直出的html
app.renderToHTML(...);

nextjs也提供了getRequestHandlerapi,來獲取自動處理頁面請求的函數,const handle = app.getRequestHandler()

在nextjs直出環境下,頁面組件中我們可以通過getInitialPropsgetServerSideProps這兩個api來獲取記錄頁面渲染所需的數據。nextjs會將從這兩個api中拿到的數據寫入到id為__NEXT_DATA__的script標簽中。

我們在進行業務開發的時候getInitialPropsgetServerSideProps這兩個api只有一個有效,當我們這兩個函數都定義以后,項目構建中會報警。一般情況下,我們優先使用getServerSideProps,這也是官方所推崇的。但是,在做直出數據緩存的時候我們需要使用getInitialProps api。關於這兩個api的使用,大家可以在nextjs官方文檔中看到,這里就不再贅述。

在調用renderToHTML渲染頁面的時候,使用getInitialPropsgetServerSideProps這兩個api進行數據獲取時,渲染表現會有一定的出入。

  • getInitialProps:調用renderToHTML函數,會返回直出的html,開發者需要手動調用res.end將結果返回給客戶端。
  • getServerSideProps:調用renderToHTML函數,renderToHTML內部在獲得直出的html后,會去判斷當前是否使用的getServerSideProps來獲取的直出數據,如果是,就會直接調用res.end將數據緩存,然后將renderToHTML函數返回的直出html至空,源碼如下:
// https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/next-server.ts#L1842
if (
  !isResSent(res) &&
  !isNotFound &&
  (isSSG || isDataReq || hasServerProps)
) {
  if (isRedirect && !isDataReq) {
    await handleRedirect(pageData)
  } else {
    sendPayload(...)
  }
  resHtml = null
}

// https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/send-payload.ts#L38
export function sendPayload(
  req: IncomingMessage,
  res: ServerResponse,
  payload: any,
  type: 'html' | 'json',
  {
    generateEtags,
    poweredByHeader,
  }: { generateEtags: boolean; poweredByHeader: boolean },
  options?: PayloadOptions
): void {
  // ...
  if (!res.getHeader('Content-Type')) {
    res.setHeader(
      'Content-Type',
      type === 'json' ? 'application/json' : 'text/html; charset=utf-8'
    )
  }
  res.setHeader('Content-Length', Buffer.byteLength(payload))

  res.end(req.method === 'HEAD' ? null : payload)
}

通過上面我們了解到通過getServerSideProps api獲取直出數據,調用renderToHtml函數時無法拿到html,並且直出結果會在renderToHtml函數中調用res.end響應給客戶端,在調用renderToHtml后就沒法再調用res.end

相對於getServerSidePropsgetInitialProps作為頁面獲取數據的方式更加可控,我們不僅可以拿到直出的html,還可以控制如何響應當前請求。因此后面將使用getInitialProps作為直出數據獲取的方式。

直出緩存代碼

// server.js
const express = require('express');
const next = require('next');
const LRUCache = require('lru-cache');

const port = parseInt(process.env.PORT, 10) || 3000;
const isDev = process.env.NODE_ENV === 'development';
const app = next({ dev: isDev });

// nextjs原生請求處理函數
const handle = app.getRequestHandler();

// 緩存工具初始
const ssrCache = new LRUCache({
  max: 100,
  maxAge: 1 * 60 * 60 * 1000, // 1小時緩存
});

// 使用請求的url作為緩存key
function getCacheKey (req) {
  return `${req.url}`
}

function renderAndCache (req, res, pagePath, queryParams) {
  const key = getCacheKey(req)
  // 如果緩存中有直出的html數據,就直接將緩存內容響應給客戶端
  if (ssrCache.has(key)) {
    res.send(ssrCache.get(key));
    return    
  }

  // 如果沒有當前緩存,調用renderToHTML生成直出html
  app.renderToHTML(req, res, pagePath, queryParams)
    .then((html) => {
      if(res.statusCode === 200) {
        // 使用緩存工具將html存放
        ssrCache.set(key, html);
      }else{
        ssrCache.del(key);
      }
      
      // 響應直出內容
      res.send(html);
    })
    .catch((err) => {
      app.renderError(err, req, res, pagePath, queryParams)
    })
}

async function main() {
  await app.prepare();

  const server = express();
  server.listen(port, (err) => {
    if (err) throw err;
    console.log(`> Ready on http://localhost:${port}`);
  });

  server.get('/', (req, res) => renderAndCache(req, res, '/'));

  // app.getRequestHandler()得到的原生資源處理函數,靜態資源請求、直出請求這個函數都能正常處理
  server.get('*', (req, res) => handle(req, res));
}

main();
// package.json
{
  // ...
  "scripts": {
    "dev": "cross-env NODE_ENV=development node server.js",
  }
}
// 頁面代碼
export default function Home() {}

Home.getInitialProps = async () => {
  reutrn {
    // 直出所需數據
  }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM