【NEXT.JS 系列】基本功能—獲取數據

Home 教學 【NEXT.JS 系列】基本功能—獲取數據
【NEXT.JS 系列】基本功能—獲取數據
Next.js

Next.js 中的獲取數據允許您根據應用程式的情況以不同的方式呈現您的內容。 其中包括使用服務器端渲染或靜態生成進行預先渲染,以及使用增量靜態重新生成在運行時更新或建立內容。

getServerSideProps

如果您從頁面匯出名為 getServerSideProps(服務器端渲染)的函數,Next.js 將使用 getServerSideProps 返回的數據在每個請求上預先渲染此頁面。

export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

請注意,無論呈現類型如何,任何 props 都將傳遞給頁面元件,並且可以在初始 HTML 中在客戶端查看。 這是為了讓頁面正確地水合 hydrated。 確保您沒有在 props 中傳遞任何不應在客戶端上使用的敏感資訊。

getServerSideProps 什麼時候運行

getServerSideProps 僅在服務器端運行,從不在瀏覽器上運行。如果頁面使用 getServerSideProps,則:

  • 當你直接請求這個頁面時,getServerSideProps 在請求的時候運行,這個頁面會和返回的 props 一起預先渲染
  • 當您通過 next/linknext/router 在客戶端頁面轉換請求此頁面時,Next.js 向服務器發送 API 請求,服務器運行 getServerSideProps

getServerSideProps 返回將用於呈現頁面的 JSON。所有這些工作都將由 Next.js 自動處理,因此只要定義了 getServerSideProps,您就不需要做任何額外的事情。

您可以使用 next-code-elimination 工具來驗證 Next.js 從客戶端包中消除了什麼。

getServerSideProps 只能從頁面匯出。您不能從非頁面文件中匯出它。

請注意,您必須將 getServerSideProps 作為獨立函數匯出 – 如果將 getServerSideProps 添加為頁面元件的屬性,它將不起作用。

getServerSideProps API 參考涵蓋了可與 getServerSideProps 一起使用的所有參數和 props。

我什麼時候應該使用 getServerSideProps

僅當您需要呈現必須在請求時獲取其數據的頁面時,才應使用 getServerSideProps。 這可能是由於數據的性質或請求的屬性(例如授權標頭或地理位置)造成的。 使用 getServerSideProps 的頁面將在請求時在服務器端呈現,並且僅在配置了快取控制標頭時才被快取。

如果您在請求期間不需要渲染數據,那麼您應該考慮在客戶端獲取數據或 getStaticProps

getServerSideProps 或 API 路由

當您想從服務器獲取數據時,可能很容易使用 API 路由,然後從 getServerSideProps 調用該 API 路由。 這是一種不必要且低效的方法,因為它會由於服務器上運行的 getServerSideProps 和 API Routes 而導致發出額外的請求。

舉個例子。 API 路由用於從 CMS 獲取一些數據。 然後直接從 getServerSideProps 調用該 API 路由。 這會產生額外的調用,從而降低性能。 相反,直接將 API Route 中使用的邏輯導入 getServerSideProps。 這可能意味著直接從 getServerSideProps 內部調用 CMS、數據庫或其他 API。

在客戶端獲取數據

如果您的頁面包含頻繁更新的數據,並且您不需要預先渲染數據,您可以在客戶端獲取數據。 這方面的一個例子是用戶特定的數據:

  • 首先,立即顯示沒有數據的頁面。 頁面的某些部分可以使用靜態生成進行預先渲染。 您可以顯示缺失數據的加載狀態
  • 然後,在客戶端獲取數據並在準備好時顯示它

例如,這種方法適用於用戶儀表板頁面。 因為儀表板是一個私人的、用戶特定的頁面,所以並不要求 SEO,並且該頁面不需要預先渲染。 數據經常更新,這需要在請求時獲取數據。

使用 getServerSideProps 在請求時獲取數據

以下示範顯示如何在請求時獲取數據並預先渲染結果。

function Page({ data }) {
  // Render data...
}

// This gets called on every request
export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  // Pass data to the page via props
  return { props: { data } }
}

