Skip to content

Latest commit

 

History

History
269 lines (219 loc) · 9.23 KB

2020-07-27:Next.js Server-side Rendering vs Static Generation.md

File metadata and controls

269 lines (219 loc) · 9.23 KB

Next.js: Server-side Rendering vs. Static Generation

Lee Robinson July 10th 2020

這篇雖然是講解 Next.js 的各種用法

  • 但也順便討論了 SSR vs SSG 的優缺點,是很好的思考文章
  • 另外也凸顯了 Next.js 能夠同時混用兩種模式的優點(超強大)

Next.js 提供兩種 pre-rendered HTML 的方法

1. Server-side Rendering (SSR)

SSR 時,Next.js每一個 request 進來時,會將 page 轉為 HTML 來 pre-render

  • TTFB (Time to first byte) 會比較慢
  • 但 data 永遠是最新的

2. Static Generation (SSG)

SSG 時,Next.js 會在 request 前 (通常是在 build time) 就把 page 轉為 HTML 來 pre-render

  • HTML 就能夠被 cached、能靠 CDN 支援
  • SSG 的 performance 比較好
  • 但因為是 request 前就先 build 好,所以 data 可能是舊的(比起 request 來產生 data)

兩種結合的方法

  • 先把大部分內容用 SSG 產生好,而 data 部份留在 client 時再 fetch

E-commerce Next.js Example

這邊用電商網站做舉例,假如有四大頁面

  • About Us: 公司介紹、No need to fetch data
  • 所有產品: 產品的 List 清單、需要從 DB 取資料、所有 User 看到此頁面的內容是一樣的
  • 單一產品: 產品詳細頁面、需要從 DB 取資料、所有 User 看到此頁面的內容是一樣的
  • 購物車 : User 購物車、需要從 DB 取資料、每位 User 看到的不一樣,是自己的 data

Next.js 最優秀的地方之一就是可以針對每一個 page 來規劃它的 data fetch 策略

  • About Us: SSG without data
  • 所有產品/單一產品: SSG with data,然後用 Incremental SSG 強化
  • 購物車 : SSG without data,並結合 Client-side Fetching

About Us Page: Static Generation without Data

當沒有 data 需要 fetch 時,Next.js default 行為就是在 build time 時,轉成 HTML

pages folder 底下建立 Component、export

// pages/about.js

// This page can can be pre-rendered without
// external data: It will be pre-rendered
// into a HTML file at build time.
export default function About() {
  return <div>
    <h1>About Us</h1>
    {/* ... */}
  </div>
}

All Products Page: Static Generation with Data

所有產品

  • 我們打算在 build time 時去 db 查資料

建立 page 的 component,並 export 一個 getStaticProps function

  • getStaticProps 會在 build time 時被呼叫,去 db 取資料來產生 pre-render 的 page component
  • (getStaticProps 並不會被 bundle 進去 JS bundle、不會給 client side 看到,可用來直接存取 db)
// This function runs at build time on the build server
export async function getStaticProps() {
  return {
    props: {
      products: await getProductsFromDatabase()
    }
  }
}

