如何使用 Next.js 构建博客

2025-05-26

如何使用 Next.js 构建博客

你好,希望你一切都好。😊

我叫 Sagar,在Fabric担任软件工程师。我想分享我的想法和经验。构建一个极速博客是我的梦想,很多次我都开始尝试,但最终都失败了。Next.js 9.3 发布后,我们可以借助SSG (静态站点生成)API 轻松生成静态页面。在这篇博文中,我们将使用Next.js从头开始​​构建一个博客网站。在开始编写代码之前,我想先回答一个问题。

在这篇博文中

  • 为什么我选择 Next.js 而不是 Gatsby?
  • 项目设置
  • 应用程序结构
  • 创建博客内容
  • 方法是什么getStaticProps()
  • 方法是什么getStaticPaths()
  • 结论
  • 参考

为什么我选择 Next.js 而不是 Gatsby?

在这里,我并不是想说Gatsby不好。Next.js 和 Gatsby 各有优势。但我发现使用 Gatsby 需要进行额外的配置,而使用 Next.js 则不需要。而且有很多 Gatsby 插件可以帮助我们减轻开发负担。

有一篇很好的文章可以比较 Next.js 和 Gatsy 的功能。

  1. https://www.gatsbyjs.org/features/jamstack/gatsby-vs-nextjs
  2. https://blog.logrocket.com/next-js-vs-gatsbyjs-a-developers-perspective/
  3. https://dev.to/jameesy/gatsby-vs-next-js-what-why-and-when-4al5

理论讲得够多了,让我们开始编码吧……

项目设置

创建项目文件夹并使用 npm 初始化它。

mkdir my-personal-blog 
cd my-personal-blog
npm init --y
Enter fullscreen mode Exit fullscreen mode

npm init --y命令将package.json在根级别创建文件。

在您的项目中安装nextreactreact-dom。确保您的next.js版本为 9.3 或更高版本,否则 SSG API 将无法使用。

npm install next react react-dom --save
npm install uuid unified remark-html remark-highlight.js remark-parse gray-matter --save-dev
Enter fullscreen mode Exit fullscreen mode

好的,等一下,让我快速解释一下项目依赖关系。

  1. uuid - 用于创建 RFC4122 UUID。
  2. 统一- 通过语法树解析、检查、转换和序列化内容的接口。
  3. remark-html - Remark 插件将 Markdown 编译为 HTML
  4. remark-highlight.js - Remark 插件使用 highlights.js 突出显示代码块。
  5. remark-parse - 用于解析 Markdown 的 Remark 插件
  6. gray-matter - 从字符串或文件中解析前言。

打开package.json并添加以下脚本:

"scripts": {   
  "dev": "next",   
  "build": "next build",
  "start": "next start"
}
Enter fullscreen mode Exit fullscreen mode

应用程序结构

在我们开始编写代码之前,请先设置文件夹结构,使其看起来像这样:

/my-personal-blog/
|--/components
|--/node_modules
|--/contents
|--/pages
|----/index.js
|----/blog
|------/[slug].js
|--/styles
|----/global.css
|--/utils
|--package.json
Enter fullscreen mode Exit fullscreen mode

创建博客内容

再一步,让我们hello-world.md在项目contents文件夹中添加一个文件,创建一个名为 的文件hello-world.md,并添加以下 Markdown 内容。稍后,我们会将这些内容渲染到网站上。

---
title: My first blog
slug: hello-world
date: "31-05-2020"
---

Pellentesque condimentum velit vel justo rutrum, sit amet commodo diam tincidunt. Nunc diam massa, interdum ut aliquet at, scelerisque ac ex. Integer cursus sem ac pretium posuere. Ut at odio nulla. Phasellus nec ante luctus, egestas dui id, maximus dui. In aliquam elit sit amet sollicitudin luctus. Nunc nec leo quis ante vestibulum egestas. In dignissim libero vitae congue bibendum. Sed iaculis eros a leo pellentesque, et ultrices leo malesuada. Nullam ultrices rutrum accumsan. Pellentesque tempus sapien et vestibulum placerat.