export default Page

使用服務器端渲染 (SSR) 進行緩存

您可以在 getServerSideProps 中使用快取標頭 (Cache-Control) 來快取動態結果。 例如,使用 stale-while-revalidate

// This value is considered fresh for ten seconds (s-maxage=10).
// If a request is repeated within the next 10 seconds, the previously
// cached value will still be fresh. If the request is repeated before 59 seconds,
// the cached value will be stale but still render (stale-while-revalidate=59).
//
// In the background, a revalidation request will be made to populate the cache
// with a fresh value. If you refresh the page, you will see the new value.
export async function getServerSideProps({ req, res }) {
  res.setHeader(
    'Cache-Control',
    'public, s-maxage=10, stale-while-revalidate=59'
  )

  return {
    props: {},
  }
}

可以到此了解更多有關快取的內容。

getServerSideProps 是否呈現錯誤頁面

如果在 getServerSideProps 中拋出錯誤,它將顯示 pages/500.js 文件。 查看 500 page 的文件以了解有關如何建立它。 在開發過程中,不會使用此文件,而是顯示 dev overlay。

getStaticPaths

如果一個頁面有動態路由並使用 getStaticProps,它需要定義一個靜態生成的路徑列表。

當您從使用動態路由的頁面匯出名為 getStaticPaths(靜態站點生成)的函數時,Next.js 將靜態預先渲染 getStaticPaths 指定的所有路徑。

// pages/posts/[id].js

// Generates `/posts/1` and `/posts/2`
export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
    fallback: false, // can also be true or 'blocking'
  }
}

// `getStaticPaths` requires using `getStaticProps`
export async function getStaticProps(context) {
  return {
    // Passed to the page component as props
    props: { post: {} },
  }
}

export default function Post({ post }) {
  // Render post...
}

getStaticPaths API 參考文件涵蓋了可與 getStaticPaths 一起使用的所有參數和 props。

什麼時候應該使用 getStaticPaths?

如果您要靜態預先渲染使用動態路由的頁面,則應該使用 getStaticPaths 並且:

  • 數據來自 headless CMS
  • 數據來自數據庫
  • 數據來自文件系統
  • 數據可以公開快取(不是用戶特定的)
  • 頁面必須預先渲染(用於 SEO)並且速度非常快 — getStaticProps 生成 HTML 和 JSON 文件,這兩個文件都可以被 CDN 快取以提高性能

getStaticPaths 什麼時候運行

getStaticPaths 只會在生產中的建構期間運行,它不會在運行時調用。 您可以使用此工具驗證 getStaticPaths 中編寫的程式碼是否已從客戶端綑綁包中刪除。

關於 getStaticPaths,getStaticProps 如何運行

  • getStaticProps 在 run next build 時針對建構期間返回的任何 paths 運行
  • getStaticProps 當使用 fallback: true 時於背景執行
  • getStaticProps 當使用 fallback: blocking 時會在初始渲染之前調用

我在哪裡可以使用 getStaticPaths

  • getStaticPaths 必須與 getStaticProps 一起使用
  • 您不能將 getStaticPathsgetServerSideProps 一起使用
  • 您可以從也使用 getStaticProps 的動態路由中匯出 getStaticPaths
  • 您不能從非頁面文件(例如您的元件文件夾)中匯出 getStaticPaths
  • 您必須將 getStaticPaths 匯出為獨立函數,而不是頁面元件的屬性

在開發中的每個請求上運行

在開發(next dev)中,每個請求都會調用 getStaticPaths

依照需求生成路徑

getStaticPaths 允許您控制在建構過程中生成哪些頁面,而不是通過 fallback 按需生成。 在建構期間生成更多頁面將導致建構速度變慢。

您可以通過為路徑返回一個空數陣列來延遲按需生成所有頁面。 這在將 Next.js 應用程式部署到多個環境時特別有用。 例如,您可以通過按需生成所有頁面以供預覽(但不是生產建構)來加快建構速度。 這對於擁有成百上千個靜態頁面的網站很有幫助。

// pages/posts/[id].js