// The page component receives products prop
// from getStaticProps at build time
export default function Products({ products }) {
  return (
    <>
      <h1>Products</h1>
      <ul>
        {products.map((product) => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </>
  )
}

Individual Product Page: Static Generation with Data

  • 需要為每一個產品產生一個 page,用 product id 來當作 route
    • (for example, /products/[id])

這可以在 build time 時使用 Next.jsdynamic routesgetStaticPaths.

  • 建立一個檔案叫做 products/[id].js
  • 裡面使用 getStaticPaths 來 return 所有 id
    • 這樣就會在 build time 時產生所有頁面
  • 接著用 getStaticProps 來依 id 來取得產品詳細 data,在 build time 完成這些
// pages/products/[id].js

// In getStaticPaths(), you need to return the list of
// ids of product pages (/products/[id]) that you’d
// like to pre-render at build time. To do so,
// you can fetch all products from a database.
export async function getStaticPaths() {
  const products = await getProductsFromDatabase()

  const paths = products.map((product) => ({
    params: { id: product.id }
  }))

  // fallback: false means pages that don’t have the
  // correct id will 404.
  return { paths, fallback: false }
}

// params will contain the id for each generated page.
export async function getStaticProps({ params }) {
  return {
    props: {
      product: await getProductFromDatabase(params.id)
    }
  }
}

export default function Product({ product }) {
  // Render product
}

Incremental Static Generation

假如,電商成長很快。原本 100 個產品,已經成長到 10萬 個產品,這時候有兩個問題

  • 在 build time 時 Pre-rendering 10萬 個 page 非常慢
  • 某一產品資料更新時,我們只想 update 那一個產品,並不想整個 App 重 build 一次啊

這就靠 Incremental Static Generation 來處理

  • Incremental Static Generation 讓我們在 build time 之後 能 pre-render pages 的 subset
  • 這能用在 1. add new pages 2. update exist page

Adding Pages (Fallback)

10萬 個產品,並且在 build time pre-render,這會非常慢

  • 可以利用 lazily 來 pre-render page
  • 當 user request 某產品 X 時,我們在 pre-render

這方法要在 getStaticPaths 裡面把 fallback set true 然後在 page 裡面就能用 router.isFallback 判斷,來顯示一個 loading 的樣式

這個細節很重要,多看看官網的說明

// pages/products/[id].js

export async function getStaticProps({ params }) {
  // ...
}

export async function getStaticPaths() {
  // ...

  // fallback: true means that the missing pages
  // will not 404, and instead can render a fallback.
  return { paths, fallback: true }
}

export default function Product({ product }) {
  const router = useRouter()

  if (router.isFallback) {
    return <div>Loading...</div>
  }

  // Render product...
}

Updating Existing Pages (Incremental Static "Re"generation)

當某產品 data update 時,不需要整個 App 重 build

  • 透過 Incremental Static Regeneration 來每隔一些 interval 後來 pre-rendered the page

getStaticProps 中把 unstable_revalidate 設為 60

  • unstable_ 表示此功能還在 beta 階段
    • (Next.js v9.5 Stable,使用時確認一下 document)
// pages/products/[id].js

export async function getStaticProps({ params }) {
  return {
    props: {
      product: await getProductFromDatabase(params.id)
    },
    unstable_revalidate: 60
  }
}

Shopping Cart Page: Static Generation without Data, Combined with Client-side Fetching

  • 購物車我們只能部分 pre-render,User 相關的一定需要等 request
  • 這個情境,也不需要 Server-side Rendering,而 Client-side Fetching 的效能會更好
  • 這邊推薦 SWR 來搭配
import useSWR from 'swr'

function ShoppingCart() {
  // fetchAPI is the function to do data fetching
  const { data, error } = useSWR('/api/cart', fetchAPI)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>Items in Cart: {data.products.length}</div>
}

Benefits of Static

  • fallback
    • 當 user request 某一(還沒有 build pre-render的)產品
    • 用 fallback,代替 404
    • 先是顯示 loading,當 Next.js build 完產品頁面時,就會改顯示產品頁面
    • 後續有其他 User 來此頁面時,就會提供這個已經產生好的頁面
  • update Page
    • 當需要 update 已經存在的 page 時,利用 timeout 來每隔 60 秒 update page
    • 60 秒內,所有的 user 的 request 只會看到舊的 page
    • Next.js update 過後,新的頁面就 ready 了
  • Static is fast: Pre-rendered HTML files can be cached and served by a global CDN
  • Static is always online: backend or db 死掉了也沒關係
  • Static minimizes backend load: 不需要 request,所以 backend, db 也就沒有 loading

Server-side Rendering

  • 要採用 SSR 時,可以用 getServerSideProps
  • 但採用 SSR,相對就沒有上面 (static) 的好處
    • 所以 Next.js 如果符合需求,官方推薦採用 Incremental Static Generation or Client-side Fetching

Writing Data

fetch data 還不夠,App 一定還需要寫資料進去,下面用 新增產品 來舉例

export default async (req, res) => {
  const response = await fetch(`https://.../cart`, {
    body: JSON.stringify({
      productId: req.query.productId
    }),
    headers: {
      Authorization: `Token ${process.env.YOUR_API_KEY}`,
      'Content-Type': 'application/json'
    },
    method: 'POST'
  })

  const { products } = await response.json()
  return res.status(200).json({ products })
};

API routes