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

使用 Next.js 进行静态网站生成 (SSG)

作者头像Vultr阅读时间:7 分钟

静态网站生成器 (SSG) Static Site Generators (SSGs) 是用于预先生成 HTML 的工具,以便服务器可以高效地向所有访问者发送相同的内容,而无需首先创建它。与动态网页不同,动态网页可能需要在页面加载时查询数据库并填充模板,而 SSG 预先构建了文件,因此当您部署它们时,服务器在访问您的网站时需要做的工作更少。

SSG 具有多种优势,例如更快的加载时间和更好的访客用户体验。对于开发人员来说,静态网站的可扩展性也很方便,因为您可以缓存和服务整个网站 内容分发网络 (CDN),而不是部署更多服务器。由于没有服务器端处理或数据库交互,静态网站对 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

关注 MDN 最新动态

订阅 MDN 新闻通讯,不错过任何关于最新 Web 开发趋势、技巧和最佳实践的更新。