export async function getStaticPaths() {
  // When this is true (in preview environments) don't
  // prerender any static pages
  // (faster builds, but slower initial page load)
  if (process.env.SKIP_BUILD_STATIC_GENERATION) {
    return {
      paths: [],
      fallback: 'blocking',
    }
  }

  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // Get the paths we want to prerender based on posts
  // In production environments, prerender all pages
  // (slower builds, but faster initial page load)
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // { fallback: false } means other routes should 404
  return { paths, fallback: false }
}

getStaticProps

如果您從頁面匯出名為 getStaticProps(靜態網站生成)的函數,Next.js 將使用 getStaticProps 返回的 props 在建構時預先渲染此頁面。

export async function getStaticProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

請注意,無論呈現類型如何,任何 props 都將傳遞給頁面元件,並且可以在初始 HTML 中在客戶端查看。 這是為了讓頁面正確地水合 hydrated。 確保您沒有在 props 中傳遞任何不應在客戶端上使用的敏感資訊。

什麼時候應該使用 getStaticProps?

在以下情況下,您應該使用 getStaticProps:

  • 呈現頁面所需的數據在用戶請求之前的建構時可用
  • 數據來自 headless CMS
  • 頁面必須預先渲染(用於 SEO)並且速度非常快 — getStaticProps 生成 HTML 和 JSON 文件,這兩個文件都可以被 CDN 快取以提高性能
  • 數據可以公開快取(不是特定於用戶的)。 在某些特定情況下,可以通過使用 middleware 重寫路徑來繞過這種情況。

getStaticProps 什麼時候運行

getStaticProps 始終在服務器上運行,從不在客戶端上運行。 您可以使用此工具驗證在 getStaticProps 中編寫的程式碼是否已從客戶端綑綁包中刪除。

  • getStaticProps 始終在 next build 期間運行
  • 使用 revalidategetStaticProps 在後台運行
  • 使用 revalidate()getStaticProps 在後台按需運行
  • 當與增量靜態再生結合使用時,getStaticProps 將在後台運行,同時重新驗證陳舊的頁面,並將新頁面提供給瀏覽器。

getStaticProps 在生成靜態 HTML 時無權訪問傳入請求(例如查詢參數或 HTTP 標頭)。 如果您需要訪問頁面請求,請考慮使用 Middleware

使用 getStaticProps 從 CMS 獲取數據

以下示範顯示如何從 CMS 獲取部落格文章列表。

// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  )
}

// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
export async function getStaticProps() {
  // Call an external API endpoint to get posts.
  // You can use any data fetching library
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // By returning { props: { posts } }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts,
    },
  }
}

export default Blog

getStaticProps API 參考文件涵蓋了可與 getStaticProps 一起使用的所有參數和 props。

直接寫服務端程式碼

由於 getStaticProps 僅在服務器端運行,它永遠不會在客戶端運行。 它甚至不會包含在瀏覽器的 JS 綑綁包中,因此您可以編寫資料庫的直接查詢,而無需將它們發送到瀏覽器。

這意味著您可以直接在 getStaticProps 中編寫服務器端代碼,而不是從 getStaticProps 獲取 API 路由(它本身從外部源獲取數據)。

舉個例子。 API 路由用於從 CMS 獲取一些數據。 然後直接從 getStaticProps 調用該 API 路由。 這會產生額外的調用,從而降低性能。 相反,可以通過使用 lib/ 目錄來共享從 CMS 獲取數據的邏輯。 然後可以與 getStaticProps 共享。

// lib/load-posts.js

// The following function is shared
// with getStaticProps and API routes
// from a `lib/` directory
export async function loadPosts() {
  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts/')
  const data = await res.json()

  return data
}

// pages/blog.js
import { loadPosts } from '../lib/load-posts'

// This function runs only on the server side
export async function getStaticProps() {
  // Instead of fetching your `/api` route you can call the same
  // function directly in `getStaticProps`
  const posts = await loadPosts()

  // Props returned will be passed to the page component
  return { props: { posts } }
}

或者,如果您不使用 API 路由來獲取數據,那麼可以直接在 getStaticProps 中使用 fetch() API 來獲取數據。

