Article Cover Image - Static Site Generation (SSG) with Next.js
贊助

使用 Next.js 進行靜態站點生成 (SSG)

閱讀時間 7 分鐘

靜態站點生成器 (SSGs) 是一種預先生成 HTML 的工具,以便伺服器能夠高效地將相同的內容傳送給所有訪問者,而無需首次建立。與動態網頁不同,動態網頁在頁面載入時伺服器可能會查詢資料庫並填充模板,SSG 會預先構建檔案,這樣在部署時,伺服器在網站被訪問時需要做的功就更少了。

SSG 有許多優點,例如更快的載入時間和更好的使用者體驗。對於開發者來說,靜態站點的可伸縮性也很方便,因為你可以將整個網站快取並在 內容分發網路 (CDNs) 中提供,而不是部署更多伺服器。由於沒有伺服器端處理或資料庫互動,靜態站點更能抵禦 SQL 注入或跨站指令碼 (XSS) 攻擊等安全漏洞。

在本文中,我們將建立一個 Next.js 應用程式,並使用預設渲染模式來建立一個性能高且高效的靜態站點。

在 Vultr 上設定 Next.js 應用

首先,按照我們上一篇文章中 在 Vultr 上部署伺服器 部分的步驟,使用 NodeJS 映象部署一個伺服器。接下來,透過 SSH 訪問伺服器終端,併為我們的 Web 應用程式設定一個專案。

為了開始,我們將使用 create-next-app,這是一個用於快速引導 Next.js 應用程式的入門模板。作為示例,我們將在應用程式中使用靜態生成,但會在構建時獲取一些資料。這樣,我們將構建一個靈活的網站,同時還能受益於部署靜態頁面。

