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