靜態生成 HTML 和 JSON

在建構時預先渲染帶有 getStaticProps 的頁面時,除了頁面 HTML 文件之外,Next.js 還會生成一個 JSON 文件,其中包含運行 getStaticProps 的結果。

此 JSON 文件將用於通過 next/linknext/router 進行客戶端路由。 當您導航到使用 getStaticProps 預先渲染的頁面時,Next.js 會獲取此 JSON 文件(在建構時預先計算)並將其用作頁面元件的 props。 這意味著客戶端頁面轉換不會調用 getStaticProps,因為只使用導出的 JSON。

使用增量靜態生成時,getStaticProps 將在後台執行以生成客戶端導航所需的 JSON。 您可能會以針對同一頁面發出多個請求的形式看到這一點,但是,這是有意為之,對最終用戶的性能沒有影響。

我在哪裡可以使用 getStaticProps

getStaticProps 只能從頁面匯出。 您不能從非頁面文件、_app_document_error 匯出它。

這種限制的原因之一是 React 需要在呈現頁面之前擁有所有必需的數據。

此外,您必須將 export getStaticProps 作為獨立函數使用 — 如果您將 getStaticProps 添加為頁面元件的屬性,它將不起作用。

在開發中的每個請求上運行

在開發(next dev)中,每個請求都會調用 getStaticProps

預覽模式

您可以使用預覽模式暫時繞過靜態生成並在請求時而不是建構時呈現頁面。 例如,您可能正在使用 headless CMS,並希望在草稿發布之前對其進行預覽。

增量靜態再生 Incremental Static Regeneration

Next.js 允許您在建構網站後建立或更新靜態頁面。 增量靜態重新生成 (ISR) 使您能夠在每個頁面的基礎上使用靜態生成,而無需重建整個網站。 使用 ISR,您可以在擴展到數百萬頁的同時保留靜態的優勢。

要使用 ISR,請將 revalidate 屬性添加到 getStaticProps:

function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 10 seconds
    revalidate: 10, // In seconds
  }
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
export async function getStaticPaths() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // We'll pre-render only these paths at build time.
  // { fallback: blocking } will server-render pages
  // on-demand if the path doesn't exist.
  return { paths, fallback: 'blocking' }
}

export default Blog

當對在建構時預先渲染的頁面發出請求時,它將最初顯示快取的頁面。

  • 在初始請求之後和 10 秒之前對頁面的任何請求也會被快取並且是瞬時的。
  • 在 10 秒窗口之後,下一個請求仍將顯示緩存(陳舊)頁面
  • Next.js 在後台觸發頁面的重新生成。
  • 頁面生成成功後,Next.js 將使快取失效並顯示更新的頁面。 如果後台重新生成失敗,舊頁面仍將保持不變。

當向尚未生成的路徑發出請求時,Next.js 將在第一個請求時服務器渲染頁面。 未來的請求將從快取中提供靜態文件。

注意:檢查您的上游數據提供者是否預設啟用了快取。 您可能需要禁用(例如 useCdn: false),否則重新驗證將無法提取新數據來更新 ISR 快取。 當 CDN 返回 Cache-Control 標頭時,可以在 CDN(對於被請求的端點)進行快取。

按需重新驗證

如果您將 revalidate 時間設置為 60,則所有訪問者將在一分鐘內看到您網站的相同生成版本。 使快取無效的唯一方法是在一分鐘後訪問該頁面的人。

v12.2.0 開始,Next.js 支持按需增量靜態重新生成以手動清除特定頁面的 Next.js 快取。 這使得在以下情況下更容易更新您的網站:

  • 來自 headless CMS 的內容已建立或更新
  • 電子商務 metadata 的更改(價格、描述、類別、評論等)

getStaticProps 中,您無需指定 revalidate 即可使用按需重新驗證。 如果省略 revalidate,Next.js 將使用預設值 false(不重新驗證)並且僅在調用 revalidate() 時按需重新驗證頁面。

使用按需重新驗證