我們將使用 Nano 文字編輯器在伺服器上建立和編輯專案檔案。你可以檢視 快捷鍵備忘單 來幫助使用 Nano。我們還將使用 Uncomplicated Firewall (UFW) 來控制允許進出伺服器的流量。我們的 Next 應用使用埠 3000,因此我們可以使用 UFW 只允許透過此埠的入站流量。

  1. 允許入站連線到埠 3000,然後重新載入防火牆。

    bash
    sudo ufw allow 3000
    sudo ufw reload
    
  2. 建立一個名為 sample-app 的 Next.js 應用程式。

    bash
    npx create-next-app@latest sample-app
    
  3. 在設定嚮導中,輸入以下響應

    ✔ Would you like to use TypeScript? … No
    ✔ Would you like to use ESLint? … Yes
    ✔ Would you like to use Tailwind CSS? … No
    ✔ Would you like to use `src/` directory? … Yes
    ✔ Would you like to use App Router? (recommended) … Yes
    ✔ Would you like to customize the default import alias (@/*)? … No
    
  4. 導航到專案目錄,並開啟 package.json 檔案

    bash
    cd sample-app
    nano package.json
    
  5. 更改 start 值,以便透過伺服器 IP 訪問應用程式。

    json
    "scripts": {
        "start": "next start -H 0.0.0.0",
    },
    
  6. 儲存並退出檔案。

  7. 導航到 src/app 目錄。

    bash
    cd src/app
    
  8. src/app 目錄中建立一個名為 posts 的新目錄,然後導航到其中。

    bash
    mkdir posts
    cd posts
    
  9. 建立一個名為 page.js 的 JavaScript 檔案

    bash
    nano page.js
    
  10. 將以下程式碼複製並貼上到 page.js 檔案中。

    jsx
    import React from "react";
    import styles from "../page.module.css";
    
    async function getPosts() {
      const res = await fetch("https://jsonplaceholder.typicode.com/posts");
      return res.json();
    }
    
    const posts = await getPosts();
    
    export default async function PostsPage() {
      return (
        <main className={styles.main}>
          <h1>Posts archive</h1>
          <ol>
            {posts.map((post) => (
              <li key={post.id}>{post.title}</li>
            ))}
          </ol>
        </main>
      );
    }
    
  11. 儲存並退出檔案

  12. 從專案根目錄,建立一個生產構建並以生產模式啟動 Next.js 應用程式

    bash
    npm run build && npm run start
    

您應該會看到類似這樣的內容

  Creating an optimized production build ...
✓ Compiled successfully
✓ Linting and checking validity of types
✓ Collecting page data
✓ Generating static pages (6/6)
✓ Collecting build traces
✓ Finalizing page optimization

Route (app)                              Size     First Load JS
┌ ○ /                                    5.42 kB        92.4 kB
├ ○ /_not-found                          871 B          87.9 kB
└ ○ /posts                               137 B          87.2 kB
+ First Load JS shared by all            87 kB
 ├ chunks/23-ef3c75ca91144cad.js        31.5 kB
 ├ chunks/fd9d1056-2821b0f0cabcd8bd.js  53.6 kB
 └ other shared chunks (total)          1.87 kB

○  (Static)  prerendered as static content

您可以在 http://<server-ip>:3000/posts 檢視應用程式,以瞭解我們目前生成的內容。

讓我們看一下我們到目前為止使用的程式碼。對於資料獲取,getPosts() 向一個提供佔位符資料的 URL 傳送一個 GET 請求。您的應用程式可能不會進行此類獲取請求;您可能從伺服器本地的檔案或甚至查詢本地資料庫中獲取資訊。此示例很有幫助,因為它展示了當我們需要來自網站的外部資料時會發生什麼。

我們將請求結果儲存在一個名為 posts 的變數中,我們可以在頁面模板中迭代它。PostsPage 函式是元件的預設匯出,它返回一個 JSX 元素,該元素將被渲染為元件的輸出。我們迭代從端點返回的每個帖子,並建立一個有序的帖子標題列表。

您可以透過按 Ctrl + C 來停止應用程式。

理解 Next.js 應用中的 SSG

以下是使用 Next.js 應用中的 SSG 時應理解的關鍵概念,包括它們帶來的好處。

  • 預渲染
    • 在構建過程中,網站的所有頁面都會被預渲染成靜態 HTML 檔案。內容被生成一次並作為靜態檔案儲存。
  • 內容來源
    • 您可以從各種來源獲取內容,例如無頭 CMS、資料庫或 REST API。
    • 在構建過程中,內容會被獲取和處理以建立 HTML 檔案。
  • 部署
    • 您可以使用 Web 伺服器或 內容分發網路 (CDN) 來快取和高效地提供構建後的檔案。如果您知道檔案不會更改,那麼您可以更積極地快取,這對訪問者和您的伺服器成本都更有效。
    • 您無需考慮預配和維護伺服器端邏輯的環境。

為靜態 Next.js 站點新增動態路由

現在我們知道如何在構建時從公共 API 獲取資料,並使用響應填充頁面模板以生成靜態頁面,我們可能需要做更復雜的事情。

我們在 /posts 有一個帖子存檔,列出了所有帖子標題,但我們想為每個帖子建立單獨的頁面。我們將為此使用數字帖子 id(例如 /posts/12),但您可以想象使用 title 也會很有趣,例如 /posts/my-cool-post

這提出了一個有趣的問題,因為我們在構建時可能不知道我們正在獲取的帖子的 ID。我們如何為每個帖子建立模板頁面?我們可以使用動態路由來處理這種情況,這允許我們對路由進行特殊處理,但仍保持靜態站點生成作為首選輸出。

要為數字部落格 ID 新增動態路由,請使用特殊的 Next.js 格式 [segmentName](在本例中為 [id])建立一個目錄。如果您在另一臺機器上使用 zsh 等 shell 執行這些步驟,您將需要引用方括號,例如 '[id]'

  1. src/app/posts 目錄中建立一個名為 [id] 的新目錄,然後導航到其中。

    bash
    mkdir "[id]"
    cd "[id]"
    
  2. 建立一個新的 JavaScript 檔案。

    bash
    nano page.js
    
  3. 將以下程式碼複製並貼上到 page.js 檔案中。

    jsx
    import React from "react";
    import { notFound } from "next/navigation";
    import styles from "../../page.module.css";
    
    async function getPost(id) {
      const res = await fetch(
        `https://jsonplaceholder.typicode.com/posts/${id}`,
      );
      if (!res.ok) {
        return null;
      }
      return res.json();
    }
    
    export async function generateStaticParams() {
      const res = await fetch("https://jsonplaceholder.typicode.com/posts");
      const posts = await res.json();
    
      return posts.map((post) => ({
        id: post.id.toString(),
      }));
    }
    
    export default async function PostPage({ params }) {
      const post = await getPost(params.id);
    
      if (!post) {
        notFound();
      }
    
      return (
        <main className={styles.main}>
          <h1>{post.title}</h1>
          <p>{post.body}</p>
        </main>
      );
    }
    
  4. 儲存並關閉檔案。

  5. 重新建立生產構建並重新啟動應用程式

    bash
    # in the project root:
    npm run build && npm run start
    

您應該看到以下內容

Creating an optimized production build ...
✓ Compiled successfully
✓ Linting and checking validity of types
✓ Collecting page data
✓ Generating static pages (106/106)
✓ Collecting build traces
✓ Finalizing page optimization

Route (app)                              Size     First Load JS
┌ ○ /                                    5.42 kB        92.4 kB
├ ○ /_not-found                          871 B          87.9 kB
├ ○ /posts                               338 B          87.4 kB
└ ● /posts/[id]                          338 B          87.4 kB
   ├ /posts/1
   ├ /posts/2
   ├ /posts/3
   └ [+97 more paths]

請注意,這次我們生成了 106 個頁面,所以額外的 100 個帖子是在構建時建立的,頁面路徑根據外部資料進行了預渲染。如果您想探索其他選項,還可以檢視 getStaticPathsNext.js 示例儲存庫,瞭解將動態路由新增到 SSG 構建的其他方法。

您現在可以訪問伺服器 http://<server-ip>:3000/posts 並透過數字 ID(例如 http://<server-ip>:3000/posts/2)探索每個帖子。

實際用途和示例

以下是一些使用 Next.js 的 SSG 應用效果很好的示例

  • 內容豐富的網站:部落格、文件和營銷網站,其內容相對靜態且變化不大。
  • 電子商務:您可以預渲染產品頁面,以便擁有載入速度快的頁面,而轉換率至關重要。
  • Jamstack 架構:Jamstack 網站通常在客戶端使用 JavaScript 功能,同時保持伺服器端靜態。如果您在客戶端進行大量網路請求,請注意您可能會將效能成本轉移給您的訪問者,因此請仔細權衡此選項。

總結

在本文中,我們建立了一個 Next.js 應用程式並將其部署到 Vultr NodeJS 映象。我們學習瞭如何使用 SSG 來構建一個快速、可伸縮且相對安全的應用程式。透過學習如何使用 SSG,您可以使用現代工具和架構建立更具彈性的 Web 應用程式。

這是一篇 Vultr 的贊助文章。Vultr 是全球最大的私營雲計算平臺。Vultr 深受開發者喜愛,已為 185 個國家的 150 萬多客戶提供服務,提供靈活、可伸縮的全球雲計算、雲 GPU、裸金屬和雲端儲存解決方案。瞭解更多關於 Vultr 的資訊。