Donec ultrices in tortor eget facilisis. Pellentesque orci risus, vulputate consequat fermentum eget, euismod sed nulla. Sed luctus sapien quis magna lobortis porttitor. In porttitor nibh id tincidunt imperdiet. Suspendisse ultricies tellus dolor, et gravida tortor vehicula quis. Maecenas tempus est sit amet congue rhoncus. Vivamus vitae felis lacinia, viverra nibh id, pulvinar eros. In viverra venenatis ligula, vitae efficitur felis vehicula vitae. Vestibulum feugiat vel risus iaculis tincidunt.
Enter fullscreen mode Exit fullscreen mode

在您的项目中创建一个页面目录并填充 pages/index.js以下内容:

import React from "react";
import Link from "next/link";

function IndexPage(props) {
  return (
    <div>
      <h1>Blog list</h1>
      <ul>
        {props.blogs.map((blog, idx) => {
          return (
            <li key={blog.id}>
              <Link href={`/blog/${blog.slug}`}>
                <a>{blog.title}</a>
              </Link>
            </li>
          );
        })}
      </ul>
    </div>
  );
}

// This function gets called at build time on server-side.
export async function getStaticProps() {
  const fs = require("fs");
  const matter = require("gray-matter");
  const { v4: uuid } = require("uuid");

  const files = fs.readdirSync(`${process.cwd()}/contents`, "utf-8");

  const blogs = files
    .filter((fn) => fn.endsWith(".md"))
    .map((fn) => {
      const path = `${process.cwd()}/contents/${fn}`;
      const rawContent = fs.readFileSync(path, {
        encoding: "utf-8",
      });
      const { data } = matter(rawContent);

      return { ...data, id: uuid() };
    });

    // By returning { props: blogs }, the IndexPage component
  // will receive `blogs` as a prop at build time
  return {
    props: { blogs },
  };
}

export default IndexPage;
Enter fullscreen mode Exit fullscreen mode

上面的文件中有很多内容index.jsx。在这里,我们创建了一个名为 的函数组件IndexPage,它将从getStaticProps方法中接收博客数据作为 prop。在理解方法内部的代码编写之前,getStaticProps()我想先解释一下getStaticProps()

方法是什么getStaticProps()

简单来说,此方法仅在构建时运行,并将 props 传递给页面组件进行预渲染,并且它不会接收任何请求时间数据,如查询参数或 HTTP 标头。

主要用于在构建时获取数据,数据来源可以是 API、静态文件,甚至是数据库查询。

从性能角度来看,如果预先构建页面,就无需向用户传递额外的 js 文件。否则,页面交互时间会大幅增加。


让我们回到IndexPage组件,如果你浏览里面写的代码,getStaticProps()你会看到我需要内置模块使用从当前目录fs读取文件夹将给我文件夹中列出的所有文件。所以我只过滤 markdown 文件(以 .md 结尾的文件)。_contentprocess.cwd()fs.readdirSync(path)_content

我正在迭代files并将这些文件内容传递给 ,gray-matter它将解析 front-matter markdown 文件并返回一个包含datacontent属性的对象。在这个getStaticProps()方法中我们不需要内容,所以我跳过了它,但在特定的博客页面上我们需要它。

通过返回{ props: blogs },IndexPage 组件将blogsprop构建时接收。

IndexPage组件开始,我映射博客道具并使用Link标签呈现所有博客,以便我们能够导航到特定的博客。

https://dev-to-uploads.s3.amazonaws.com/i/m4xdjk6sjvnanaxxzzmy.png