首先,建立一個只有 Next.js 應用程式知道的秘密令牌。 此密鑰將用於防止未經授權訪問重新驗證 API 路由。 您可以使用以下 URL 結構訪問路由(手動或使用 webhook):

https://<your-site.com>/api/revalidate?secret=<token>

接下來,將密鑰作為環境變量添加到您的應用程式中。 最後,創建重新驗證 API 路由:

// pages/api/revalidate.js

export default async function handler(req, res) {
  // Check for secret to confirm this is a valid request
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: 'Invalid token' })
  }

  try {
    // this should be the actual path not a rewritten path
    // e.g. for "/blog/[slug]" this should be "/blog/post-1"
    await res.revalidate('/path-to-revalidate')
    return res.json({ revalidated: true })
  } catch (err) {
    // If there was an error, Next.js will continue
    // to show the last successfully generated page
    return res.status(500).send('Error revalidating')
  }
}

查看我們的示範 demo,了解按需重新驗證的實際效果。

在開發過程中測試按需 ISR

使用 next dev 在本地運行時,每個請求都會調用 getStaticProps。 要驗證您的按需 ISR 配置是否正確,您需要建立生產版本並啟動生產服務器:

$ next build
$ next start

然後,您可以確認靜態頁面已成功重新驗證。

錯誤處理和重新驗證

如果在處理後台重新生成時 getStaticProps 內部出現錯誤,或者您手動拋出錯誤,則最後成功生成的頁面將繼續顯示。 在下一個後續請求中,Next.js 將重試調用 getStaticProps

export async function getStaticProps() {
  // If this request throws an uncaught error, Next.js will
  // not invalidate the currently shown page and
  // retry getStaticProps on the next request.
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  if (!res.ok) {
    // If there is a server error, you might want to
    // throw an error instead of returning so that the cache is not updated
    // until the next successful request.
    throw new Error(`Failed to fetch posts, received status ${res.status}`)
  }

  // If the request was successful, return the posts
  // and revalidate every 10 seconds.
  return {
    props: {
      posts,
    },
    revalidate: 10,
  }
}

客戶端數據獲取

當您的頁面不需要 SEO 索引、不需要預先渲染數據或頁面內容需要頻繁更新時,客戶端數據獲取非常有用。 與服務器端渲染 API 不同,您可以在元件級別使用客戶端數據獲取。

如果在頁面級別完成,則在運行時獲取數據,並且頁面的內容會隨著數據的變化而更新。 在元件級別使用時,在元件掛載時獲取數據,並隨著數據的變化更新元件的內容。

請務必注意,使用客戶端數據獲取可能會影響應用程式的性能和頁面的加載速度。 這是因為數據獲取是在元件或頁面掛載時完成的,並且數據沒有被快取。

使用 useEffect 獲取客戶端數據

以下示範顯示何使用 useEffect hook 在客戶端獲取數據。

function Profile() {
  const [data, setData] = useState(null)
  const [isLoading, setLoading] = useState(false)

  useEffect(() => {
    setLoading(true)
    fetch('/api/profile-data')
      .then((res) => res.json())
      .then((data) => {
        setData(data)
        setLoading(false)
      })
  }, [])

  if (isLoading) return <p>Loading...</p>
  if (!data) return <p>No profile data</p>

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.bio}</p>
    </div>
  )
}

使用 SWR 獲取客戶端數據

Next.js 背後的團隊創建了一個名為 SWR 的用於數據獲取的 React hook library。 如果您在客戶端獲取數據,強烈建議您這樣做。 它處理快取、重新驗證、焦點跟踪、間隔重新獲取等。

使用與上面相同的示範,我們現在可以使用 SWR 獲取配置文件數據。 SWR 會自動為我們快取數據,並在數據過期時重新驗證數據。

有關使用 SWR 的更多內容,請查看 SWR 文件

import useSWR from 'swr'

const fetcher = (...args) => fetch(...args).then((res) => res.json())

function Profile() {
  const { data, error } = useSWR('/api/profile-data', fetcher)

  if (error) return <div>Failed to load</div>
  if (!data) return <div>Loading...</div>

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.bio}</p>
    </div>
  )
}

相關文章