使用 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 的信息。