使用 Next.js 進行靜態站點生成 (SSG)
靜態站點生成器 (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 只允許透過此埠的入站流量。
-
允許入站連線到埠
3000,然後重新載入防火牆。bashsudo ufw allow 3000 sudo ufw reload -
建立一個名為
sample-app的 Next.js 應用程式。bashnpx create-next-app@latest sample-app -
在設定嚮導中,輸入以下響應
✔ 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
-
導航到專案目錄,並開啟
package.json檔案bashcd sample-app nano package.json -
更改
start值,以便透過伺服器 IP 訪問應用程式。json"scripts": { "start": "next start -H 0.0.0.0", }, -
儲存並退出檔案。
-
導航到
src/app目錄。bashcd src/app -
在
src/app目錄中建立一個名為posts的新目錄,然後導航到其中。bashmkdir posts cd posts -
建立一個名為
page.js的 JavaScript 檔案bashnano page.js -
將以下程式碼複製並貼上到
page.js檔案中。jsximport 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> ); } -
儲存並退出檔案
-
從專案根目錄,建立一個生產構建並以生產模式啟動 Next.js 應用程式
bashnpm 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]'。
-
在
src/app/posts目錄中建立一個名為[id]的新目錄,然後導航到其中。bashmkdir "[id]" cd "[id]" -
建立一個新的 JavaScript 檔案。
bashnano page.js -
將以下程式碼複製並貼上到
page.js檔案中。jsximport 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> ); } -
儲存並關閉檔案。
-
重新建立生產構建並重新啟動應用程式
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 個帖子是在構建時建立的,頁面路徑根據外部資料進行了預渲染。如果您想探索其他選項,還可以檢視 getStaticPaths 和 Next.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 的資訊。