现在,是时候从查询参数中接收 slug 并将博客内容渲染到屏幕上了。让我们创建一个名为[slug].jsinsidepages/blog/文件夹的文件,并查看下面的BlogPostPage组件。为了基于 Markdown 文件静态生成所有博客文章,我们需要指定生成的路径。为此,我们需要导出一个异步函数getStaticPaths()

// file: pages/blog/[slug].js
import React from "react";

function BlogPostPage(props) {
  return (
    <div>
            <h1>{props.blog.title}</h1>
      <section dangerouslySetInnerHTML={{ __html: props.blog.content }}></section>
    </div>
  );
}

// pass props to BlogPostPage component
export async function getStaticProps(context) {
  const fs = require("fs");
  const html = require("remark-html");
  const highlight = require("remark-highlight.js");
  const unified = require("unified");
  const markdown = require("remark-parse");
  const matter = require("gray-matter");

  const slug = context.params.slug; // get slug from params
  const path = `${process.cwd()}/contents/${slug}.md`;

    // read file content and store into rawContent variable
    const rawContent = fs.readFileSync(path, {
    encoding: "utf-8",
  });

  const { data, content } = matter(rawContent); // pass rawContent to gray-matter to get data and content

  const result = await unified()
    .use(markdown)
    .use(highlight) // highlight code block
    .use(html)
    .process(content); // pass content to process

  return {
    props: {
            blog: {
                ...data,
          content: result.toString(),
            }
    },
  };
}

// generate HTML paths at build time
export async function getStaticPaths(context) {
  const fs = require("fs");

    const path = `${process.cwd()}/contents`;
  const files = fs.readdirSync(path, "utf-8");

    const markdownFileNames = files
    .filter((fn) => fn.endsWith(".md"))
    .map((fn) => fn.replace(".md", ""));

  return {
    paths: markdownFileNames.map((fileName) => {
      return {
        params: {
          slug: fileName,
        },
      };
    }),
    fallback: false,
  };
}

export default BlogPostPage;
Enter fullscreen mode Exit fullscreen mode

方法是什么getStaticPaths()

此方法定义了构建时必须呈现为 HTML 的路径列表,如果页面包含动态路由(例如),则非常有用blog/[slug].jsNext.js它将静态预渲染由 指定的所有路径getStticPaths()。从getStaticPaths()方法强制返回path和一个fallback键。如果fallbackfalse,则构建时未返回的任何路径都getStaticPaths()将导致 404 页面。

https://dev-to-uploads.s3.amazonaws.com/i/u2vx9ycjuvl2x8x8lqea.png

您可以在这里找到 git 存储库:https://github.com/sagar-gavhane/my-personal-blog

结论

使用 Next.js 创建博客网站Next.js非常简单。我们只需遵循几个步骤,例如读取文件、解析文件内部内容getStaticProps()并使用方法生成预渲染路径getStaticPaths()。我发现很多人都在尝试利用这个强大的功能来预渲染静态页面。最近,Next.js 在 v9.4 中宣布了一项增量静态再生功能,Next.js这将帮助我们静态预渲染无限数量的页面。

参考链接

  1. https://nextjs.org/blog
  2. https://github.com/vercel/next.js/tree/canary/examples/blog-starter
  3. https://github.com/tscanlin/next-blog
  4. https://www.gatsbyjs.org/features/jamstack/gatsby-vs-nextjs
  5. https://blog.logrocket.com/next-js-vs-gatsbyjs-a-developers-perspective/
  6. https://dev.to/jameesy/gatsby-vs-next-js-what-why-and-when-4al5

https://i.gifer.com/5qJ.gif

感谢您的阅读。希望您喜欢这篇文章,欢迎点赞、评论或分享给您的朋友。如需更深入地了解Next.jsAPI 的签出功能,请访问官方文档网站。

文章来源:https://dev.to/sagar/building-a-blog-with-next-js-253
PREV
使用 RxJS 在 JavaScript 中进行反应式编程。
NEXT
使用无服务器框架构建 RESTful API